Data Access 之 MyBatis(五)- MyBatis Cache(下)

简介: Data Access 之 MyBatis(五)- MyBatis Cache

二级缓存

  • 二级缓存是全局作用域的缓存
  • 二级默认不开启,需要手动配置
  • MyBatis提供二级缓存接口以及实现,缓存实现要求Entity实现Serializable接口
  • 二级缓存在SqlSession(一级缓存)关闭或提交后,一级缓存的数据会放到二级缓存中才会生效,

二级缓存使用步骤

  1. 全局配置文件中开启二级缓存
<setting name="cacheEnabled" value="true"/>
复制代码
  1. 需要使用二级缓存的映射文件使用cache标签配置缓存,放在mapper标签中
<cache></cache>
复制代码
  1. Entity实体类接口需要实现序列化Serializable接口
@Data
public class Teacher implements Serializable {
    private Integer id;
    private String teacherName;
    private String className;
    private String address;
    private Date birthDate;
}
复制代码

新增测试方法

@Test
public void testSecondLevelCache(){
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    TeacherMapper teacherMapper1 = sqlSession1.getMapper(TeacherMapper.class);
    TeacherMapper teacherMapper2 = sqlSession2.getMapper(TeacherMapper.class);
    teacherMapper1.getTeacherById(1);
    // 注意:只有关闭了一级缓存,二级缓存才会生效
    sqlSession1.close();
    System.out.println("-------------");
    teacherMapper2.getTeacherById(1);
    sqlSession2.close();
}
复制代码

执行测试

347fa01e101b46c49e056aa5bd2326de_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

控制台只输出了一条SQL,且缓存命中率为0.5,也就是说第一次查询没有命中缓存,第二次查询命中了缓存,说明二级缓存生效了

二级缓存的属性

在Mapper XML中配置二级缓存的cache标签有以下这些属性

  • eviction:表示缓存回收策略,默认策略为LRU
  • LRU:最近最少使用,移除长时间不被使用的额对象
  • FIFO:先进先出,按照对象进入缓存的顺序来移除
  • SOFT:软引用,易出基于垃圾回收状态和软引用规则的对象
  • WEAK:弱引用,更积极的移除基于垃圾回收状态和弱引用规则的对象
  • flushInterval:刷新间隔,单位为浩渺,默认不设置
  • size:引用数目,代表最多可以缓存多少个对象,太多会导致内存溢出
  • readOnly:只读,可以设置tru或false
  • true:只读缓存,会给所有嗲用着返回缓存对象的相同实例,因为这些对象不能被修改,有很高的性能
  • false:读写缓存,会返回缓存对象的拷贝(通过系列化),会更安全但也因此损失了性能

缓存查询顺序及原理

缓存的查询顺序

在TeacherMapperTest测试类中增加一个方法testCacheQueryOrder,测试缓存的查询顺序

@Test
public void testCacheQueryOrder(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
    Teacher teacher = mapper.getTeacherById(2);
    System.out.println(teacher);
    sqlSession.close();
}
复制代码

第一次执行测试

754ec74d4039440f87f54fb75a4e4cbd_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

缓存命中率为0,并且输出了执行的SQL,说明肯定去查了二级缓存和一级缓存,二级缓存中没有数据,一级缓存中也没有数据,所以查询了数据库

在末尾增加代码,再次查询

SqlSession sqlSession1 = sqlSessionFactory.openSession();
TeacherMapper mapper1 = sqlSession1.getMapper(TeacherMapper.class);
Teacher teacher1 = mapper1.getTeacherById(2);
System.out.println(teacher1);
sqlSession1.close();
复制代码

第二次执行测试

5f6e2548d13c4e0986d56af6e25e84c4_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

执行了两次查询,第一次查询一级缓存和二级缓存中都没有数据所以去查询数据库,第二次从二级缓存中查到数据,所以二级缓存命中率为0.5

在sqlSession1.close()上面增加代码两行代码,再次查询teaher并输出。

Teacher teacher2 = mapper1.getTeacherById(2);
System.out.println(teacher2);
复制代码

第三次次执行测试

image.png

两次都是从二级缓存中获获取

在sqlSession1.close()上面增加代码,查询其他ID的数据

