【MyBatis学习笔记 八】MyBatis两级缓存机制

2023-12-24 10:58

本文主要是介绍【MyBatis学习笔记 八】MyBatis两级缓存机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

早在去年12月份的一篇Blog中【MySQL数据库原理 一】MySQL架构及查询语句执行流程探索MySQL的执行步骤中就提到过查询缓存这一个概念:
在这里插入图片描述

并且提到过其实不建议使用查询缓存,正因为如此,我们才不把缓存做到数据库,这样作为服务端的数据库缓存了各个客户端大量查询结果能用的比例却比较低,性价比不高;反之大多数应用都把缓存做到了应用逻辑层,简单的如一个map的MyBatis,由客户端自己定义策略。
在这里插入图片描述

缓存的基本概念

什么是缓存,缓存就是存储在内存中的临时数据。将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。使用缓存的目的就是减少和数据库的交互次数,减少系统开销,提高系统效率,但是一定要注意,缓存存储的只能是:经常查询并且不经常改变的数据

MyBatis两级缓存

MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率,MyBatis默认定义了两级缓存:一级缓存和二级缓存

  • 一级缓存,SqlSession级别的缓存,也称为本地缓存,默认情况下,只有一级缓存开启。
  • 二级缓存,基于namespace级别的缓存,也称为全局缓存,需要手动开启和配置。

其中,为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存,整体的结构图如下:

在这里插入图片描述
我们使用之前的Person进行相关的测试,看看一次会话期间的一级缓存是如何实现的。PersonDao查询语句如下

Person getPersonByIdResult(@Param("id")int id);

xml中的SQL配置如下:

<select id="getPersonByIdResult" resultMap="PersonBankAccountByResultMap">select p.id id,p.username,username,p.password password,p.age age,p.phone phone,p.email email,p.hobby hobby,ba.id bid,ba.bank_name bank_name,ba.account_name account_name,ba.person_id person_idfrom person p,bank_Account bawhere p.id=ba.person_id and p.id=#{id}</select><resultMap id="PersonBankAccountByResultMap" type="com.example.MyBatis.dao.model.Person"><!-- id为主键 --><id column="id" property="id"/><result column="username" property="username"/><result column="password" property="password"/><result column="age" property="age"/><result column="phone" property="phone"/><!-- column写错会因为查到数据匹配不上,给默认值, type写错会报错,找不到属性--><result column="email" property="email"/><!-- column是数据库表的列名 , property是对应实体类的属性名 --><result column="hobby" property="interests"/><collection  property="bankAccounts" javaType="ArrayList" ofType="com.example.MyBatis.dao.model.BankAccount"><result column="bid" property="id"  /><result column="bank_name" property="bankName"/><result column="account_name" property="accountName"/><result column="person_id" property="personId"/></collection></resultMap>

MyBatis一级缓存

一级缓存表示会话缓存,也就是SqlSession缓存,在一次会话中的查询数据会被缓存下来。

一级缓存测试

