MyBatis--11.查询缓存

383 阅读8分钟

1.引言

​ 合理地使用缓存可以有效的提高程序的执行效率,当程序需要频繁查询相同的数据时,就应该设置缓存,因为频繁的访问数据库会导致访问速度的降低,所以我们应该避免一些重复的访问操作;在MyBatis中存在着两级缓存,一级缓存默认开启,二级缓存则是需要手动开启;

1573109765279

2.缓存的作用域和生命周期

2.1.缓存的作用域

​ 在MyBatis中,无论是一级缓存还是二级缓存,都是以namespace为作用域的,也就是说,一个作用域对应着一个缓存,彼此之间互不干扰;

2.2.缓存的生命周期

​ 在MyBatis中,一级缓存的生命周期和SqlSession同步,当SqlSession关闭时,一级缓存也就失效了,MyBatis默认开启一级缓存;二级缓存的生命周期则是和SqlSessionFactory同步,默认是关闭的;

3.一级缓存

​ 在MyBatis中,默认开启一级缓存,并且不能关闭,我们可以通过代码来证明一级缓存的存在依据

​ 当程序执行了增删改的操作后,缓存中的数据就会被清空,这是为了保证数据的一致性,当我们对数据库执行了增删改的操作后,其数据已经发生了变化,而此时执行查询操作后,因为缓存控件的数据应该被清空了,所以程序会从数据库将数据查找出来并添加到缓存空间中,这样就能保证我们从缓存中获取的数据是最新的数据了;

3.1.证明一级缓存的存在

	//证明Mybatis一级缓存存在的依据
	@Test
	public void test01() {
		Student student1 = dao.selectStudentById(1);
		System.out.println(student1);
		
		
		Student student2 = dao.selectStudentById(1);
		System.out.println(student2);
	}

	<!--console的输出-->
[main] DEBUG - ==>  Preparing: select id,name,age,score from student where id=? 
[main] DEBUG - ==> Parameters: 1(Integer)
[main] DEBUG - <==      Total: 1
Student [id=1, name=张三, age=23, score=93.5]
Student [id=1, name=张三, age=23, score=93.5]

​ 可以从控制台的输出中看出,我们执行两次相同的查询操作,第一次查询触发了SQL的运行,而第二次则是直接输出,并没有执行SQL语句,证明了MyBatis的一级缓存是默认开启的;

3.2.从一级缓存中读取数据的依据是:按照SQL的id和SQL语句来查询

​ 我们已经证明了一级缓存的存在,但是程序是如何从缓存中获取数据呢,或者说数据在缓存中如何存放的,其实数据在缓存中是存放在一个Map中的,我们每次访问缓存的时候都会通过其key去获取对应的value,在Hibernate中,它的缓存是以对象的id作为其Key,而在MyBatis中是以SQL的id 和SQL语句作为Map的key的;

	//从一级缓存中读取数据的依据是:SQL的id 和 SQL语句
	//而Hibernate从缓存中读取数据的依据是:对象的id
	@Test
	public void test02() {
		Student student1 = dao.selectStudentById(1);
		System.out.println(student1);
		//我们定义了一个和selectStudentById()完全一样的方法selectStudentById2()
		//当访问相同的数据,结果console显示执行了两次查询
        //也就是说mybatis从一级缓存读取数据是以SQL的id(statement)作为其依据的
        //因为一个查询可以用来访问多个数据,所以mybatis是以SQL的id和SQL语句作为其查询依据的
		Student student2 = dao.selectStudentById2(1);
		System.out.println(student2);
	}
	
	<!--console输出信息-->
[main] DEBUG - ==>  Preparing: select id,name,age,score from student where id=? 
[main] DEBUG - ==> Parameters: 1(Integer)
[main] DEBUG - <==      Total: 1
Student [id=1, name=张三, age=23, score=93.5]
[main] DEBUG - ==>  Preparing: select id,name,age,score from student where id=? 
[main] DEBUG - ==> Parameters: 1(Integer)
[main] DEBUG - <==      Total: 1
Student [id=1, name=张三, age=23, score=93.5]

