二级缓存
- 二级缓存是全局作用域的缓存
- 二级默认不开启,需要手动配置
- MyBatis提供二级缓存接口以及实现,缓存实现要求Entity实现Serializable接口
- 二级缓存在SqlSession(一级缓存)关闭或提交后,一级缓存的数据会放到二级缓存中才会生效,
二级缓存使用步骤
- 全局配置文件中开启二级缓存
<setting name="cacheEnabled" value="true"/> 复制代码
- 需要使用二级缓存的映射文件使用cache标签配置缓存,放在mapper标签中
<cache></cache> 复制代码
- Entity实体类接口需要实现序列化Serializable接口
@Data public class Teacher implements Serializable { private Integer id; private String teacherName; private String className; private String address; private Date birthDate; } 复制代码
新增测试方法
@Test public void testSecondLevelCache(){ SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); TeacherMapper teacherMapper1 = sqlSession1.getMapper(TeacherMapper.class); TeacherMapper teacherMapper2 = sqlSession2.getMapper(TeacherMapper.class); teacherMapper1.getTeacherById(1); // 注意:只有关闭了一级缓存,二级缓存才会生效 sqlSession1.close(); System.out.println("-------------"); teacherMapper2.getTeacherById(1); sqlSession2.close(); } 复制代码
执行测试
控制台只输出了一条SQL,且缓存命中率为0.5,也就是说第一次查询没有命中缓存,第二次查询命中了缓存,说明二级缓存生效了
二级缓存的属性
在Mapper XML中配置二级缓存的cache标签有以下这些属性
- eviction:表示缓存回收策略,默认策略为LRU
- LRU:最近最少使用,移除长时间不被使用的额对象
- FIFO:先进先出,按照对象进入缓存的顺序来移除
- SOFT:软引用,易出基于垃圾回收状态和软引用规则的对象
- WEAK:弱引用,更积极的移除基于垃圾回收状态和弱引用规则的对象
- flushInterval:刷新间隔,单位为浩渺,默认不设置
- size:引用数目,代表最多可以缓存多少个对象,太多会导致内存溢出
- readOnly:只读,可以设置tru或false
- true:只读缓存,会给所有嗲用着返回缓存对象的相同实例,因为这些对象不能被修改,有很高的性能
- false:读写缓存,会返回缓存对象的拷贝(通过系列化),会更安全但也因此损失了性能
缓存查询顺序及原理
缓存的查询顺序
在TeacherMapperTest测试类中增加一个方法testCacheQueryOrder,测试缓存的查询顺序
@Test public void testCacheQueryOrder(){ SqlSession sqlSession = sqlSessionFactory.openSession(); TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); Teacher teacher = mapper.getTeacherById(2); System.out.println(teacher); sqlSession.close(); } 复制代码
第一次执行测试
缓存命中率为0,并且输出了执行的SQL,说明肯定去查了二级缓存和一级缓存,二级缓存中没有数据,一级缓存中也没有数据,所以查询了数据库
在末尾增加代码,再次查询
SqlSession sqlSession1 = sqlSessionFactory.openSession(); TeacherMapper mapper1 = sqlSession1.getMapper(TeacherMapper.class); Teacher teacher1 = mapper1.getTeacherById(2); System.out.println(teacher1); sqlSession1.close(); 复制代码
第二次执行测试
执行了两次查询,第一次查询一级缓存和二级缓存中都没有数据所以去查询数据库,第二次从二级缓存中查到数据,所以二级缓存命中率为0.5
在sqlSession1.close()上面增加代码两行代码,再次查询teaher并输出。
Teacher teacher2 = mapper1.getTeacherById(2); System.out.println(teacher2); 复制代码
第三次次执行测试
两次都是从二级缓存中获获取
在sqlSession1.close()上面增加代码,查询其他ID的数据
// 查询一个二级缓存中不存在的数据 // 一级缓存中如果还没有,就去查询数据库,之后放在一级缓存中 Teacher teacher3 = mapper1.getTeacherById(3); System.out.println(teacher3); // 这次查询会从一级缓存中查询 Teacher teacher4 = mapper1.getTeacherById(3); System.out.println(teacher4) 复制代码
第四次执行测试
缓存命中率降低,说明数据不是从二级缓存而是从一级缓存中拿的,二级缓存不存在这个数据,也说明是优先看二级缓存,二级缓存中没有才去看的一级缓存,因为如果从一级缓存中直接找到数据,就没有必要再到二级缓存中去找了,缓存命中率降低就说明了先找二级缓存再找一级缓存
总结:
- 不会出现一级缓存和二级缓存中有同一个数据的情况
- 二级缓存中的数据在一级缓存也就是SqlSession缓存关闭了才有
- 二级缓存中没有数据就会看一级缓存,一级缓存中也没有就会看数据库,数据库查完之后就会存在SqlSession中
- 任何时候都是先看二级缓存在看一级缓存,最后都没有再去查询数据库
缓存的原理
缓存相关的设置
- MyBatis全局配置文件mybatis-config.xml中的cacheEnable标签配置了二级缓存的开关,一级缓存一直是开启的
- Mapper XML文件中select标签具有useCache标签,可以设置是否使用二级缓存,一级缓存一直使用
- sql标签具有flushCache属性,增删改语句默认flushCache为true,sql执行后会清空一级缓存以及二级缓存,查询语句默认flushCache为false
- sqlSession对象具有clearCache方法,只能用来清除一级缓存
- 在某一个作用域进行了增删改操作后,默认所有的select的缓存会被清空,参考一级缓存失效的几种情况
测试是否使用二级缓存,设置select语句属性useCache="false"
@Test public void testUseCacheFalse(){ SqlSession sqlSession = sqlSessionFactory.openSession(); TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); Teacher teacher = mapper.getTeacherById(2); System.out.println(teacher); sqlSession.close(); System.out.println("第二次查询,不同的SqlSession,不同的Mapper"); SqlSession sqlSession1 = sqlSessionFactory.openSession(); TeacherMapper mapper1 = sqlSession1.getMapper(TeacherMapper.class); Teacher teacher1 = mapper1.getTeacherById(2); System.out.println(teacher1); System.out.println("第三次查询,与第一次同一个SqlSession, 不同的Mapper"); Teacher teacher2 = mapper1.getTeacherById(2); System.out.println(teacher2); sqlSession1.close(); } 复制代码
执行测试
没有用到二级缓存,但是用到了一级缓存,设置useCache=false对一级缓存没有任何影响
设置useCache="true",再次执行测试
缓存命中率为0.6666,第三次查询是从二级缓存中获取的,先查了二级缓存,二级缓存中有数据,就不用再去查一级缓存
整合第三方缓存
整合EHcache
MyBatis中定义了Cache接口,整合第三方或者自定义缓存可以实现该接口
该接口提供了一些必须实现的方法,如putObject方法就是将数据放入缓存中,getObject方法就是将数据从缓存中取出等其他方法。
Ehcache是一个纯Java的进程内缓存框架,具有快速、简单的特点,是Hibernate中默认的CacheProvider。
mybatis-ehcache中帮我们写好了mybatis中Cache接口的实现
第一步首先在pom.xml文件中添加ehcache依赖和mybatis-ehcache依赖
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.2</version> </dependency> <!-- https://mvnrepository.com/artifact/org.ehcache/ehcache --> <dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> <version>3.9.9</version> </dependency> 复制代码
第二步是在resources目录下增加ehcache缓存配置ehcache.xml
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <!-- 磁盘保存路径 --> <diskStore path="/Users/jingnan/Practice/mybatis-cache/ehcache" /> <defaultCache maxElementsInMemory="1" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache> 复制代码
属性说明:
- diskStore:指定数据在磁盘中的存储位置。
- defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用指定的的管理策略
以下属性是必须的:
- maxElementsInMemory - 在内存中缓存的element的最大数目
- maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
- eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
- overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
以下属性是可选的:
- timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
- timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
- diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
- diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
- diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
- memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
第三步是在TeaacheMapper中设置二级缓存使用ehcache,并且实体类可以不再实现序列化接口
<!--设置第三方的自定义缓存--> <cache type="org.mybatis.caches.ehcache.EhcacheCache" ></cache> 复制代码
新增测试方法testEhcache(),将上一个测试方法的代码拷贝过来。使用第三方缓存不再需要实体类实现序列化接口
@Test public void testEhcache(){ SqlSession sqlSession = sqlSessionFactory.openSession(); TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); Teacher teacher = mapper.getTeacherById(2); System.out.println(teacher); sqlSession.close(); // System.out.println("第二次查询,不同的SqlSession,不同的Mapper"); SqlSession sqlSession1 = sqlSessionFactory.openSession(); TeacherMapper mapper1 = sqlSession1.getMapper(TeacherMapper.class); Teacher teacher1 = mapper1.getTeacherById(2); System.out.println(teacher1); sqlSession1.close(); } 复制代码
执行测试
设置EHcache配置文件中保存到内存中的数据为1,即只要数据量超过1就会保存磁盘上,设置0的意思保存到内存中的数据是无限大
再次执行测试
查看存放缓存的磁盘目录
其他Mapper XML中配置二级缓存可以通过引用已配置缓存的namesapce
<cache-ref namespace="com.citi.mapper.TeacherMapper"/> 复制代码
也可以直接cache标签type属性定义
<cache type="org.mybatis.caches.ehcache.EhcacheCache" ></cache>