MyBatis 示例-缓存
MyBatis 提供两种类型的缓存,一种是一级缓存,另一种是二级缓存,本章通过例子的形式描述 MyBatis 缓存的使用。
测试类:com.yjw.demo.CacheTest
一级缓存
MyBatis 默认开启一级缓存。一级缓存是相对于同一个 SqlSession 而言的,所以在参数和 SQL 完全一样的情况下,我们使用同一个 SqlSession 对象调用同一个 Mapper 的方法,往往只执行一次 SQL,因为使用 SqlSession 第一次查询后,MyBatis 会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没超时的情况下,SqlSession 都只会取出当前缓存的数据,而不会再次发送 SQL 到数据库。
测试方法:
/**
* 一级缓存
*/
@Test
public void l1Cache() {
SqlSession sqlSession = sqlSessionFactory.openSession();
long startTime1 = System.currentTimeMillis();
sqlSession.selectList("com.yjw.demo.mybatis.biz.dao.StudentDao.listByConditions");
LOGGER.info("第一次查询执行时间:" + (System.currentTimeMillis() - startTime1));
long startTime2 = System.currentTimeMillis();
sqlSession.selectList("com.yjw.demo.mybatis.biz.dao.StudentDao.listByConditions");
LOGGER.info("第二次查询执行时间:" + (System.currentTimeMillis() - startTime2));
sqlSession.close();
}
2019-09-16 10:16:02.133 INFO 26268 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
2019-09-16 10:16:02.148 DEBUG 26268 --- [ main] c.y.d.m.b.d.StudentDao.listByConditions : ==> Preparing: select id, name, sex, selfcard_no, note from t_student
2019-09-16 10:16:02.210 DEBUG 26268 --- [ main] c.y.d.m.b.d.StudentDao.listByConditions : ==> Parameters:
2019-09-16 10:16:02.242 DEBUG 26268 --- [ main] c.y.d.m.b.d.StudentDao.listByConditions : <== Total: 3
2019-09-16 10:16:02.243 INFO 26268 --- [ main] com.yjw.demo.CacheTest : 第一次查询执行时间:825
2019-09-16 10:16:02.244 INFO 26268 --- [ main] com.yjw.demo.CacheTest : 第二次查询执行时间:1
对比两次查询的日志内容,第二次查询没有执行 SQL 语句,显然第二次查询是从缓存中获取的数据。
二级缓存(不建议使用)
MyBatis 默认不开启二级缓存。二级缓存是 SqlSessionFactory 层面上的 ,二级缓存的开启需要进行配置,实现二级缓存的时候,MyBatis 要求返回的 POJO 必须是可序列化的,也就是要求实现 Serializable 接口,配置的方法很简单,只需要在映射 XML 文件配置 <cache /> 元素就可以开启缓存了。
MyBatis 二级缓存是基于 namespace 的,缓存的内容是根据 namespace 存放的,可以认为 namespace 就是缓存的 KEY 值 。
<cache />
这样的一条语句里面,很多设置是默认的,如果我们只是这样配置,那么就意味着:
- 映射语句文件中的所有 select 语句将会被缓存;
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存;
- 缓存会使用默认的 Least Recently Used(LRU,最近最少使用的)算法来收回;
- 根据时间表,比如 No Flush Interval,(CNFI,没有刷新间隔),缓存不会以任何时间顺序来刷新;
- 缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用;
- 缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全地被调用者修改,不干扰其他调用者或线程所做的潜在修改。
另外我们还可以通过<cache-res />配置实现多个 namespace 共用同一个二级缓存,即同一个 Cache 对象。

如上图所示,namespace2 共用了 namespace1 的 Cache 对象。
二级缓存可以和一级缓存共存,通过下图来理解 MyBatis 的两层缓存结构。

