【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

相关文章

Maven 配置中的 <mirror>绕过 HTTP 阻断机制的方法

《Maven配置中的<mirror>绕过HTTP阻断机制的方法》:本文主要介绍Maven配置中的<mirror>绕过HTTP阻断机制的方法,本文给大家分享问题原因及解决方案,感兴趣的朋友一... 目录一、问题场景:升级 Maven 后构建失败二、解决方案:通过 <mirror> 配置覆盖默认行为1. 配置示

MyBatis ResultMap 的基本用法示例详解

《MyBatisResultMap的基本用法示例详解》在MyBatis中,resultMap用于定义数据库查询结果到Java对象属性的映射关系,本文给大家介绍MyBatisResultMap的基本... 目录MyBATis 中的 resultMap1. resultMap 的基本语法2. 简单的 resul

Mybatis的分页实现方式

《Mybatis的分页实现方式》MyBatis的分页实现方式主要有以下几种,每种方式适用于不同的场景,且在性能、灵活性和代码侵入性上有所差异,对Mybatis的分页实现方式感兴趣的朋友一起看看吧... 目录​1. 原生 SQL 分页(物理分页)​​2. RowBounds 分页(逻辑分页)​​3. Page

MyBatis Plus 中 update_time 字段自动填充失效的原因分析及解决方案(最新整理)

《MyBatisPlus中update_time字段自动填充失效的原因分析及解决方案(最新整理)》在使用MyBatisPlus时,通常我们会在数据库表中设置create_time和update... 目录前言一、问题现象二、原因分析三、总结:常见原因与解决方法对照表四、推荐写法前言在使用 MyBATis

Mybatis Plus Join使用方法示例详解

《MybatisPlusJoin使用方法示例详解》:本文主要介绍MybatisPlusJoin使用方法示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,... 目录1、pom文件2、yaml配置文件3、分页插件4、示例代码:5、测试代码6、和PageHelper结合6

Redis过期删除机制与内存淘汰策略的解析指南

《Redis过期删除机制与内存淘汰策略的解析指南》在使用Redis构建缓存系统时,很多开发者只设置了EXPIRE但却忽略了背后Redis的过期删除机制与内存淘汰策略,下面小编就来和大家详细介绍一下... 目录1、简述2、Redis http://www.chinasem.cn的过期删除策略(Key Expir

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

Go语言中Recover机制的使用

《Go语言中Recover机制的使用》Go语言的recover机制通过defer函数捕获panic,实现异常恢复与程序稳定性,具有一定的参考价值,感兴趣的可以了解一下... 目录引言Recover 的基本概念基本代码示例简单的 Recover 示例嵌套函数中的 Recover项目场景中的应用Web 服务器中

MyBatis设计SQL返回布尔值(Boolean)的常见方法

《MyBatis设计SQL返回布尔值(Boolean)的常见方法》这篇文章主要为大家详细介绍了MyBatis设计SQL返回布尔值(Boolean)的几种常见方法,文中的示例代码讲解详细,感兴趣的小伙伴... 目录方案一:使用COUNT查询存在性(推荐)方案二:条件表达式直接返回布尔方案三:存在性检查(EXI

MyBatis编写嵌套子查询的动态SQL实践详解

《MyBatis编写嵌套子查询的动态SQL实践详解》在Java生态中,MyBatis作为一款优秀的ORM框架,广泛应用于数据库操作,本文将深入探讨如何在MyBatis中编写嵌套子查询的动态SQL,并结... 目录一、Myhttp://www.chinasem.cnBATis动态SQL的核心优势1. 灵活性与可