原文始发于:07. Mybatis 缓存-二级缓存
Mybatis 除了一级缓存外, 还支持二级缓存, 二级缓存是namespace级别的.
1. 二级缓存
1.1 二级缓存特点
- 二级缓存是namespace 级别的, 凌驾于sqlSession 之上, 可实现sqlSession 之间的共享
- 二级缓存全局配置模式是开启的, 可进行关闭
- 二级缓存是事务性的, 当sqlSession正常关闭或提交事务时, 会将sqlSession中的一级缓存刷新到二级缓存中
- 当事务发生回滚时, sqlSession中的一级缓存不会刷新到二级缓存中.
1.2 开启二级缓存
- 开启缓存的最简单的方式, 就是直接在sql映射文件中,添加标签即可.
- 需要注意的是, 需要缓存的实体需要实现序列化接口
<cache/>
1.3 cache 标签默认行为
使用其默认配置. 默认的缓存特点:
- 映射语句文件中的所有 select 语句的结果将会被缓存。可通过设置useCache=false 来关闭select语句的缓存
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
<cache/>
1.4 cache 标签属性
cache 标签提供了四个属性来自定义默认二级缓存的行为:
- flushInterval: 设置刷新间隔, 即请空缓存的时间, 单位毫秒. 默认不情况, 当在调用语句时刷新
- size: 设置每个namespace最多缓存对象的引用个数, 默认为1024.
- readOnly: 设置得到的缓存是否只读.
- true: 设置只读, 会返回给缓存的引用, 速度会很快. 但不安全,因为可以修改
- false: 设置读写, 会通过反序列化技术生成新的对象, 效率会稍微慢一点儿
- eviction: 设置缓存清除策略, 默认为LRU.
- LRU: 最近最少使用:移除最长时间不被使用的对象。
- FIFO: 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT: 软引用:基于垃圾回收器状态和软引用规则移除对象。
- WEAK: 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
2. 二级缓存测试
2.1 设置全局开启二级缓存
全局二级缓存是默认开启的, 或者显示声明开启全局二级缓存.
<settings> <setting name="cacheEnabled" value="true" /> </settings>
2.2 需要缓存的实体实现序列化接口
由于缓存需要序列化实体, 所以实体必须实现序列化接口.
public class EmployeePO implements Serializable { private static final long serialVersionUID = -2164461308545729638L; //... }
2.3 测试用例
- 测试二级缓存生效: sqlSession 关闭时, 将本sqlSession中的一级缓存写入二级缓存中
- 测试二级缓存生效: sqlSession 提交事务时, 将本sqlSession中的一级缓存写入二级缓存中
- 测试二级缓存不生效: 如果查询中设置useCache=false, 二级缓存不生效
- 测试二级缓存不生效: sqlSession 回滚事务时, 不会将sqlSession中的缓存写入二级缓存中
public class TestCacheLevel2 { // 测试二级缓存生效: sqlSession 关闭时, 将本sqlSession中的一级缓存写入二级缓存中 @Test public void test_valid1(){ SqlSession sqlSession1 = SqlSessionUtil.openSession(false); SqlSession sqlSession2 = SqlSessionUtil.openSession(false); EmployeeMapper empMapper1 = sqlSession1.getMapper(EmployeeMapper.class); EmployeeMapper empMapper2 = sqlSession2.getMapper(EmployeeMapper.class); System.out.println("进行第一次查询..."); EmployeePO emp1 = empMapper1.findById(1L); // sqlSession 关闭时, 将一级缓存写入二级缓存. sqlSession1.close(); System.out.println("进行第二次查询..."); EmployeePO emp2 = empMapper2.findById(1L); Assert.assertNotEquals(emp1, emp2); } // 测试二级缓存生效: sqlSession 提交事务时, 将本sqlSession中的一级缓存写入二级缓存中 @Test public void test_valid2(){ SqlSession sqlSession1 = SqlSessionUtil.openSession(false); SqlSession sqlSession2 = SqlSessionUtil.openSession(false); EmployeeMapper empMapper1 = sqlSession1.getMapper(EmployeeMapper.class); EmployeeMapper empMapper2 = sqlSession2.getMapper(EmployeeMapper.class); System.out.println("进行第一次查询..."); EmployeePO emp1 = empMapper1.findById(1L); // sqlSession 提交事务时, 将本sqlSession中的数据写入缓存中 sqlSession1.commit(); System.out.println("进行第二次查询..."); EmployeePO emp2 = empMapper2.findById(1L); Assert.assertNotEquals(emp1, emp2); } // 测试二级缓存不生效: 如果查询中设置useCache=false, 二级缓存不生效 @Test public void test_invalid1(){ SqlSession sqlSession1 = SqlSessionUtil.openSession(false); SqlSession sqlSession2 = SqlSessionUtil.openSession(false); EmployeeMapper empMapper1 = sqlSession1.getMapper(EmployeeMapper.class); EmployeeMapper empMapper2 = sqlSession2.getMapper(EmployeeMapper.class); System.out.println("进行第一次查询..."); EmployeePO emp1 = empMapper1.findById(1L); // sqlSession 关闭时, 将一级缓存写入二级缓存. sqlSession1.close(); System.out.println("进行第二次查询..."); EmployeePO emp2 = empMapper2.findById(1L); Assert.assertNotEquals(emp1, emp2); } // 测试二级缓存不生效: sqlSession 回滚事务时, 不会将sqlSession中的缓存写入二级缓存中 @Test public void test_invalid2(){ SqlSession sqlSession1 = SqlSessionUtil.openSession(true); SqlSession sqlSession2 = SqlSessionUtil.openSession(true); EmployeeMapper empMapper1 = sqlSession1.getMapper(EmployeeMapper.class); EmployeeMapper empMapper2 = sqlSession2.getMapper(EmployeeMapper.class); System.out.println("进行第一次查询..."); EmployeePO emp1 = empMapper1.findById(1L); // sqlSession 提交事务时, 将本sqlSession中的数据写入缓存中 sqlSession1.rollback(); System.out.println("进行第二次查询..."); EmployeePO emp2 = empMapper2.findById(1L); Assert.assertNotEquals(emp1, emp2); } }