【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中$与#的区别解析》文章浏览阅读314次,点赞4次,收藏6次。MyBatis使用#{}作为参数占位符时,会创建预处理语句(PreparedStatement),并将参数值作为预处理语句... 目录一、介绍二、sql注入风险实例一、介绍#(井号):MyBATis使用#{}作为参数占位符时,会

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

MyBatis-Plus 中 nested() 与 and() 方法详解(最佳实践场景)

《MyBatis-Plus中nested()与and()方法详解(最佳实践场景)》在MyBatis-Plus的条件构造器中,nested()和and()都是用于构建复杂查询条件的关键方法,但... 目录MyBATis-Plus 中nested()与and()方法详解一、核心区别对比二、方法详解1.and()

Spring事务传播机制最佳实践

《Spring事务传播机制最佳实践》Spring的事务传播机制为我们提供了优雅的解决方案,本文将带您深入理解这一机制,掌握不同场景下的最佳实践,感兴趣的朋友一起看看吧... 目录1. 什么是事务传播行为2. Spring支持的七种事务传播行为2.1 REQUIRED(默认)2.2 SUPPORTS2

MySQL中的锁机制详解之全局锁,表级锁,行级锁

《MySQL中的锁机制详解之全局锁,表级锁,行级锁》MySQL锁机制通过全局、表级、行级锁控制并发,保障数据一致性与隔离性,全局锁适用于全库备份,表级锁适合读多写少场景,行级锁(InnoDB)实现高并... 目录一、锁机制基础:从并发问题到锁分类1.1 并发访问的三大问题1.2 锁的核心作用1.3 锁粒度分

Redis的持久化之RDB和AOF机制详解

《Redis的持久化之RDB和AOF机制详解》:本文主要介绍Redis的持久化之RDB和AOF机制,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述RDB(Redis Database)核心原理触发方式手动触发自动触发AOF(Append-Only File)核

PostgreSQL中MVCC 机制的实现

《PostgreSQL中MVCC机制的实现》本文主要介绍了PostgreSQL中MVCC机制的实现,通过多版本数据存储、快照隔离和事务ID管理实现高并发读写,具有一定的参考价值,感兴趣的可以了解一下... 目录一 MVCC 基本原理python1.1 MVCC 核心概念1.2 与传统锁机制对比二 Postg

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

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

MyBatis ResultMap 的基本用法示例详解

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