我们编写测试类来查看下一级缓存是否生效

  @Testpublic void testGetPersonByIdResultFirstCache() {//1.获取SqlSession对象SqlSession sqlSession = MybatisUtils.getSqlSession();//2.执行SQLPersonDao personDao = sqlSession.getMapper(PersonDao.class);Person person = personDao.getPersonByIdResult(1);System.out.println(person);Person person2 = personDao.getPersonByIdResult(1);System.out.println(person2);//关闭sqlSessionsqlSession.close();}

日志打印结果如下:
在这里插入图片描述
查询同样的数据,sql语句只被执行了一次,第二次是从缓存中获取的。

一级缓存失效情况

一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它。但有以下几种情况一级缓存会自动失效:

1 SqlSession不同导致缓存失效

两次请求的sqlSession不同导致缓存失效,这个很容易理解,因为一级缓存的作用域就在一次会话内嘛

  @Testpublic void testGetPersonByIdResultFirstCache() {//1.获取SqlSession对象SqlSession sqlSession1 = MybatisUtils.getSqlSession();SqlSession sqlSession2 = MybatisUtils.getSqlSession();//2.执行SQLPersonDao personDao1 = sqlSession1.getMapper(PersonDao.class);PersonDao personDao2 = sqlSession2.getMapper(PersonDao.class);Person person = personDao1.getPersonByIdResult(1);System.out.println(person);Person person2 = personDao2.getPersonByIdResult(1);System.out.println(person2);//关闭sqlSessionsqlSession1.close();sqlSession2.close();}

从日志可以看到进行了两次查询
在这里插入图片描述

2 查询条件不同导致缓存失效

查询条件不同导致缓存失效,这个也很容易理解,因为两次查询的数据不同,第二次查询获取到的数据没有出现在第一次查询后缓存里

 @Testpublic void testGetPersonByIdResultFirstCache() {//1.获取SqlSession对象SqlSession sqlSession = MybatisUtils.getSqlSession();//2.执行SQLPersonDao personDao = sqlSession.getMapper(PersonDao.class);Person person = personDao.getPersonByIdResult(1);System.out.println(person);Person person2 = personDao.getPersonByIdResult(2);System.out.println(person2);//关闭sqlSessionsqlSession.close();}

从日志可以看到进行了两次查询
在这里插入图片描述

3 两次查询间执行增删改操作导致缓存失效

同MySQL之前的查询缓存一样,只要表在两次查询之间发生了变更,那么这个变更语句会清空查询缓存,这也是MySQL弃用查询缓存的原因:

 @Testpublic void testGetPersonByIdResultFirstCache() {//1.获取SqlSession对象SqlSession sqlSession = MybatisUtils.getSqlSession();//2.执行SQLPersonDao personDao = sqlSession.getMapper(PersonDao.class);Person person = personDao.getPersonByIdResult(1);System.out.println(person);int result= personDao.deletePerson(0);System.out.println(result);Person person2 = personDao.getPersonByIdResult(1);System.out.println(person2);//关闭sqlSessionsqlSession.close();}

从日志可以看到进行了两次查询
在这里插入图片描述

4 手动清除SqlSession缓存导致缓存失效

这个也很容易理解,当我们手动clear掉SqlSession缓存时,缓存自然而然也被处理掉了:

  @Testpublic void testGetPersonByIdResultFirstCache() {//1.获取SqlSession对象SqlSession sqlSession = MybatisUtils.getSqlSession();//2.执行SQLPersonDao personDao = sqlSession.getMapper(PersonDao.class);Person person = personDao.getPersonByIdResult(1);System.out.println(person);sqlSession.clearCache();Person person2 = personDao.getPersonByIdResult(1);System.out.println(person2);//关闭sqlSessionsqlSession.close();}

从日志可以看到进行了两次查询
在这里插入图片描述

MyBatis二级缓存

二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存,基于namespace级别的缓存,一个命名空间,对应一个二级缓存,也就是一个Mapper对应一个二级缓存。工作机制:

  1. 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
  2. 如果当前会话关闭了,这个会话对应的一级缓存就没了,一级缓存中的数据被保存到二级缓存中
  3. 新的会话查询信息,可以从二级缓存中获取内容;
  4. 不同的mapper查出的数据会放在自己对应的缓存(map)

我们来看下二级缓存的实现方式:

1 开启全局缓存

首先需要在全局配置文件mybatis-config.xml中打开全局缓存,也就是二级缓存:

    <settings><setting name="logImpl" value="log4j"/><setting name="cacheEnabled" value="true"/>  //打开全局缓存</settings>

2 PersonMapper中开启全局缓存

personMapper.xml的配置文件中加入:

 <!--创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突--><cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

3 测试二级缓存

只有在一级缓存会话关闭或者提交后,缓存数据才会被提交到二级缓存,我们模拟下这个实现:

@Testpublic void testGetPersonByIdResultSecondCache() {//1.获取SqlSession对象SqlSession sqlSession1 = MybatisUtils.getSqlSession();SqlSession sqlSession2 = MybatisUtils.getSqlSession();//2.执行SQLPersonDao personDao1 = sqlSession1.getMapper(PersonDao.class);PersonDao personDao2 = sqlSession2.getMapper(PersonDao.class);Person person = personDao1.getPersonByIdResult(1);System.out.println(person);//会话1关闭,会话2执行查询sqlSession1.close();Person person2 = personDao2.getPersonByIdResult(1);System.out.println(person2);//关闭sqlSessionsqlSession1.close();sqlSession2.close();}

查看日志发现只进行了一次查询,全局缓存生效
在这里插入图片描述

EhCache实现二级缓存

Ehcache是一种广泛使用的java分布式缓存,用于二级全局缓存。要在应用程序中使用Ehcache,需要引入依赖的jar包,输入如下Maven坐标引入:

       <dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-ehcache</artifactId><version>1.2.1</version></dependency>

然后在对应的Mapper中,我们这里是personMapper.xml文件中加入该缓存支持,同时注释原有二级缓存配置:

<!--创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突--><!--<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>--><cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

代码测试结果同原有二级缓存测试。

总结一下

MySQL其实不建议使用查询缓存,同时8.X以后版本也移除了该模块。正因为如此,我们才不把缓存做到数据库,作为服务端的数据库缓存了各个客户端大量查询结果只要进行一次全表更新就失效,能用的比例非常低,性价比不高;反之大多数应用都把缓存做到了应用逻辑层,简单的如一个map的MyBatis,由客户端自己定义策略,缓存由框架自己实现,MySQL没有负担,客户端也能灵活定义自己的配置。

这篇关于【MyBatis学习笔记 八】MyBatis两级缓存机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/531551

相关文章

mybatis映射器配置小结

《mybatis映射器配置小结》本文详解MyBatis映射器配置,重点讲解字段映射的三种解决方案(别名、自动驼峰映射、resultMap),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定... 目录select中字段的映射问题使用SQL语句中的别名功能使用mapUnderscoreToCame

mybatis-plus如何根据任意字段saveOrUpdateBatch

《mybatis-plus如何根据任意字段saveOrUpdateBatch》MyBatisPlussaveOrUpdateBatch默认按主键判断操作类型,若需按其他唯一字段(如agentId、pe... 目录使用场景方法源码方法改造首先在service层定义接口service层接口实现总结使用场景my

MyBatis ParameterHandler的具体使用

《MyBatisParameterHandler的具体使用》本文主要介绍了MyBatisParameterHandler的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参... 目录一、概述二、源码1 关键属性2.setParameters3.TypeHandler1.TypeHa

MyBatis-plus处理存储json数据过程

《MyBatis-plus处理存储json数据过程》文章介绍MyBatis-Plus3.4.21处理对象与集合的差异:对象可用内置Handler配合autoResultMap,集合需自定义处理器继承F... 目录1、如果是对象2、如果需要转换的是List集合总结对象和集合分两种情况处理,目前我用的MP的版本

使用Spring Cache本地缓存示例代码

《使用SpringCache本地缓存示例代码》缓存是提高应用程序性能的重要手段,通过将频繁访问的数据存储在内存中,可以减少数据库访问次数,从而加速数据读取,:本文主要介绍使用SpringCac... 目录一、Spring Cache简介核心特点:二、基础配置1. 添加依赖2. 启用缓存3. 缓存配置方案方案

基于Redis自动过期的流处理暂停机制

《基于Redis自动过期的流处理暂停机制》基于Redis自动过期的流处理暂停机制是一种高效、可靠且易于实现的解决方案,防止延时过大的数据影响实时处理自动恢复处理,以避免积压的数据影响实时性,下面就来详... 目录核心思路代码实现1. 初始化Redis连接和键前缀2. 接收数据时检查暂停状态3. 检测到延时过

Redis中哨兵机制和集群的区别及说明

《Redis中哨兵机制和集群的区别及说明》Redis哨兵通过主从复制实现高可用,适用于中小规模数据;集群采用分布式分片,支持动态扩展,适合大规模数据,哨兵管理简单但扩展性弱,集群性能更强但架构复杂,根... 目录一、架构设计与节点角色1. 哨兵机制(Sentinel)2. 集群(Cluster)二、数据分片

Unity新手入门学习殿堂级知识详细讲解(图文)

《Unity新手入门学习殿堂级知识详细讲解(图文)》Unity是一款跨平台游戏引擎,支持2D/3D及VR/AR开发,核心功能模块包括图形、音频、物理等,通过可视化编辑器与脚本扩展实现开发,项目结构含A... 目录入门概述什么是 UnityUnity引擎基础认知编辑器核心操作Unity 编辑器项目模式分类工程

Java实现本地缓存的四种方法实现与对比

《Java实现本地缓存的四种方法实现与对比》本地缓存的优点就是速度非常快,没有网络消耗,本地缓存比如caffine,guavacache这些都是比较常用的,下面我们来看看这四种缓存的具体实现吧... 目录1、HashMap2、Guava Cache3、Caffeine4、Encache本地缓存比如 caff

MyBatis-Plus 与 Spring Boot 集成原理实战示例

《MyBatis-Plus与SpringBoot集成原理实战示例》MyBatis-Plus通过自动配置与核心组件集成SpringBoot实现零配置,提供分页、逻辑删除等插件化功能,增强MyBa... 目录 一、MyBATis-Plus 简介 二、集成方式(Spring Boot)1. 引入依赖 三、核心机制