Mybatis框架

在经过Mybatis入门学习Mybatis实现增删改查后,又学习了后续整体的框架

MyBatis核心接口和类

1. SqlSessionFactoryBuilder负责构建SqlSessionFactory,并且提供了多个build()方法的重载。也就是说:此对象可以从xml配置文件,或从Configuration对象来构建SqlSessionFactory。
2. SqlSessionFactory就是创建SqlSession实例的工厂。通过openSession方法来获取SqlSession对象。而且,SqlSessionFactory一旦被创建,那么在整个应用程序期间都存在。
3. SqlSession是一个面向程序员的接口,它提供了面向数据库执行sql命令所需的所有方法。SqlSession对应一次数据库会话,它是线程不安全的。

封装持久层

MyBatis开发DAO层有两种方式:

  1. 原始dao方式
  2. mapper代理方式

原始dao方式

按照JDBC课程中封装dao层的方式,我们可以先封装一个 Util 工具类,在此工具类中封装一个获取SqlSessionFactory的方法。然后创建dao接口和实现类。
SqlSessionFactory工具类:

package com.neusoft.util;
import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Util {
    public static SqlSessionFactory sqlSessionFactory = null;
    public static SqlSessionFactory getSqlSessionFactory() {
        if(sqlSessionFactory==null){
            String resource = "mybatis/SqlMapConfig.xml";
            try {
                Reader reader = Resources.getResourceAsReader(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sqlSessionFactory;
    }
}

dao接口:

package com.neusoft.dao;
import java.util.List;
import com.neusoft.po.Emp;
public interface EmpDao {
    public Emp getEmpById(int empno);
    public List<Emp> listEmp();
}

dao的实现类:

package com.neusoft.dao.impl;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import com.neusoft.dao.EmpDao;
import com.neusoft.po.Emp;
import com.neusoft.util.Util;
public class EmpDaoImpl implements EmpDao{
    @Override
    public Emp getEmpById(int empno){
        SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
        Emp emp = sqlSession.selectOne("emp.getEmpById",empno);
        sqlSession.close();
        return emp;
    }
    @Override
    public List<Emp> listEmp(){
        SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
        List<Emp> list = sqlSession.selectList("emp.listEmp");
        sqlSession.close();
        return list;
    }
}

测试:

EmpDao dao = new EmpDaoImpl();
Emp emp = dao.getEmpById(7369);
System.out.println(emp);
List<Emp> list = dao.listEmp();
for(Emp emp : list) {
    System.out.println(emp);
}

从上面代码中可以发现,使用原始dao方式存在很多问题:

  1. dao实现类中存在大量重复代码
  2. 调用sqlSession方法时,将statement的id硬编码了
  3. 调用sqlSession方法时传入的参数,由于sqlSession使用了泛型,所以即使传入参数的数据类型错误,在编译阶段也不会报错。

mapper代理方式

只需要mapper接口和mapper.xml映射文件,Mybatis可以自动生成mapper接口实现类代理对象。编mapper接口需要遵循4个一致

  1. Mapper映射文件的名字和mapper接口的名字一致
  2. Mapper映射文件中statementId的值,与mapper接口中对应的方法名一致
  3. Mapper映射文件中statement的输入参数parameterType的类型,与mapper接口中对应方法的参数类型一致。
  4. Mapper映射文件中statement的输出参数resultType的类型,与mapper接口中对应方法的返回值类型一致

解释一下:

  • 第一个一致:
    名字相同
    而且前面学习提到过,xml映射文件的namespace属性的取值问题,当使用原始dao开发时,可以随意取值;使用mapper代理开发时,取值为mapper接口的全路径
    namespace取值

  • 第二个一致:方法名一致
    image
    image

  • 第三个一致:输入类型一致
    image
    image

  • 第四个一致:输出类型一致
    image
    image

还要记得在SqLMapConfig中注册映射文件
image

  • 优化:
    如果映射文件与mapper接口名称一致,且处在同一个文件夹内,那么就可以使用接口来批量加载映射文件。
    image
    注意一个是“/”,一个是“.”
    在第四个一致中,xml文件中的输出类型写的很长,也可以进行简化,同样在SqlMapConfig中
    image
    image

  • 代码:

映射文件EmpMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace属性: 现在可以随意给值 ,但当使用mapper代理方式开发时,有特点的取值。即为mapper接口的全路径-->
<!--        1.  Mapper映射文件的名字和mapper接口的名字一致-->
<!--        2.  Mapper映射文件中statementId的值,与mapper接口中对应的方法名一致-->
<!--        3.  Mapper映射文件中statement的输入参数parameterType的类型,与mapper接口中对应方法的参数类型一致。-->
<!--        4.  Mapper映射文件中statement的输出参数resultType的类型,与mapper接口中对应方法的返回值类型一致-->

<mapper namespace="com.neuedu.mapper.EmpMapper">
    <!-- 按id查询员工 -->
    <select id="findEmpById" parameterType="int" resultType="Employee">
     <!-- id就是这条语句的唯一标识,parameterType是员工id的属性,resultType是返回类型,要把实体类的路径写完整 -->
        select * from tb_emp where id = #{value}
     <!-- 占位符要使用#{} parameterType的类型如果为 简单类型(基本类型和String),#{}中的值任意。-->
    </select>

    <!-- 按名称模糊查询,当查询结果有多个时,resultType的类型为pojo-->
    <select id="findEmpByName" parameterType="string" resultType="Employee">
    <!-- 不使用拼接,要在test中加% -->
          SELECT * FROM tb_emp WHERE NAME LIKE #{value}
        <!-- 字符串拼接的方法。注意:慎用,会产生sql注入。-->
    <!--  SELECT * FROM tb_emp WHERE NAME LIKE '%${value}%'-->
    </select>

    <!-- 删除员工 -->
    <delete id="deleteEmp" parameterType="int">
	delete from tb_emp where id=#{value}
    </delete>
    <!-- 更新员工-->
     <!--   如果输入参数为pojo类型,#{pojo对象的属性名}  -->
    <update id="editEmp" parameterType="com.neuedu.pojo.Employee">
	update tb_emp set
		loginName=#{loginName},name=#{name},email=#{email},
		status=#{status},deptId=#{deptId},photoPath=#{photoPath}
		where id=#{id}
    </update>

    <!-- 插入员工 -->
    <insert id="saveEmp" parameterType="com.neuedu.pojo.Employee">
        INSERT INTO tb_emp
        (loginname,PASSWORD,NAME,hiredate,email,photopath,deptId)
        VALUES (#{loginName},#{password},#{name},#{hiredate},#{email},#{photoPath},#{deptId})
        <!-- order: 执行时机   keyColumn:表中自动名称  keyProperty:映射的pojo属性名称 -->
        <selectKey order="AFTER" resultType="int" keyColumn="id" keyProperty="id">
            SELECT LAST_INSERT_ID()
        </selectKey>
    </insert>
</mapper>
接口类EmpMapper.java
package com.neuedu.mapper;

import java.util.List;

import com.neuedu.pojo.Employee;

public interface EmpMapper {
	/**
	 *  登录方法
	 * @param loginName 登录名
	 * @param password  密码
	 * @return  登录员工的信息
	 */
	Employee login(String loginName, String password);
	/**
	 *  添加员工
	 * @param emp  插入信息
	 */
	void saveEmp(Employee emp);
	/**
	 *  删除员工
	 * @param id  员工id
	 */
	void deleteEmp(Integer id);
	/**
	 *   修改员工
	 * @param emp 员工信息
	 */
	void updateEmp(Employee emp);
	/**
	 *  按id查询
	 * @param id 员工id
	 * @return  员工信息
	 */
	Employee findEmpById(Integer id);
	/**
	 * 按照name查询
	 * @param name 员工姓名
	 * @return 员工列表
	 */
	Employee findEmpByName(String name);
	}
测试类TstMybatis
package com.neuedu.test;

import com.neuedu.mapper.EmpMapper;
import com.neuedu.pojo.Employee;
import com.neuedu.utils.DBUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class TestMyMapper {
    @Test
    public void testFindById() {
        SqlSession session = DBUtils.getSession();
        EmpMapper empMapper = session.getMapper(EmpMapper.class);
        Employee emp = empMapper.findEmpById(2);
        System.out.println(emp.getName());
        session.close();
    }
}

输入参数

parameterType 属性:表示执行sql语句时,需要使用的数据。

简单类型或String

在statement语句中#{}的值可以任意
image

pojo类型

输入参数为pojo类型,#{}中的值为pojo对象的属性名
image

扩展的pojo属性

需求查找员工所在的部门名称
image
image
问题提出:员工表emp中没有部门名的字段,需要联合到部门表dept;即Employee实体类无法满足需求
解决方法:

  1. 在Employee类中加入一个属性dname,这样是最简单的,但是可能会出现问题。因为Employee类是Mybatis根据数据库的字段自动生成的,添加属性不规范后再次生成的能会覆盖掉手动添加的属性。所以可以在现有的pojo上做出扩展
  2. 建立一个EmployeeVo继承类,当中只声明一个属性dname,其余的继承Employee类
    image
    这个类与数据库没有映射关系可以随便修改,称之为扩展的pojo。
    sql
    在写sql语句时可以直接用
    image
    测试:
    image
    记得保持四个一致,要在EmpMapper接口类中加入方法
    image

包装的pojo

在包装POJO类中,关联多个POJO对象,比如按照上例可以把emp和dept联合起来组成EmpDept类;
image
sql语句
写sql语句时输入类型也可以直接用:
image
注意#{}里面的值,empdept里没有name属性只有imageemp与dept,但是可以用emp与dept来访问他们的name
测试
写测试的时候会复杂一点
image
在接口中加入方法
image

输出参数:resultType

简单类型

如果resultType为简单类型,查询结果必须为一个值。一行一列。
image
四个一致:在接口中定义方法
image
测试方法
image

pojo

如果select语句使用投影查询时。即查询列表只定义表中部分字段。
此时结果中只会封装查询列表中有的字段,查询列表中没有的字段,输出参数pojo属性值为默认值

Employee中的所有属性

image
写一个投影查询
image
可以看出查询语句并没有把所有的属性都查出来

接口定义方法:image
在Employee.java实体类中加入一个tostring方法,只选取部分数据库中的字段:
image
测试:
image
输出结果:
image
同样能映射成功。
思考:此时用的是表中字段的名字与属性映射还是用的查询列表的名字与属性映射?

验证:
在查询列表中起别名再测试
image
结果:
image
email为空。

说明输出参数进行映射时,是使用的查询列表的别名和pojo属性名进行映射,如果字段别名和pojo属性名不对应,则会映射失败

把所有的都起别名,只剩下id能对应上
image
结果:
image

结论:即使只有一个属性能对应,也会创建pojo对象

如果所有的都对应不上呢?
image
结果:
image

不报错,输出为空。

结论:所有属性都对应不上则不会创建pojo对象

如果必须要起别名来查询,怎么解决?
可以利用resultMap

输出参数:resultMap

在statement语句中把resultType的位置写为resultMap
image

  1. resultMap是一个标签,每一个statement语句都会根据标签的id去找他的映射类型,上例中的类型就是Employee,有不同的对应不上的自己定义。
  1. 主键属性映射用<id>普通属性映射用<result>

再次测试:
image

email映射成功

但是resultMap的主要作用是实现关联映射,在后续会学到

动态sql

where和if

运用:条件查询
在前几天j学习aveweb时用servlet写条件查询时,写了一个"1=1",再连接其他的条件
image

测试

  1. 没有输入时
    image
    image
  2. 给name赋值查询:
    image
    image
  3. 再给email赋值查询:
    image
    image
  4. 再给部门赋值查询:
    image
    image
    说明SQL语句正确可以正常拼接条件,但是在sql语句中加一个"1=1"不得劲

优化:放在<where>标签里面,<if>语句会加上and
image
测试能成功,但是发现sql语句中没有了and
image

解释:where标签会为sql语句添加where字句,同时会去掉第一个条件前面的and,所以放心的加and,不管哪条语句是第一个条件都会自动去掉前面的and。

把所有条件去掉
image
测试:
image
所有条件都不满足where自然也就不起作用了

第二个sql语句:
image
在接口类中定义:
image
测试:
image
结果:
image
都没什么问题。

重点是两个语句的where标签中的语句是一模一样的,就是说这段代码可以复用,那就可以封装起来。

封装sql代码

把重复代码写在<sql>标签内
image

在statement中用<include>来包含<sql>语句
image

注意:

  1. 代码片段中,封装的sql语法尽量能够多被复用。所以where不太适合在片段中
  2. 代码片段中尽量进行单表操作,为了提高复用性。能重复用到很多表的毕竟是少数。

改进:把sql片段的where删除
image

<include>前加上<where>image

foreach

用于批量删除。
输入参数中,应该包含一个数组(List)类型的数据,该数据包含要删除的所有id。
此时输入参数为pojo,即Employee。需要扩展属性,添加一个int[] ids属性

删除语句
image

测试
image

结果
image

对比着理解sql的书写:
image

参数的含义:
tem表示集合中每一个元素进行迭代时的别名
index指定一个名字,用于表示在迭代过程中,每次迭代到的位置
open表示该语句以什么开始
separator表示在每次进行迭代之间以什么符号作为分隔符
close表示以什么结束

第二条的书写
遇到and与or时,and的优先级更高,所以可以在or的语句外加一个"()"
image

sql语句中,除了上面用到的几个子标签,还有其他的
image

choose:多条件判断,按顺序判断其内部when标签中的test条件出否成立,如果有一个成立,则 choose 结束。
当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的sql。
类似于Java 的 switch 语句,choose 为 switch,when 为 case,otherwise 则为 default。

image

image

trim:插入数据,动态拼接insert语句.主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是prefix和suffix
可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的属性是prefixOverrides和suffixOverrides;

image
image
帮助我们去除末尾的“,”并填上“()”。

逆向工程

(当前阶段只是了解)
Mybatis提供了一个项目,可以通过数据库中的表自动导出对应的pojo、mapper.xml、mapper.java。逆向工程只适合单表操作。

创建

image

修改配置文件

image

执行逆向工程

image

关联映射

以下图的电商业务为例,查询名叫张三的人买过哪些东西,就需要多表查询
image
数据库表详情:
image
image
image
image
image

需求:查询订单关联的用户

resultType:一对一关联映射

image

测试
image

结果
image

没什么问题,但是当业务需求越来越多,需要的包装的pojo就会越来越多,与数据库无关的实体类越来越多,导致系统维护起来很麻烦

resultMap:一对一关联映射

准备工作:在pojo的order类中加入user,即定义关联字段并提供get与set
image

查询语句
image
resultMap进行映射
image

  1. resultMap的type属性即为select语句的输出类型,即为order(进行了打包,完整路径为com.nenedu.pojo.Order)
  2. 先对order类中本身有的属性进行映射,主键用<id>,普通属性用<result>
  3. 再对order类中加入的User进行映射,一对一映射用<association>,其中的JavaType表示关联属性的类型,即为User(com.neuedu.pojo.User)
  4. 细心,一个属性一个属性的来。

接口方法
image

测试
image

结果与resultTyoe相同。
注意其结构类型:
image

resultMap:一对多关联映射

需求:查询用户及其关联的所有订单

要在User类中关联订单order的类型,订单有很多,采用list
image

查询语句
image

resultMap进行映射
image

  1. 先映射User本身的属性,再映射关联属性
  2. 一对多的关联属性用collection,使用ofType属性,他表示集合中存放的对象的类型(泛型属性)
  3. JavaType表示的是关联属性的类型,但在user中关联的是list,我们需要的是list内部的对象的属性

接口方法:
image

测试:
image

结果:image

resultMap:多对多关联映射

需求:查询刘备的信息及其购买过的商品信息

sql语句:
image
关联了4个表,主表为User

在之前例子的基础上再往order表中关联订单明细OrderDetail,一个订单有多个明细用list;订单明细表OrderDetail中关联商品Item,一个明细只针对一个商品.
关联环为:User->Order->OrderDetail->Item

查询语句
image

接口方法:
image
叫刘备的可能有很多,用list装起来。

resultMap进行映射

<resultMap type="user" id="findUserAndItemMap">
   	<!-- user表 -->
  	<id column="uid" property="id"/>
  	<result column="uname" property="name"/>
  	<!-- 一对多 <list>order表 -->
  	<collection property="orderList" ofType="Order">
  		<id column="oid" property="id"/>
  		<result column="orderNum" property="orderNum"/>
  		<result column="uid" property="userId"/>
  		<!-- 一对多  <list>orderdetail表 -->
  		<collection property="detailList" ofType="orderDetail">
  			<id column="oid" property="orderId"/>
  			<id column="iid" property="itemId"/>
  			<result column="count" property="count"/>
  			<!-- 一对一  item表 -->
  			<association property="item" javaType="item">
  				<id column="iid" property="id"/>
  				<result column="iname" property="name"/>
  			</association>
  		</collection>
  	</collection>
</resultMap>

每个表看清楚映射的单个(association)还是多个(collection),找好每个表的属性,一步一步来就不容易错

测试:
image

结果:
image

总结

使用resultMap实现关联映射时:

  1. 使用association标签完成多对一或一对一映射。
    a. association标签:将关联查询信息映射到一个po对象中。
    b. association标签中的javaType属性:表示该po对象的类型。
    c. association标签中的select属性:表示应用哪一个关联查询。
    d. association标签中的column属性:表示应用关联查询的条件。
  2. 使用collection标签完成一对多,多对多映射。
    a. collection标签:将关联查询信息映射到一个list集合中。
    b. collection标签的ofType属性:表示该集合中的元素对象的类型。
    c. collection标签中的select属性:表示应用哪一个关联查询。
    d. collection标签中的column属性:表示应用关联查询的条件。

延迟加载

含义

延迟加载:执行查询时,关联查询不会立即加载。只有在使用关联数据时才会加载。

1. 优点:按需加载,提高效率。
2. 具有关联关系的对象才会存在延迟加载
3. 例如:查询订单关联用户。默认当查询订单信息时,会立即加载关联的用户信息。如果程序中只需要使用订单信息,而不需要使用关联的用户信息,则立即加载关联的用户信息就没有必要。可以对订单关联的用户信息实现延迟加载,即使用到用户信息是再加载,不用就不加载,提供系统的执行效率。
4. 在mybatis中,只有association、collection标签具有延迟加载的功能。

配置

注意: 要使用关联查询的延迟加载,就必须要使用单独查询形式。并且,需要先启用MyBatis的延迟加载配置(需要配置两项):

  1. azyLoadingEnabled:延迟加载的全局开关(默认为false)。
  2. aggressiveLazyLoading:延迟加载整个对象(默认为true; false:对象的每个属性都会延迟加载,即属性按需加载)
<!-- 如果aggressiveLazyLoading为true,那么lazyLoadingEnabled即使为true也无效。 -->
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

查询语句
UserMapper.xml中有语句:

<select id="findUserById" parameterType="int" resultType="user">
		select id,name from users where id=#{value}
	</select>

OrderMapper.xml有语句:

<!-- 查询订单关联用户lazy -->
  <resultMap type="Order" id="findOrderAndUserLazyMap">
  	<id column="id" property="id"/>
  	<result column="orderNum" property="orderNum"/>
  	<result column="userId" property="userId"/>
  	<!-- 映射关联属性 -->
  	<!-- select 属性 调用已经存在的statement -->
  	<association property="user" select="com.neuedu.mapper.UserMapper.findUserById" column="userId"></association>
  </resultMap>
  <select id="findOrderAndUserLazy" parameterType="string" resultMap="findOrderAndUserLazyMap">
  	SELECT id,orderNum,userId FROM orders WHERE orderNum = #{orderNum }
  </select>

调用了在另一个xml文件的查询语句,用到的时候才执行。

进行断点测试:
image

  1. 第一条语句执行的时候,user的值为空,第二条语句没有加载执行;

image

  1. 执行到session.commit();发送了数据,user中也有值

image

注释掉一条语句测试:
image

结果:只发送了一条数据,也只执行了一条语句
image

但是在配置文件中将lazyLoadingEnabled的true改为false,即使注释了语句也还是会加载
image

确认了mybatis默认是立即加载的。

注解开发

在上面的开发过程中我们用的都是映射文件开发,就是把查询语句写在xml文件当中,然后在接口类中调用。
MyBatis也支持使用注解来配置映射语句。直接把语句写在接口类中。

记得把文件改一下,已经没有Employee.xml文件了。
image

主要有四种注解来实现增删改查:@Select、@Insert、@Update、@Delete
基础的书写如下

package com.neuedu.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Select;

import com.neuedu.pojo.Employee;

public interface EmpMapper {

	@Select(value="select * from tb_emp where id=#{value}")
	Employee findEmpById(Integer id);
	
	@Select(value="select * from tb_emp where name like '%${value}%'")
	List<Employee> findEmpByName(String name);
	void saveEmp(Employee emp);
	
	@Delete(value="delete from tb_emp where id=#{value}")
	void deleteEmp(Integer id);
	void editEmp(Employee emp);
}

测试类:

@Test
	public void testFindById() throws Exception{
		
		SqlSession session = sf.openSession();
		EmpMapper mapper = session.getMapper(EmpMapper.class);
		mapper.findEmpById(3);
		session.commit();
		session.close();
	}

测试一个:
image

成功。
其他的如用注解实现动态sql、懒加载等等在这个里面写的非常详细了
参考文档:mybatis注解开发

查询缓存

一级缓存

Mybatis的一级缓存是SqlSession级别的缓存,每个SqlSession使用独立的一级缓存空间。

当执行Mapper接口中方法时,获得一个sqlSession的对象。sqlSession对象线程不安全
每次操作都会使用独立的sqlSession对象。
当sqlSession对象被创建时,sqlSession对象的内部会开辟一个缓存区域(HashMap)

image

工作原理

当通过同一个SqlSession对象进行查询操作时。一级缓存会起作用。
Mybatis默认是开启一级缓存的,而且不能关闭一级缓存。
image

当一个sqlSession对象发起了一次查询操作时,首先会到一级缓存中查找是否存在该对象
如果在缓存中没有找到对应的对象,则发生sql语句到数据库中进行查询。并把查询到的对象保存到一级缓存中一份
当通过同一个sqlSession对象发起第二次查询操作时,首先会到一级缓存中查找,如果找到对应的对象,则不会在发送sql语句到数据库。这样,可以提高查询效率。
在两次查询的中间,如果执行了commit操作(update、insert、delete),会清除缓存中的所有数据。

测试

@Test
	public void testFindUserCache() {
		
		SqlSession session = sf.openSession();
		
		UserMapper userMapper = session.getMapper(UserMapper.class);
		
		//第一次查询
		User u1 = userMapper.findUserById(1);//发送sql
		
		u1.setName("tom");
		userMapper.modifyUser(u1);
		session.commit();//清空一级缓存
		
		//第二次查询
		User u2 = userMapper.findUserById(1);//不会发送,使用缓存中的对象
		
		session.close();
		
		SqlSession session2 = sf.openSession();
		
		UserMapper userMapper2 = session2.getMapper(UserMapper.class);
		
		User u3 = userMapper2.findUserById(1);//发送sql,新的sqlSession对象,使用新的一级缓存
		
		session2.close();
		
	}

二级缓存

二级缓存就是,Mapper级别的缓存,当不同的sqlSession对象访问同一个Mapper接口中的方法时,会使用到二级缓存。
image

当使用一个sqlSession对象进行查询操作时,到二级缓存中查找,如果没有找到,则发送sql语句到数据库中查询,并把查询结果保存到二级缓存
在两次查询过程中,如果执行了commit操作,则清空二级缓存。
当使用另外一个sqlSession对象进行查询操作时,到二级缓存中查找,如果找到,则不发送sql语句查询数据库。

默认mybatis是开启二级缓存的,但可以在sqlMapConfig.xml文件中配置二级缓存。
image

要在需要被缓存的xxxMapper.xml文件中配置,该Mapper使用二级缓存。
image

测试
第一次查询时,到二级缓存中查找是否存在对象
image
缓存命中率。 在缓存中查找对象的命中率。
第二次查询时,二级缓存中存在对象,则命中率为0.5
使用二级缓存要注意: 一些异常
image

被二级缓存缓存的对象,有可能会被执行序列化或反序列化操作,所以 被缓存的对象所属的类必须支持序列化, 要实现Sericlizable接口。

分布式缓存插件 ehcache

需求:分布式系统,系统存在两个子系统,在一个系统中登录,在另外一个系统上是否也能够使用登录信息?

image

mybatis与ehcache整合

引入jar包

<dependency>
	    <groupId>net.sf.ehcache</groupId>
	    <artifactId>ehcache</artifactId>
	    <version>2.10.3</version>
	</dependency>
  	<dependency>
	    <groupId>org.mybatis.caches</groupId>
	    <artifactId>mybatis-ehcache</artifactId>
	    <version>1.1.0</version>
	</dependency>

假日ehcache 实现类
image

在系统中加入ehcache的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd"
         updateCheck="true" monitoring="autodetect"
         dynamicConfig="true">
    <!-- 磁盘的缓存路径 -->
    <diskStore path="d:\\ehcache\temp"/>
	<defaultCache
            maxElementsInMemory="100" 
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="1200"
            maxElementsOnDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
    <!--  Cache配置
·           name:Cache的唯一标识
·           maxElementsInMemory:内存中最大缓存对象数。
·           maxElementsOnDisk:磁盘中最大缓存对象数,若是0表示无穷大。
·           eternal:Element是否永久有效,一但设置了,timeout将不起作用。
·           overflowToDisk:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中。
·           timeToIdleSeconds:设置Element在失效前的允许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
·           timeToLiveSeconds:设置Element在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当element不是永久有效时使用,默认是0.,也就是element存活时间无穷大。
·           diskPersistent:是否缓存虚拟机重启期数据。(这个虚拟机是指什么虚拟机一直没看明白是什么,有高人还希望能指点一二)。
·           diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
·           diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
·           memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。	 -->
</ehcache>

应用场景:经常被访问,但很少被修改。适合进行缓存。
电商系统中的商品信息不适合mybatis缓存。

posted @ 2021-08-12 08:51  九尾。  阅读(276)  评论(0编辑  收藏  举报