MyBatis 二级缓存实现详解及使用注意事项
二级缓存介绍
在上文中提到的一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示。

二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。
当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
二级缓存配置
要正确的使用二级缓存,需完成如下配置的。
首先,在MyBatis的配置文件中开启二级缓存。
<setting name="cacheEnabled" value="true"/>
然后,在MyBatis的 Mapper XML 中加入 cache 或者 cache-ref 标签。
cache标签用于声明这个namespace需要使用二级缓存,并且可以自定义配置。
<cache/>
type:cache使用的类型,默认是PerpetualCache,这在一级缓存中提到过。eviction: 定义回收的策略,常见的有FIFO,LRU。flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。size: 最多缓存对象的个数。readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache。
<cache-ref namespace="mapper.StudentMapper"/>
二级缓存实验
public void testCacheWithoutCommitOrClose() throws Exception {
SqlSession sqlSession1 = factory.openSession(false); // 不自动提交事务
SqlSession sqlSession2 = factory.openSession(false); // 不自动提交事务
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
System.out.println(studentMapper.getStudentById(1)); // 查询数据库
System.out.println(studentMapper2.getStudentById(1)); // 查询数据库,二级缓存不起作用
}
public void testCacheWithCommitOrClose() throws Exception {
SqlSession sqlSession1 = factory.openSession(false); // 不自动提交事务
SqlSession sqlSession2 = factory.openSession(false); // 不自动提交事务
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
System.out.println(studentMapper.getStudentById(1)); // 查询数据库
sqlSession1.commit(); // 提交事务
System.out.println(studentMapper2.getStudentById(1)); // 查询缓存,二级缓存起作用
}
public void testCacheWithUpdate() throws Exception {
SqlSession sqlSession1 = factory.openSession(false); // 不自动提交事务
SqlSession sqlSession2 = factory.openSession(false); // 不自动提交事务
SqlSession sqlSession3 = factory.openSession(false); // 不自动提交事务
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
StudentMapper studentMapper3 = sqlSession3.getMapper(StudentMapper.class);
System.out.println(studentMapper.getStudentById(1)); // 查询数据库
sqlSession1.commit(); // 提交事务
System.out.println(studentMapper2.getStudentById(1)); // 查询缓存,二级缓存起作用
studentMapper3.updateStudentName("方方",1); // 更新数据
sqlSession3.commit(); // 提交事务
System.out.println(studentMapper2.getStudentById(1)); // 查询数据库,说明清除了二级缓存
}
public void testCacheWithDiffererntNamespace() throws Exception {
SqlSession sqlSession1 = factory.openSession(false); // 不自动提交事务
SqlSession sqlSession2 = factory.openSession(false); // 不自动提交事务
SqlSession sqlSession3 = factory.openSession(false); // 不自动提交事务
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
ClassMapper classMapper = sqlSession3.getMapper(ClassMapper.class);
System.out.println(studentMapper.getStudentByIdWithClassInfo(1)); // 关联查询数据库
sqlSession1.close();
System.out.println(studentMapper2.getStudentByIdWithClassInfo(1)); // 查询缓存,二级缓存起作用
classMapper.updateClassName("特色一班",1); // 更新其中一个表的数据
sqlSession3.commit(); // 提交更新事务
System.out.println(studentMapper2.getStudentByIdWithClassInfo(1)); // 仍然查询缓存,引起了脏读
}
// 让ClassMapper引用StudenMapper的命名空间,这样两个映射文件对应的SQL操作都会使用同一块缓存
public void testCacheWithDiffererntNamespace() throws Exception {
SqlSession sqlSession1 = factory.openSession(false); // 不自动提交事务
SqlSession sqlSession2 = factory.openSession(false); // 不自动提交事务
SqlSession sqlSession3 = factory.openSession(false); // 不自动提交事务
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
ClassMapper classMapper = sqlSession3.getMapper(ClassMapper.class);
System.out.println(studentMapper.getStudentByIdWithClassInfo(1)); // 关联查询数据库
sqlSession1.close();
System.out.println(studentMapper2.getStudentByIdWithClassInfo(1)); // 查询缓存,二级缓存起作用
classMapper.updateClassName("特色一班",1); // 更新其中一个表的数据
sqlSession3.commit(); // 提交更新事务
System.out.println(studentMapper2.getStudentByIdWithClassInfo(1)); // 关联查询数据库,二级缓存失效
}
总结
- MyBatis的二级缓存相对于一级缓存来说,实现了
SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。 - MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
- 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。
参考:https://mybatis.org/mybatis-3/zh/configuration.html#settings
http://mybatis.org/spring/zh/factorybean.html
https://tech.meituan.com/2018/01/19/mybatis-cache.html
https://blog.csdn.net/u010349169/article/details/41408341
https://blog.csdn.net/mengsofts/article/details/88074790
MyBatis 二级缓存实现详解及使用注意事项的更多相关文章
- MyBatis 二级缓存全详解
目录 MyBatis 二级缓存介绍 二级缓存开启条件 探究二级缓存 二级缓存失效的条件 第一次SqlSession 未提交 更新对二级缓存影响 探究多表操作对二级缓存的影响 二级缓存源码解析 二级缓存 ...
- MyBatis 一级缓存实现详解及使用注意事项
一级缓存介绍 在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis提供了一级缓存的方案优化这部分场景,如果是相同的SQL语句,会优先命中一级缓存,避免直接对 ...
- MyBatis 一级缓存、二级缓存全详解(一)
目录 MyBatis 一级缓存.二级缓存全详解(一) 什么是缓存 什么是MyBatis中的缓存 MyBatis 中的一级缓存 初探一级缓存 探究一级缓存是如何失效的 一级缓存原理探究 还有其他要补充的 ...
- 《深入理解mybatis原理6》 MyBatis的一级缓存实现详解 及使用注意事项
<深入理解mybatis原理> MyBatis的一级缓存实现详解 及使用注意事项 0.写在前面 MyBatis是一个简单,小巧但功能非常强大的ORM开源框架,它的功能强大也体现在它的缓 ...
- 《深入理解mybatis原理》 MyBatis的一级缓存实现详解 及使用注意事项
MyBatis是一个简单,小巧但功能非常强大的ORM开源框架,它的功能强大也体现在它的缓存机制上.MyBatis提供了一级缓存.二级缓存 这两个缓存机制,能够很好地处理和维护缓存,以提高系统的性能.本 ...
- mybatis深入理解(五)-----MyBatis的一级缓存实现详解 及使用注意事项
0.写在前面 MyBatis是一个简单,小巧但功能非常强大的ORM开源框架,它的功能强大也体现在它的缓存机制上.MyBatis提供了一级缓存.二级缓存 这两个缓存机制,能够很好地处理和维护缓存,以提高 ...
- Hibernate中一级缓存和二级缓存使用详解
一.一级缓存二级缓存的概念解释 (1)一级缓存就是Session级别的缓存,一个Session做了一个查询操作,它会把这个操作的结果放在一级缓存中,如果短时间内这个 session(一定要同一个ses ...
- Spring 整合 Hibernate 时启用二级缓存实例详解
写在前面: 1. 本例使用 Hibernate3 + Spring3: 2. 本例的查询使用了 HibernateTemplate: 1. 导入 ehcache-x.x.x.jar 包: 2. 在 a ...
- Mybatis SQL映射文件详解
Mybatis SQL映射文件详解 mybatis除了有全局配置文件,还有映射文件,在映射文件中可以编写以下的顶级元素标签: cache – 该命名空间的缓存配置. cache-ref – 引用其它命 ...
随机推荐
- Java Jar包压缩、解压使用
什么是jar包JAR(Java Archive)是Java的归档文件,它是一种与平台无关的文件格式,它允许将许多文件组合成一个压缩文件. 如何打/解包使用jdk/bin/jar.exe工具,配置完环境 ...
- alert之后才执行
如果在正常情况下,代码要在alert之后才执行,解决办法:将要执行的代码用setTimeout延迟执行即可(原因:页面未加载完毕)
- AOP中环绕通知的书写和配置
package com.hope.utils;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotatio ...
- 【MySQL】排名函数
https://www.cnblogs.com/shizhijie/p/9366247.html 排名函数 主要有rank和dense_rank两种 区别: rank在排名的时候,排名的键一样的时候是 ...
- Python matplotlib绘图设置图例
一.语法简介 plt.legend(loc=2,edgecolor='red',facecolor='green',shadow='True',fontsize=10) #edgecolor 图例边框 ...
- 09 - Vue3 UI Framework - Table 组件
接下来做个自定义的表格组件,即 table 组件 返回阅读列表点击 这里 需求分析 开始之前我们先做一个简单的需求分析 基于原生 table 标签的强语义 允许用户自定义表头.表体 可选是否具有边框 ...
- [BUUCTF]PWN——[BJDCTF 2nd]secret
[BJDCTF 2nd]secret 附件 步骤: 例行检查,64位程序,开启了canary和nx 本地试运行一下,看看程序大概的情况,好像是一个什么游戏 64位ida载入,检索程序里的字符串,发现了 ...
- springboot学习(一)
最近想学习springboot所以在网上找了很多文章参考怎么构建springboot项目以及集成mybatis 集成mybatis的部分参考了这两篇文章 https://blog.csdn.net/t ...
- .NET静态代码织入——肉夹馍(Rougamo)
肉夹馍是什么 肉夹馍通过静态代码织入方式实现AOP的组件..NET常用的AOP有Castle DynamicProxy.AspectCore等,以上两种AOP组件都是通过运行时生成一个代理类执行AOP ...
- CF19A World Football Cup 题解
Content 有 \(n\) 个球队参加一场足球比赛,比赛排名前 \(\dfrac{n}{2}\) 的队伍将会进入下一轮的淘汰赛.比赛将会打 \(\dfrac{n(n-1)}{2}\) 场,胜者得 ...