3.3.证明一级缓存的生命周期是以SQLSession同步的

	@Test
	public void test01() {
		sql = SqlSessionUtil.getSqlSession();
		dao = sql.getMapper(StudentDao.class);
		Student student1 = dao.selectStudentById(1);
		System.out.println(student1);
		//当执行完一次查询操作后就将sqlSession关闭,然后再重新开启sqlSession
        //可以从控制台的输出中看到第二次查询也执行了SQL语句
        //也就是说,当SQLSession关闭的时候,缓存也就随之清空了
        //这也验证了一级缓存和sqlSession的生命周期同步
		sql.close();
		
		sql = SqlSessionUtil.getSqlSession();
		dao = sql.getMapper(StudentDao.class);
		Student student2 = dao.selectStudentById(1);
		System.out.println(student2);
	}

	<!--console输出信息-->
[main] DEBUG - ==>  Preparing: select id,name,age,score from student where id=? 
[main] DEBUG - ==> Parameters: 1(Integer)
[main] DEBUG - <==      Total: 1
Student [id=1, name=张三, age=23, score=93.5]
[main] DEBUG - ==>  Preparing: select id,name,age,score from student where id=? 
[main] DEBUG - ==> Parameters: 1(Integer)
[main] DEBUG - <==      Total: 1
Student [id=1, name=张三, age=23, score=93.5]

3.4.证明执行增删改操作会触发对缓存清空

	@Test
	public void test01() {
		Student student1 = dao.selectStudentById(1);
		System.out.println(student1);
		//执行了插入操作后我们可以看到控制台的输出中,第二次查询也触发了SQL的执行
        //也就是说当程序执行增删改操作后会将缓存中的数据清空
		dao.insertStudent(new Student("赵六",26,96.5));
		
		Student student2 = dao.selectStudentById(1);
		System.out.println(student2);
	}

	<!--console输出信息-->
[main] DEBUG - ==>  Preparing: select id,name,age,score from student where id=? 
[main] DEBUG - ==> Parameters: 1(Integer)
[main] DEBUG - <==      Total: 1
Student [id=1, name=张三, age=23, score=93.5]
[main] DEBUG - ==>  Preparing: insert into student(name,age,score) values(?,?,?) 
[main] DEBUG - ==> Parameters: 赵六(String), 26(Integer), 96.5(Double)
[main] DEBUG - <==    Updates: 1
[main] DEBUG - ==>  Preparing: select id,name,age,score from student where id=? 
[main] DEBUG - ==> Parameters: 1(Integer)
[main] DEBUG - <==      Total: 1
Student [id=1, name=张三, age=23, score=93.5]

4.二级缓存

	<!--二级缓存的使用原则-->
1)多个namespace不要操作同一张表 2)不要在关联关系表上执行增删改操作  3)查询多于修改时使用二级缓存

​ 在实际的项目中我们很少使用内置的二级缓存,这是因为实际项目中的表基本上都有着关联关系关系,很少操作会只涉及到一张表(namespace),所以我们很多时候都不会使用内置的二级缓存;而是会选择使用第三方的二级缓存,例如Ehcache二级缓存;

4.1.开启二级缓存

​ 1)实体类实现序列化

​ 2)在映射文件中开启<cache/>标签

<!--1)Student.class-->
public class Student implements Serializable{...}

<!--2)mapper.xml-->
<cache/>
	@Test
	public void test01() {
		sql = SqlSessionUtil.getSqlSession();
		dao = sql.getMapper(StudentDao.class);
		Student stu1 = dao.selectStudentById(1);
		System.out.println(stu1);
        //想要测试二级缓存就必须去除掉一级缓存的影响,因为一级缓存的生命周期和sqlSession同步
		//所以在第一次查询后将sqlSession关闭,这样一级缓存就失效了
        //在第二次查询之前重新获取sqlSession,这样就能看到二级缓存的效果了
		sql.close();
		
		sql = SqlSessionUtil.getSqlSession();
		dao = sql.getMapper(StudentDao.class);
		Student stu2 = dao.selectStudentById(1);
		System.out.println(stu2);
	}

	<!--console输出信息-->
	//Cache Hit Ratio 的值表示的是查询命中率
	//第一次查询因为缓存中没有该值,所以命中率为0.0
	//第二次查询的时候就查找到该值,所以命中率就为0.5   命中率 = 命中次数 / 查询次数
        
[main] DEBUG - Cache Hit Ratio [com.mybatis.dao.StudentDao]: 0.0
[main] DEBUG - ==>  Preparing: select id,name,age,score from student where id=? 
[main] DEBUG - ==> Parameters: 1(Integer)
[main] DEBUG - <==      Total: 1
Student [id=1, name=张三, age=23, score=93.5]
[main] DEBUG - Cache Hit Ratio [com.mybatis.dao.StudentDao]: 0.5
Student [id=1, name=张三, age=23, score=93.5]

