【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

相关文章

破茧 JDBC:MyBatis 在 Spring Boot 中的轻量实践指南

《破茧JDBC:MyBatis在SpringBoot中的轻量实践指南》MyBatis是持久层框架,简化JDBC开发,通过接口+XML/注解实现数据访问,动态代理生成实现类,支持增删改查及参数... 目录一、什么是 MyBATis二、 MyBatis 入门2.1、创建项目2.2、配置数据库连接字符串2.3、入

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

Spring Security 单点登录与自动登录机制的实现原理

《SpringSecurity单点登录与自动登录机制的实现原理》本文探讨SpringSecurity实现单点登录(SSO)与自动登录机制,涵盖JWT跨系统认证、RememberMe持久化Token... 目录一、核心概念解析1.1 单点登录(SSO)1.2 自动登录(Remember Me)二、代码分析三、

MyBatis-Plus 自动赋值实体字段最佳实践指南

《MyBatis-Plus自动赋值实体字段最佳实践指南》MyBatis-Plus通过@TableField注解与填充策略,实现时间戳、用户信息、逻辑删除等字段的自动填充,减少手动赋值,提升开发效率与... 目录1. MyBATis-Plus 自动赋值概述1.1 适用场景1.2 自动填充的原理1.3 填充策略

Go语言并发之通知退出机制的实现

《Go语言并发之通知退出机制的实现》本文主要介绍了Go语言并发之通知退出机制的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、通知退出机制1.1 进程/main函数退出1.2 通过channel退出1.3 通过cont

Spring Boot 中的默认异常处理机制及执行流程

《SpringBoot中的默认异常处理机制及执行流程》SpringBoot内置BasicErrorController,自动处理异常并生成HTML/JSON响应,支持自定义错误路径、配置及扩展,如... 目录Spring Boot 异常处理机制详解默认错误页面功能自动异常转换机制错误属性配置选项默认错误处理

java如何实现高并发场景下三级缓存的数据一致性

《java如何实现高并发场景下三级缓存的数据一致性》这篇文章主要为大家详细介绍了java如何实现高并发场景下三级缓存的数据一致性,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 下面代码是一个使用Java和Redisson实现的三级缓存服务,主要功能包括:1.缓存结构:本地缓存:使

mybatis中resultMap的association及collectio的使用详解

《mybatis中resultMap的association及collectio的使用详解》MyBatis的resultMap定义数据库结果到Java对象的映射规则,包含id、type等属性,子元素需... 目录1.reusltmap的说明2.association的使用3.collection的使用4.总

Apache Ignite缓存基本操作实例详解

《ApacheIgnite缓存基本操作实例详解》文章介绍了ApacheIgnite中IgniteCache的基本操作,涵盖缓存获取、动态创建、销毁、原子及条件更新、异步执行,强调线程池注意事项,避免... 目录一、获取缓存实例(Getting an Instance of a Cache)示例代码:二、动态

mybatis-plus QueryWrapper中or,and的使用及说明

《mybatis-plusQueryWrapper中or,and的使用及说明》使用MyBatisPlusQueryWrapper时,因同时添加角色权限固定条件和多字段模糊查询导致数据异常展示,排查发... 目录QueryWrapper中or,and使用列表中还要同时模糊查询多个字段经过排查这就导致只要whe