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 示例-简介

MyBatis 示例-类型处理器

MyBatis 示例-传递多个参数

MyBatis 示例-主键回填

MyBatis 示例-动态 SQL

MyBatis 示例-联合查询

MyBatis 示例-缓存

MyBatis 示例-插件

MyBatis 示例-缓存的更多相关文章

  1. MyBatis 示例-传递多个参数

    映射器的主要元素: 本章介绍 select 元素中传递多个参数的处理方式. 测试类:com.yjw.demo.MulParametersTest 使用 Map 传递参数(不建议使用) 使用 MyBat ...

  2. MyBatis 示例-类型处理器

    MyBatis 提供了很多默认类型处理器,参考官网地址:链接,除了官网提供的类型处理器,我们也可以自定义类型处理器. 具体做法为:实现 org.apache.ibatis.type.TypeHandl ...

  3. MyBatis 示例-简介

    简介 为了全面熟悉 MyBatis 的使用,整理一个 MyBatis 的例子,案例中包含了映射器.动态 SQL 的使用.本章先介绍项目结构和配置. 项目地址:链接 数据库表的模型关系:链接 项目结构 ...

  4. MyBatis 示例-联合查询

    简介 MyBatis 提供了两种联合查询的方式,一种是嵌套查询,一种是嵌套结果.先说结论:在项目中不建议使用嵌套查询,会出现性能问题,可以使用嵌套结果. 测试类:com.yjw.demo.JointQ ...

  5. MyBatis 示例-动态 SQL

    MyBatis 的动态 SQL 包括以下几种元素: 详细的使用参考官网文档:http://www.mybatis.org/mybatis-3/zh/dynamic-sql.html 本章内容简单描述这 ...

  6. MyBatis 示例-插件

    简介 利用 MyBatis Plugin 插件技术实现分页功能. 分页插件实现思路如下: 业务代码在 ThreadLocal 中保存分页信息: MyBatis Interceptor 拦截查询请求,获 ...

  7. MyBatis 示例-主键回填

    测试类:com.yjw.demo.PrimaryKeyTest 自增长列 数据库表的主键为自增长列,在写业务代码的时候,经常需要在表中新增一条数据后,能获得这条数据的主键 ID,MyBatis 提供了 ...

  8. mybatis 二级缓存

    Mybatis读取缓存次序: 先从二级缓存中获取数据,如果有直接获取,如果没有进行下一步: 从一级缓存中取数据,有直接获取,如果没有进行下一步: 到数据库中进行查询,并保存到一级缓存中: 当sqlSe ...

  9. mybatis一级缓存和二级缓存(二)

    注意事项与示例配置 一级缓存 Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个SqlSession而言.所以在参数和SQL完全一样的情况下,我们使用 ...

随机推荐

  1. jsp 中include使用问题

    如果在需要在jsp页面做页面包含时 会使用到include 指令 来进行页面包含操作 这里是页面的头部 文件名称是head.jsp <%@ page language="java&qu ...

  2. asp.net core3.0 mvc 用 autofac

    好久没有写文章了,最近在用.net core3.0,一些开发中问题顺便记录: 1.首先nuget引入 Autofac Autofac.Extensions.DependencyInjection 2. ...

  3. Java String 类解析

    I.构造函数: public String() {} 默认构造函数 public String(String original) {} 使用原有字符串构造  public String(char va ...

  4. Mysql 笔记二

    Mysql 笔记二 Mysql 笔记二 Table of Contents 1. 前言 2. Master Thread 工作方式 2.1. 主循环(loop) 2.2. 后台循(backgroup ...

  5. 【django】分页

    分页 1.简单分页 from django.conf.urls import url from django.contrib import admin from app01 import views ...

  6. Spring boot 梳理 - Spring boot自动注册DispatcherServlet

    spring boot提供的DispatcherServlet的name就是“dispatcherServlet”. 源码 public ServletRegistrationBean dispatc ...

  7. Ubuntu su命令 Authentication failure的解决办法

    重新设置root的密码: $ sudo passwd root Enter new UNIX password: Retype new UNIX password: passwd: password ...

  8. Golang 接口与反射知识要点

    目录 Golang 接口与反射知识要点 1. 接口类型变量 2. 类型断言 3. 鸭子类型 4. 反射机制 5. reflect 包 TypeOf().ValueOf() Type().Kind() ...

  9. Lxde添加触摸板双击功能、防误触

    前言 本文链接:https://www.cnblogs.com/hellxz/p/linux_touchpad_settings.html 这时简单记录一下最近两天折腾Lxde的触摸板功能的设置,留待 ...

  10. bugku—Web_Writeup

    Bugku_Web_Writeup Writeup略显粗糙~~ 部分Web题没有得到最后的flag~只是有了一个简单的思路~~ Web1: 如上,打开题目答题网址后就会弹出一张图片,看图片就可以发现是 ...