当应用程序通过 SqlSession2 执行定义在命名空间 namespace2 中的查询操作时,SqlSession2 首先到 namespace2 对应的二级缓存中查找是否缓存了相应的结果对象。如果没有,则继续到 SqlSession2 对应的一级缓存中查找是否缓存了相应的结果对象,如果依然没有,则访问数据库获取结果集并映射成结果对象返回。 最后,该结果对象会记录到 SqlSession 对应的一级缓存以及 namespace2 对应的二级缓存中,等待后续使用。另外需要注意的是,上图中的命名空间 namespace2 和 namespace3 共享了同一个二级缓存对象,所以通过 SqlSession3 执行命名空间 namespace3 中的完全相同的查询操作(只要该查询生成的 CacheKey 对象与上述 SqlSession2 中的查询生成 CacheKey 对象相同即可)时,可以直接从二级缓存中得到相应的结果对象。
案例:
我们通过案例测试一下二级缓存,首先实体类必须实现 Serializable 接口,在 StudentMapper 文件中添加如下配置:
<!-- 二级缓存 -->
<cache eviction="LRU" flushInterval="100000" size="1024" readOnly="true" />
测试方法:
/**
* 二级缓存
*/
@Test
public void l2Cache() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
long startTime1 = System.currentTimeMillis();
sqlSession1.selectList("com.yjw.demo.mybatis.biz.dao.StudentDao.listByConditions",
new StudentQuery());
LOGGER.info("第一个SqlSession查询执行时间:" + (System.currentTimeMillis() - startTime1));
sqlSession1.commit();
sqlSession1.close(); SqlSession sqlSession2 = sqlSessionFactory.openSession();
long startTime2 = System.currentTimeMillis();
sqlSession2.selectList("com.yjw.demo.mybatis.biz.dao.StudentDao.listByConditions",
new StudentQuery());
LOGGER.info("第二个SqlSession查询执行时间:" + (System.currentTimeMillis() - startTime2));
sqlSession2.commit();
sqlSession2.close();
}
2019-09-16 14:33:13.848 DEBUG 22372 --- [ main] com.yjw.demo.mybatis.biz.dao.StudentDao : Cache Hit Ratio [com.yjw.demo.mybatis.biz.dao.StudentDao]: 0.0
2019-09-16 14:33:15.748 INFO 22372 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
2019-09-16 14:33:15.764 DEBUG 22372 --- [ main] c.y.d.m.b.d.StudentDao.listByConditions : ==> Preparing: select id, name, sex, selfcard_no, note from t_student
2019-09-16 14:33:15.844 DEBUG 22372 --- [ main] c.y.d.m.b.d.StudentDao.listByConditions : ==> Parameters:
2019-09-16 14:33:15.885 DEBUG 22372 --- [ main] c.y.d.m.b.d.StudentDao.listByConditions : <== Total: 3
2019-09-16 14:33:15.887 INFO 22372 --- [ main] com.yjw.demo.CacheTest : 第一个SqlSession查询执行时间:2304
2019-09-16 14:33:15.890 DEBUG 22372 --- [ main] com.yjw.demo.mybatis.biz.dao.StudentDao : Cache Hit Ratio [com.yjw.demo.mybatis.biz.dao.StudentDao]: 0.5
2019-09-16 14:33:15.891 INFO 22372 --- [ main] com.yjw.demo.CacheTest : 第二个SqlSession查询执行时间:1
从日志中可以看出,第二次查询没有执行 SQL 语句,日志中还打印了缓存命令率:Cache Hit Ratio,所以第二次 Session 执行是从缓存中获取的数据。
二级缓存详细配置介绍:
<cache eviction="LRU" flushInterval="100000" size="1024" readOnly="true" />
- eviction:缓存回收策略,目前 MyBatis 提供一下策略;
- LRU:最近最少使用的,移除最长时间不用的对象;
- FIFO:先进先出,按对象进入缓存的顺序来移除它们;
- SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象;
- WEAK:弱引用,更积极地移除基于垃圾回收器状态和弱引用规则的对象。这里采用的是 LRU,移除最长时间不用的对象;
- flushInterval:刷新间隔时间,单位为毫秒,这里配置的是100秒刷新,如果不配置它,那么当 SQL 被执行的时候才会去刷新缓存;
- size:引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大,设置过大会导致内存溢出,这里配置的是1024个对象;
- readOnly:只读,意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,缺点是我们没有办法修改缓存。
二级缓存的问题:
- 脏数据:因为二级缓存是基于 namespace 的,比如在 StudentMapper 中存在一条查询 SQL,它关联查询了学生证件信息,这个时候开启了二级缓存,在 StudentMapper 对应的缓存中就会存在学生证件的数据,如果更新了学生证件信息的数据,那么在 StudentMapper 中就存在了脏数据;
- 全部失效:insert、update 和 delete 语句会刷新同一个 namespace 下的所有缓存数据,参考如下例子;
/**
* 测试二级缓存全部失效问题,只要执行了insert、update、delete
* 就会刷新同一个 namespace 下的所有缓存数据
*/
@Test
public void l2CacheInvalid() {
// 缓存listByConditions的数据
SqlSession sqlSession1 = sqlSessionFactory.openSession();
long startTime1 = System.currentTimeMillis();
sqlSession1.selectList("com.yjw.demo.mybatis.biz.dao.StudentDao.listByConditions",
new StudentQuery());
LOGGER.info("第一个SqlSession查询执行时间:" + (System.currentTimeMillis() - startTime1));
sqlSession1.commit();
sqlSession1.close(); // 缓存getByPrimaryKey的数据
SqlSession sqlSession2 = sqlSessionFactory.openSession();
long startTime2 = System.currentTimeMillis();
sqlSession2.selectList("com.yjw.demo.mybatis.biz.dao.StudentDao.getByPrimaryKey",
1L);
LOGGER.info("第二个SqlSession查询执行时间:" + (System.currentTimeMillis() - startTime2));
sqlSession2.commit();
sqlSession2.close(); // 执行insert语句使上面所有缓存失效
SqlSession sqlSession3 = sqlSessionFactory.openSession();
StudentDO studentDO = new StudentDO();
studentDO.setName("赵六");
studentDO.setSex(Sex.MALE);
studentDO.setSelfcardNo(4444L);
studentDO.setNote("zhaoliu");
sqlSession3.insert("com.yjw.demo.mybatis.biz.dao.StudentDao.insertByAutoInc", studentDO);
sqlSession3.commit();
sqlSession3.close(); // 再次执行上面缓存的数据,查看缓存是否已经失效
SqlSession sqlSession4 = sqlSessionFactory.openSession();
long startTime4 = System.currentTimeMillis();
sqlSession4.selectList("com.yjw.demo.mybatis.biz.dao.StudentDao.listByConditions",
new StudentQuery());
LOGGER.info("第四个SqlSession查询执行时间:" + (System.currentTimeMillis() - startTime4));
sqlSession4.commit();
sqlSession4.close(); // 缓存getByPrimaryKey的数据
SqlSession sqlSession5 = sqlSessionFactory.openSession();
long startTime5 = System.currentTimeMillis();
sqlSession5.selectList("com.yjw.demo.mybatis.biz.dao.StudentDao.getByPrimaryKey",
1L);
LOGGER.info("第五个SqlSession查询执行时间:" + (System.currentTimeMillis() - startTime5));
sqlSession5.commit();
sqlSession5.close();
}
2019-09-16 14:47:43.489 DEBUG 14940 --- [ main] com.yjw.demo.mybatis.biz.dao.StudentDao : Cache Hit Ratio [com.yjw.demo.mybatis.biz.dao.StudentDao]: 0.0
2019-09-16 14:47:44.258 INFO 14940 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
2019-09-16 14:47:44.274 DEBUG 14940 --- [ main] c.y.d.m.b.d.StudentDao.listByConditions : ==> Preparing: select id, name, sex, selfcard_no, note from t_student
2019-09-16 14:47:44.328 DEBUG 14940 --- [ main] c.y.d.m.b.d.StudentDao.listByConditions : ==> Parameters:
2019-09-16 14:47:44.369 DEBUG 14940 --- [ main] c.y.d.m.b.d.StudentDao.listByConditions : <== Total: 3
2019-09-16 14:47:44.371 INFO 14940 --- [ main] com.yjw.demo.CacheTest : 第一个SqlSession查询执行时间:1015
2019-09-16 14:47:44.377 DEBUG 14940 --- [ main] com.yjw.demo.mybatis.biz.dao.StudentDao : Cache Hit Ratio [com.yjw.demo.mybatis.biz.dao.StudentDao]: 0.0
2019-09-16 14:47:44.378 DEBUG 14940 --- [ main] c.y.d.m.b.d.StudentDao.getByPrimaryKey : ==> Preparing: select id, name, sex, selfcard_no, note from t_student where id = ?
2019-09-16 14:47:44.380 DEBUG 14940 --- [ main] c.y.d.m.b.d.StudentDao.getByPrimaryKey : ==> Parameters: 1(Long)
2019-09-16 14:47:44.382 DEBUG 14940 --- [ main] c.y.d.m.b.d.StudentDao.getByPrimaryKey : <== Total: 1
2019-09-16 14:47:44.383 INFO 14940 --- [ main] com.yjw.demo.CacheTest : 第二个SqlSession查询执行时间:7
2019-09-16 14:47:44.383 DEBUG 14940 --- [ main] c.y.d.m.b.d.StudentDao.insertByAutoInc : ==> Preparing: insert into t_student (name, sex, selfcard_no, note) values ( ?, ?, ?, ? )
2019-09-16 14:47:44.388 DEBUG 14940 --- [ main] c.y.d.m.b.d.StudentDao.insertByAutoInc : ==> Parameters: 赵六(String), 1(Integer), 4444(Long), zhaoliu(String)
2019-09-16 14:47:44.474 DEBUG 14940 --- [ main] c.y.d.m.b.d.StudentDao.insertByAutoInc : <== Updates: 1
2019-09-16 14:47:44.476 DEBUG 14940 --- [ main] com.yjw.demo.mybatis.biz.dao.StudentDao : Cache Hit Ratio [com.yjw.demo.mybatis.biz.dao.StudentDao]: 0.0
2019-09-16 14:47:44.477 DEBUG 14940 --- [ main] c.y.d.m.b.d.StudentDao.listByConditions : ==> Preparing: select id, name, sex, selfcard_no, note from t_student
2019-09-16 14:47:44.477 DEBUG 14940 --- [ main] c.y.d.m.b.d.StudentDao.listByConditions : ==> Parameters:
2019-09-16 14:47:44.481 DEBUG 14940 --- [ main] c.y.d.m.b.d.StudentDao.listByConditions : <== Total: 4
2019-09-16 14:47:44.481 INFO 14940 --- [ main] com.yjw.demo.CacheTest : 第四个SqlSession查询执行时间:5
2019-09-16 14:47:44.482 DEBUG 14940 --- [ main] com.yjw.demo.mybatis.biz.dao.StudentDao : Cache Hit Ratio [com.yjw.demo.mybatis.biz.dao.StudentDao]: 0.0
2019-09-16 14:47:44.483 DEBUG 14940 --- [ main] c.y.d.m.b.d.StudentDao.getByPrimaryKey : ==> Preparing: select id, name, sex, selfcard_no, note from t_student where id = ?
2019-09-16 14:47:44.483 DEBUG 14940 --- [ main] c.y.d.m.b.d.StudentDao.getByPrimaryKey : ==> Parameters: 1(Long)
2019-09-16 14:47:44.485 DEBUG 14940 --- [ main] c.y.d.m.b.d.StudentDao.getByPrimaryKey : <== Total: 1
2019-09-16 14:47:44.486 INFO 14940 --- [ main] com.yjw.demo.CacheTest : 第五个SqlSession查询执行时间:4
从上面的日志信息可以看出,四次查询操作,都执行了 SQL 语句,第四个和第五个查询没有从缓存中获取数据,因为第三个执行语句(insert)把当前 namespace 下的所有缓存都失效了。
鉴于二级缓存存在如上两个问题,所以在项目中不建议使用 MyBatis 的二级缓存。
MyBatis 实用篇
MyBatis 示例-缓存的更多相关文章
- MyBatis 示例-传递多个参数
映射器的主要元素: 本章介绍 select 元素中传递多个参数的处理方式. 测试类:com.yjw.demo.MulParametersTest 使用 Map 传递参数(不建议使用) 使用 MyBat ...
- MyBatis 示例-类型处理器
MyBatis 提供了很多默认类型处理器,参考官网地址:链接,除了官网提供的类型处理器,我们也可以自定义类型处理器. 具体做法为:实现 org.apache.ibatis.type.TypeHandl ...
- MyBatis 示例-简介
简介 为了全面熟悉 MyBatis 的使用,整理一个 MyBatis 的例子,案例中包含了映射器.动态 SQL 的使用.本章先介绍项目结构和配置. 项目地址:链接 数据库表的模型关系:链接 项目结构 ...
- MyBatis 示例-联合查询
简介 MyBatis 提供了两种联合查询的方式,一种是嵌套查询,一种是嵌套结果.先说结论:在项目中不建议使用嵌套查询,会出现性能问题,可以使用嵌套结果. 测试类:com.yjw.demo.JointQ ...
- MyBatis 示例-动态 SQL
MyBatis 的动态 SQL 包括以下几种元素: 详细的使用参考官网文档:http://www.mybatis.org/mybatis-3/zh/dynamic-sql.html 本章内容简单描述这 ...
- MyBatis 示例-插件
简介 利用 MyBatis Plugin 插件技术实现分页功能. 分页插件实现思路如下: 业务代码在 ThreadLocal 中保存分页信息: MyBatis Interceptor 拦截查询请求,获 ...
- MyBatis 示例-主键回填
测试类:com.yjw.demo.PrimaryKeyTest 自增长列 数据库表的主键为自增长列,在写业务代码的时候,经常需要在表中新增一条数据后,能获得这条数据的主键 ID,MyBatis 提供了 ...
- mybatis 二级缓存
Mybatis读取缓存次序: 先从二级缓存中获取数据,如果有直接获取,如果没有进行下一步: 从一级缓存中取数据,有直接获取,如果没有进行下一步: 到数据库中进行查询,并保存到一级缓存中: 当sqlSe ...
- mybatis一级缓存和二级缓存(二)
注意事项与示例配置 一级缓存 Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个SqlSession而言.所以在参数和SQL完全一样的情况下,我们使用 ...
随机推荐
- jquery让form表单异步提交
1.监听表单提交事件,并阻止表单提交 $("form").submit(function(e) { return false;//阻止表单提交 }) 2.拿到表单内容 let da ...
- Spring 梳理 - 视图解析器 VS 视图(View,ViewResolver)
View View接口表示一个响应给用户的视图,例如jsp文件,pdf文件,html文件等 该接口只有两个方法定义,分别表明该视图的ContentType和如何被渲染 Spring中提供了丰富的视图支 ...
- Spring Data JPA 梳理 - JPA是什么
总结: JPA是java的标准,不是Spring的标准 java标准中一般通过Meta-INF文件规范开发层面的事情,JPA也不例外,使用persistence.xml JPA定义了Entity 到 ...
- 第一个SharePoint Add-in工程
一.创建SharePoint hosted 工程 1.创建承载SharePoint Add-in独立域 首先,确定承载的应用程序的独立域名,可以使用类似这样的域名apps.contoso.com,鉴于 ...
- 【SQL server初级】SQL Server 2005 实现数据库同步备份 过程--结果---分析
数据库复制: 简单来说,数据库复制就是由两台服务器,主服务器和备份服务器,主服务器修改后,备份服务器自动修改. 复制的模式有两种:推送模式和请求模式,推送模式是主服务器修改后,自动发给备份服务器, ...
- 聚类算法之K-means
想想常见的分类算法有决策树.Logistic回归.SVM.贝叶斯等.分类作为一种监督学习方法,要求必须事先明确知道各个类别的信息,并且断言所有待分类项都有一个类别与之对应.但是很多时候上述条件得不到满 ...
- php 正则判断是否是手机号码 最新
php 正则判断是否是手机号码 最新 标签: php正则 2013-09-22 14:31 55076人阅读 评论(1) 收藏 举报 分类: php(42) 版权声明:本文为博主原创文章,若转载请 ...
- Chrome浏览器启动报错:应用程序无法启动,因为应用程序的并行配置不正确。
因为国庆节了,难得关一次机(可能搞IT的习惯吧),结果给祖国庆祝完70寿辰之后归来,启动电脑,就打不开Chrome浏览器了,报错如下: 应用程序无法启动,因为应用程序的并行配置不正确.有关详细信息,请 ...
- IDEA 学习笔记之 Maven项目开发
Maven项目开发: 配置Maven: 新建Maven项目: 选择webapp: 和eclipse一样,设置: 修改maven配置,添加一个新属性,可以加快项目创建速度: 完成: 新建java和tes ...
- 《java编程思想》P125-P140(第七章复用类部分)
1.类的成员默认的是包访问权限.允许包内成员访问 2.super.scrub() 调用基类的scrub方法 3.继承并不是复制基类的接口.当创建了一个导出类(子类)对象时,该对象包含了一个基类的子对象 ...