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完全一样的情况下,我们使用 ...
随机推荐
- SpringBoot的注解注入功能移植到.Net平台(开源)
*:first-child { margin-top: 0 !important; } .markdown-body>*:last-child { margin-bottom: 0 !impor ...
- uboot学习之uboot启动流程简述
一.uboot启动分为了三个阶段BL0.BL1.BL2:BL0表示上电后运行ROM中固化的一段程序,其中ROM中的程序是厂家写进去的,所以具体功能可能根据厂家芯片而有所不同.功能如下: 初始化系统时钟 ...
- centos 升级
yum -y update升级所有包同时也升级软件和系统内核 yum -y upgrade只升级所有包,不升级软件和系统内核
- Spring boot 梳理 - Spring boot自动注册DispatcherServlet
spring boot提供的DispatcherServlet的name就是“dispatcherServlet”. 源码 public ServletRegistrationBean dispatc ...
- Spring 梳理-MVC-前端控制器DispatchServlet及URL请求处理过程
大多数基于java的web框架,都有前端控制器 spring mvc请求过程 1. 请求会首先发送到DispatchServlet,这是spring的前置Servlet,它会接收请求并转发给sprin ...
- ReactNative实现GridView
ReactNative内置了ListView组件但是没有类似GridView这样的组件.利用一些已经有的属性是可以实现GridView的,利用ContentContainerStyle的属性然后配合样 ...
- Ajax async属性
async: 默认是true:异步,false:同步. 其他属性扩展: 1.url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type: 要求为String类型的参数, ...
- 基于动态代理的WebAPI/RPC/webSocket框架,一套接口定义,多个通讯方式
API/RPC/webSocket三个看起来好像没啥相同的地方,在开发时,服务端,客户端实现代码也大不一样 最近整理了一下,通过动态代理的形式,整合了这些开发,都通过统一的接口约束,服务端实现和客户端 ...
- SpringBoot2+Netty打造通俗简版RPC通信框架
2019-07-19:完成基本RPC通信! 2019-07-22:优化此框架,实现单一长连接! 2019-07-24:继续优化此框架:1.增加服务提供注解(带版本号),然后利用Spring框架的在启动 ...
- js实现烟花效果
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...