// 查询一个二级缓存中不存在的数据
// 一级缓存中如果还没有,就去查询数据库,之后放在一级缓存中
Teacher teacher3 = mapper1.getTeacherById(3);
System.out.println(teacher3);
// 这次查询会从一级缓存中查询
Teacher teacher4 = mapper1.getTeacherById(3);
System.out.println(teacher4)
复制代码

第四次执行测试

image.png

缓存命中率降低,说明数据不是从二级缓存而是从一级缓存中拿的,二级缓存不存在这个数据,也说明是优先看二级缓存,二级缓存中没有才去看的一级缓存,因为如果从一级缓存中直接找到数据,就没有必要再到二级缓存中去找了,缓存命中率降低就说明了先找二级缓存再找一级缓存

总结:

  • 不会出现一级缓存和二级缓存中有同一个数据的情况
  • 二级缓存中的数据在一级缓存也就是SqlSession缓存关闭了才有
  • 二级缓存中没有数据就会看一级缓存,一级缓存中也没有就会看数据库,数据库查完之后就会存在SqlSession中
  • 任何时候都是先看二级缓存在看一级缓存,最后都没有再去查询数据库

缓存的原理

缓存相关的设置

  • MyBatis全局配置文件mybatis-config.xml中的cacheEnable标签配置了二级缓存的开关,一级缓存一直是开启的
  • Mapper XML文件中select标签具有useCache标签,可以设置是否使用二级缓存,一级缓存一直使用
  • sql标签具有flushCache属性,增删改语句默认flushCache为true,sql执行后会清空一级缓存以及二级缓存,查询语句默认flushCache为false
  • sqlSession对象具有clearCache方法,只能用来清除一级缓存
  • 在某一个作用域进行了增删改操作后,默认所有的select的缓存会被清空,参考一级缓存失效的几种情况

测试是否使用二级缓存,设置select语句属性useCache="false"

@Test
public void testUseCacheFalse(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
    Teacher teacher = mapper.getTeacherById(2);
    System.out.println(teacher);
    sqlSession.close();
    System.out.println("第二次查询,不同的SqlSession,不同的Mapper");
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    TeacherMapper mapper1 = sqlSession1.getMapper(TeacherMapper.class);
    Teacher teacher1 = mapper1.getTeacherById(2);
    System.out.println(teacher1);
    System.out.println("第三次查询,与第一次同一个SqlSession, 不同的Mapper");
    Teacher teacher2 = mapper1.getTeacherById(2);
    System.out.println(teacher2);
    sqlSession1.close();
}
复制代码

执行测试

image.png

没有用到二级缓存,但是用到了一级缓存,设置useCache=false对一级缓存没有任何影响

设置useCache="true",再次执行测试

image.png

缓存命中率为0.6666,第三次查询是从二级缓存中获取的,先查了二级缓存,二级缓存中有数据,就不用再去查一级缓存

整合第三方缓存

image.png

整合EHcache

MyBatis中定义了Cache接口,整合第三方或者自定义缓存可以实现该接口

image.png

该接口提供了一些必须实现的方法,如putObject方法就是将数据放入缓存中,getObject方法就是将数据从缓存中取出等其他方法。

Ehcache是一个纯Java的进程内缓存框架,具有快速、简单的特点,是Hibernate中默认的CacheProvider。

mybatis-ehcache中帮我们写好了mybatis中Cache接口的实现

第一步首先在pom.xml文件中添加ehcache依赖和mybatis-ehcache依赖

<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.ehcache/ehcache -->
<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.9.9</version>
</dependency>
复制代码

第二步是在resources目录下增加ehcache缓存配置ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
 <!-- 磁盘保存路径 -->
 <diskStore path="/Users/jingnan/Practice/mybatis-cache/ehcache" />
 <defaultCache 
   maxElementsInMemory="1" 
   maxElementsOnDisk="10000000"
   eternal="false" 
   overflowToDisk="true" 
   timeToIdleSeconds="120"
   timeToLiveSeconds="120" 
   diskExpiryThreadIntervalSeconds="120"
   memoryStoreEvictionPolicy="LRU">
 </defaultCache>
</ehcache>
复制代码

属性说明:

  • diskStore:指定数据在磁盘中的存储位置。
  • defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用指定的的管理策略

