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 – 引用其它命 ...
随机推荐
- NSURLConnection和Runloop
- 1.1 涉及知识点(1)两种为NSURLConnection设置代理方式的区别 //第一种设置方式: //通过该方法设置代理,会自动的发送请求 // [[NSURLConnection alloc ...
- 【Linux】【Services】【Docker】Docker File
Docker Images: docker commit Dockerfile:文本文件,镜像文件构建脚本: Dockerfile:由一系列用于根据基础镜像构建新的镜像文件的专用指令序列组成: 指令: ...
- Spring Cloud简单项目创建
一.Zuul 原文链接 Zuul的主要功能是路由转发和过滤器.路由功能是微服务的一部分,比如/api/user转发到到user服务,/api/shop转发到到shop服务.zuul默认和Ribbon结 ...
- XML名命空间
XML的名命空间就类似于java的包,命名空间定义:xmlns:***="URI",默认命名空间定义:xmlns="URI" 引号中的URl内容用来唯一标识命名 ...
- 【C/C++】例题3-5 生成元/算法竞赛入门经典/数组与字符串
[题目] x+x的各位数之和为y,x为y的生成元. 求10万以内的n的最小生成元,无解输出0. [解答] 这是我根据自己的想法最初写的代码: #include<cstdio> #inclu ...
- 强化学习实战 | 表格型Q-Learning玩井字棋(四)游戏时间
在 强化学习实战 | 表格型Q-Learning玩井字棋(三)优化,优化 中,我们经过优化和训练,得到了一个还不错的Q表格,这一节我们将用pygame实现一个有人机对战,机机对战和作弊功能的井字棋游戏 ...
- 通过Docker部署Java项目的日志输出到宿主机指定目录
之前写过2篇关于Docker部署的文章: 1.超!超!超简单,Linux安装Docker 2.Docker通过阿里云镜像仓库使用Gitlab_CI部署SpringBoot项目 用上篇博客部署Java程 ...
- Linux目录基础
一.解析映射文件 本地的DNS Linux: /etc/hosts Windows:C:\Windows\System32\drivers\etc\hosts 二.磁盘挂载文件 /etc/fstab ...
- Notepad++ 常用功能:批量取消替换换行、强制刷新数据
批量取消替换换行 换行批量替换成空格 Ctrl+F 打开查找替换窗口,使用:\r\n 替换成 空格,全部替换 强制刷新数据源,重新加载数据 快捷键:Ctrl+R 或者 Alt+F 然后 L 或者点击菜 ...
- M函数目录(Power Query 之 M 语言)
2021-12-11更新 主页(选项卡) 管理列(组) 选择列 选择列Table.SelectColumns 删除列 删除列Table.RemoveColumns 删除其他列Table.SelectC ...