在日常工作中,我们大多使用MyBatis的默认缓存配置,但是MyBatis缓存机制也有一些不足之处,一不留神就会出现脏数据,形成一些潜在的隐患,后续排查问题容易浪费时间精力。
所以,如何用好MyBatis的缓存,是重中之重的。这里主要介绍MyBatis 的一级缓存和二级缓存。
在默认的情况下, 只开启一级缓存(一级缓存是对同一个 SqlSession 而言的)。
1 一级缓存
同一个 SqlSession 对象, 在参数和 SQL 完全一样的情况先, 只执行一次 SQL 语句(如果缓存没有过期) 也就是只有在参数和
SQL 完全一样的情况下, 才会有这种情况。
原理图如下:
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构用于存储缓存数据。不同的sqlSession之间的缓存数据区域是互相不影响的。也就是他只能作用在同一个sqlSession中,不同的sqlSession中的缓存是互相不能读取的。
1.1 同一个 SqlSession
在以上的代码中, 进行了两次查询, 使用相同的 SqlSession, 结果如下
在日志和输出中:
第一次查询发送了 SQL 语句, 后返回了结果;
第二次查询没有发送 SQL 语句, 直接从内存中获取了结果。
而且两次结果输入一致, 同时断言两个对象相同也通过。
1.2 不同的 SqlSession
在代码中, 分别使用 sqlSession 和 sqlSession2 进行了相同的查询。
其结果如下
从日志中可以看到两次查询都分别从数据库中取出了数据。 虽然结果相同, 但两个是不同的对象。
1.3 刷新缓存
刷新缓存是清空这个 SqlSession 的所有缓存, 不单单是某个键。
如果是以上, 没什么不同, 结果还是第二个不发 SQL 语句。
在此, 做一些修改, 在 StudentMapper.xml 中, 添加
修改后的配置文件如下:
结果如下:
第一次, 第二次都发送了 SQL 语句, 同时, 断言两个对象相同出错。
1.4 总结
在同一个 SqlSession 中, MyBatis 会把执行的方法和参数通过算法生成缓存的键值, 将键值和结果存放在一个 Map 中, 如果后续的键值一样, 则直接从 Map 中获取数据;
不同的 SqlSession 之间的缓存是相互隔离的;
用一个 SqlSession, 可以通过配置使得在查询前清空缓存;
任何的 UPDATE, INSERT, DELETE 语句都会清空缓存。
2 二级缓存
二级缓存存在于 SqlSessionFactory 生命周期中。二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。UserMapper有一个二级缓存区域(按namespace分),其它mapper也有自己的二级缓存区域(按namespace分)。每一个namespace的mapper都有一个二级缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。
2.1 配置二级缓存
2.1.1 全局开关
在 MyBatis 中, 二级缓存有全局开关和分开关, 全局开关, 在 MyBatis-config.xml 中如下配置:
默认是为 true, 即默认开启总开关。
2.1.2 分开关
分开关就是说在 *Mapper.xml 中开启或关闭二级缓存, 默认是不开启的。
2.1.3 entity 实现序列化接口
2.2 使用二级缓存
结果如下:
以上结果, 分几个过程解释:
第一阶段:
在第一个 SqlSession 中, 查询出 student 对象, 此时发送了 SQL 语句;
student更改了name 属性;
SqlSession 再次查询出 student1 对象, 此时不发送 SQL 语句, 日志中打印了 「Cache Hit Ratio」, 代表二级缓存使用了, 但是没有命中。 因为一级缓存先作用了。
由于是一级缓存, 因此, 此时两个对象是相同的。
调用了 sqlSession.close(), 此时将数据序列化并保持到二级缓存中。
第二阶段:
新创建一个 sqlSession.close() 对象;
查询出 student2 对象,直接从二级缓存中拿了数据, 因此没有发送 SQL 语句, 此时查了 3 个对象,但只有一个命中, 因此 命中率 1/3=0.333333;
查询出 student3 对象,直接从二级缓存中拿了数据, 因此没有发送 SQL 语句, 此时查了 4 个对象,但只有一个命中, 因此 命中率 2/4=0.5;
由于 readOnly=“false”, 因此 student2 和 student3 都是反序列化得到的, 为不同的实例。