以下属性是必须的:

  • maxElementsInMemory - 在内存中缓存的element的最大数目
  • maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
  • eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
  • overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上

以下属性是可选的:

  • timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
  • timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
  • diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
  • diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
  • diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
  • memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)

第三步是在TeaacheMapper中设置二级缓存使用ehcache,并且实体类可以不再实现序列化接口

<!--设置第三方的自定义缓存-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache" ></cache>
复制代码

新增测试方法testEhcache(),将上一个测试方法的代码拷贝过来。使用第三方缓存不再需要实体类实现序列化接口

@Test
public void testEhcache(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
    Teacher teacher = mapper.getTeacherById(2);
    System.out.println(teacher);
    sqlSession.close();
    // System.out.println("第二次查询,不同的SqlSession,不同的Mapper");
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    TeacherMapper mapper1 = sqlSession1.getMapper(TeacherMapper.class);
    Teacher teacher1 = mapper1.getTeacherById(2);
    System.out.println(teacher1);
    sqlSession1.close();
}
复制代码

执行测试

image.png

设置EHcache配置文件中保存到内存中的数据为1,即只要数据量超过1就会保存磁盘上,设置0的意思保存到内存中的数据是无限大

再次执行测试

image.png

查看存放缓存的磁盘目录

image.png

其他Mapper XML中配置二级缓存可以通过引用已配置缓存的namesapce

<cache-ref namespace="com.citi.mapper.TeacherMapper"/>
复制代码

也可以直接cache标签type属性定义

<cache type="org.mybatis.caches.ehcache.EhcacheCache" ></cache>


相关文章
|
2天前
|
druid Java 数据库连接
SpringBoot原理分析 | Spring Data整合:JDBC、Druid、Mybatis
SpringBoot原理分析 | Spring Data整合:JDBC、Druid、Mybatis
72 0
|
2天前
|
存储 设计模式 Java
Mybatis源码细节探究:二级缓存Cache对象是在什么时候创建的?
Mybatis源码细节探究:二级缓存Cache对象是在什么时候创建的?
|
2天前
|
SQL 缓存 Java
Mybatis源码细节探究:MappedStatement和Cache对象对照关系研究
Mybatis源码细节探究:MappedStatement和Cache对象对照关系研究
|
2天前
|
XML 缓存 Java
你尝试过在mybatis某个mapper上同时配置<cache/>和<cache-ref/>吗?
你尝试过在mybatis某个mapper上同时配置<cache/>和<cache-ref/>吗?
46 0
|
SQL 缓存 Java
Data Access 之 MyBatis Plus(四)- MyBatis Plus Plugin
Data Access 之 MyBatis Plus(四)- MyBatis Plus Plugin
Data Access 之 MyBatis Plus(四)- MyBatis Plus Plugin
|
Java 数据库连接 PHP
Data Access 之 MyBatis Plus(六)- ActiveRecord
Data Access 之 MyBatis Plus(六)- ActiveRecord
Data Access 之 MyBatis Plus(六)- ActiveRecord
|
SQL Java 数据库连接
Data Access 之 MyBatis Plus(五)- 自定义 BaseMapper
Data Access 之 MyBatis Plus(五)- 自定义 BaseMapper
Data Access 之 MyBatis Plus(五)- 自定义 BaseMapper
|
SQL Java 数据库连接
Data Access 之 MyBatis Plus(三)- MPG代码生成器(Part B)
Data Access 之 MyBatis Plus(三)- MPG代码生成器(Part B)
Data Access 之 MyBatis Plus(三)- MPG代码生成器(Part B)
|
XML SQL Java
Spring 全家桶之 Spring Boot 2.6.4(四)- Data Access(Part D MyBatis Plus)
Spring 全家桶之 Spring Boot 2.6.4(四)- Data Access(Part D MyBatis Plus)
Spring 全家桶之 Spring Boot 2.6.4(四)- Data Access(Part D MyBatis Plus)
|
XML 前端开发 Java
Data Access 之 MyBatis Plus(三)- MPG代码生成器(Part A)
Data Access 之 MyBatis Plus(三)- MPG代码生成器(Part A)
Data Access 之 MyBatis Plus(三)- MPG代码生成器(Part A)