4.2.证明增删改操作对二级缓存的影响

	@Test
	public void test01() {
		sql = SqlSessionUtil.getSqlSession();
		dao = sql.getMapper(StudentDao.class);
		Student stu1 = dao.selectStudentById(1);
		System.out.println(stu1);
		
		
		sql.close();
		
		sql = SqlSessionUtil.getSqlSession();
		dao = sql.getMapper(StudentDao.class);
		
        //当执行了第一次查询之后就执行插入操作,此时会清空一级缓存的内容
        //然后关闭sqlSession,执行第二次查询
        //根据console我们可以看到第二次查询也执行了SQL语句,说明增删改对二级缓存同样起作用
        //资料得:执行增删改的操作后二级缓存并没有将其key删除,而是将key所对应的value置为null
        //所以我们可以推导出二级缓存起作用的条件:存在该key并且该key所对应的value不为null
		dao.insertStudent(new Student("",0,0.0));
		
		Student stu2 = dao.selectStudentById(1);
		System.out.println(stu2);
	}

	<!--console输出信息-->
        
[main] DEBUG - Cache Hit Ratio [com.mybatis.dao.StudentDao]: 0.0
[main] DEBUG - ==>  Preparing: select id,name,age,score from student where id=? 
[main] DEBUG - ==> Parameters: 1(Integer)
[main] DEBUG - <==      Total: 1
Student [id=1, name=张三, age=23, score=93.5]
[main] DEBUG - ==>  Preparing: insert into student(name,age,score) values(?,?,?) 
[main] DEBUG - ==> Parameters: (String), 0(Integer), 0.0(Double)
[main] DEBUG - <==    Updates: 1
[main] DEBUG - ==>  Preparing: select id,name,age,score from student where id=? 
[main] DEBUG - ==> Parameters: 1(Integer)
[main] DEBUG - <==      Total: 1
Student [id=1, name=张三, age=23, score=93.5]

​ 我们也可以通过让 增删改操作 对二级缓存不起作用,只要修改mapper文件中的增删改操作信息即可(不推荐);

	<!--mapper.xml 修改了flushCache属性的值为false-->
	<insert id="insertStudent" parameterType="Student" flushCache="false">
		insert into student(name,age,score) values(#{name},#{age},#{score})
	</insert>
	
	<!--console输出信息-->
[main] DEBUG - Cache Hit Ratio [com.mybatis.dao.StudentDao]: 0.0
[main] DEBUG - ==>  Preparing: select id,name,age,score from student where id=? 
[main] DEBUG - ==> Parameters: 1(Integer)
[main] DEBUG - <==      Total: 1
Student [id=1, name=张三, age=23, score=93.5]
[main] DEBUG - ==>  Preparing: insert into student(name,age,score) values(?,?,?) 
[main] DEBUG - ==> Parameters: (String), 0(Integer), 0.0(Double)
[main] DEBUG - <==    Updates: 1
[main] DEBUG - Cache Hit Ratio [com.mybatis.dao.StudentDao]: 0.5
Student [id=1, name=张三, age=23, score=93.5]

4.3.关闭二级缓存

​ 关闭二级缓存有两种方式,分为全局关闭和局部关闭

	1 )    全局关闭  :  全局关闭需要在`mybatis.xml`中关闭,通过设置`cacheEnabled`的值,对整个应用起作用
 	<!--全局关闭二级缓存-->
 	<settings>
		<setting name="cacheEnabled" value="false"/>
	</settings>

​ 2)局部关闭,在mapper.xml中关闭,设置flushCache的值,对当前操作起作用

	<!--局部关闭二级缓存-->
	<select id="selectStudentById" resultType="Student" useCache="false">
		select id,name,age,score from student where id=#{id}
	</select>

5.EhCache二级缓存:如何开启EhCache二级缓存

​ 1) 导入jar包:ehcache-core-2.6.11.jarmybatis-ehcache-1.1.0.jar

​ 2) 在mapper中配置ehcache

<cache type="org.mybatis.caches.ehcache.EhcacheCache" />

​ 3) 在classpath中添加ehcache的配置文件:ehcache.xml,该文件在ehcache-core-2.6.11.jar可以解压得到ehcache-failsafe.xml,将其重命名为ehcache.xml

之前使用内置的二级缓存时,实体类是需要实现序列化,而ehCache二级缓存则不需要实体类实现序列化;