详细介绍Mybatis的缓存机制

news/2025/4/1 8:13:14/文章来源:https://www.cnblogs.com/jock766/p/18799066

一、缓存机制


1、缓存概述

缓存:缓存就是一块内存空间,保存临时数据

作用:将数据源(数据库或者文件)中的数据读取出来存放到缓存中,再次获取时直接从缓存中获取,可以减少和数据库交互的次数,提升程序的性能


缓存适用:

  • 适用于缓存的:经常查询但不经常修改的,数据的正确与否对最终结果影响不大的

  • 不适用缓存的:经常改变的数据 , 敏感数据(例如:股市的牌价,银行的汇率,银行卡里面的钱)等等


缓存类别:

  • 一级缓存:SqlSession 级别的缓存,又叫本地会话缓存,自带的(不需要配置),一级缓存的生命周期与 SqlSession 一致。在操作数据库时需要构造 SqlSession 对象,在对象中有一个数据结构(HashMap)用于存储缓存数据,不同的 SqlSession 之间的缓存数据区域是互相不影响的

  • 二级缓存:mapper(namespace)级别的缓存,二级缓存的使用,需要手动开启(需要配置)。多个 SqlSession 去操作同一个 Mapper 的 SQL 可以共用二级缓存,二级缓存是跨 SqlSession


开启缓存:配置核心配置文件中标签

  • cacheEnabled:true 表示全局性地开启所有映射器配置文件中已配置的任何缓存,默认 true


二、一级缓存

一级缓存是 SqlSession 级别的缓存

工作流程:第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息,得到用户信息,将用户信息存储到一级缓存中;


第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息


一级缓存的失效:

  • SqlSession 不同

  • SqlSession 相同,查询条件不同时(还未缓存该数据)

  • SqlSession 相同,手动清除了一级缓存,调用 sqlSession.clearCache()

  • SqlSession 相同,执行 commit 操作或者执行插入、更新、删除,清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读


Spring 整合 MyBatis 后,一级缓存作用:

  • 未开启事务的情况,每次查询 Spring 都会创建新的 SqlSession,因此一级缓存失效

  • 开启事务的情况,Spring 使用 ThreadLocal 获取当前资源绑定同一个 SqlSession,因此此时一级缓存是有效的


测试示例:

我们在同一个SqlSession整两个一样的查询,并用分隔符分开,执行查看结果


结果:

看分割线上是第一次查询,所以开始缓存数量为0,之后缓存数量为1,并缓存了一条数据,第二次查询直接就命中了缓存并返回了结果


三、二级缓存


1、基本介绍


定义:二级缓存是 mapper 的缓存,只要是同一个命名空间(namespace)的 SqlSession 就共享二级缓存的内容,并且可以操作二级缓存


作用:作用范围是整个应用,可以跨线程使用,适合缓存一些修改较少的数据


工作流程:一个会话查询数据,这个数据就会被放在当前会话的一级缓存中,如果会话关闭 或 提交一级缓存中的数据会保存到二级缓存


二级缓存的基本使用:


1、在 MyBatisConfig.xml 文件开启二级缓存,cacheEnabled 默认值为 true,所以这一步可以省略不配置

<!--配置开启二级缓存-->
<settings><setting name="cacheEnabled" value="true"/>
</settings>


2、配置 Mapper 映射文件, 标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值

<mapper namespace="dao.UserDao"><!--开启user支持二级缓存--><cache eviction="FIFO" flushInterval="6000" readOnly="" size="1024"/><cache></cache> <!--则表示所有属性使用默认值-->
</mapper>


eviction(清除策略):

  • LRU、最近最少使用:移除最长时间不被使用的对象,默认

  • FIFO、先进先出:按对象进入缓存的顺序来移除它们

  • SOFT、软引用:基于垃圾回收器状态和软引用规则移除对象

  • WEAK、弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象


flushInterval(刷新间隔):可以设置为任意的正整数, 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新


size(引用数目):缓存存放多少元素,默认值是 1024


readOnly(只读):可以被设置为 true 或 false

  • 只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,促进了性能提升

  • 可读写的缓存会(通过序列化)返回缓存对象的拷贝, 速度上会慢一些,但是更安全,因此默认值是 false


type:指定自定义缓存的全类名,实现 Cache 接口即可


3、要进行二级缓存的类必须实现 java.io.Serializable 接口,可以使用序列化方式来保存对象

  public class User implements Serializable{}


2、相关属性


a、select 标签的 useCache 属性


映射文件中的 select 标签中设置 useCache="true" 代表当前 statement 要使用二级缓存(默认)


注意:如果每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存

  <select id="findAll" resultType="user" useCache="true">select * from user</select>


b、每个增删改标签都有 flushCache 属性,默认为 true,代表在执行增删改之后就会清除一、二级缓存,保证缓存的一致性;而查询标签默认值为 false,所以查询不会清空缓存


c、localCacheScope:本地缓存作用域, 中的配置项,默认值为 SESSION,当前会话的所有数据保存在会话缓存中,设置为 STATEMENT 禁用一级缓存


3、源码解析

事务提交二级缓存才生效:DefaultSqlSession 调用 commit() 时会回调 executor.commit()

  • CachingExecutor#query():执行查询方法,查询出的数据会先放入 entriesToAddOnCommit 集合暂存

     // 从二缓存中获取数据,获取不到去一级缓存获取List<E> list = (List<E>) tcm.getObject(cache, key);if (list == null) {// 回调 BaseExecutor#querylist = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);// 将数据放入 entriesToAddOnCommit 集合暂存,此时还没放入二级缓存tcm.putObject(cache, key, list);}
    
  • commit():事务提交,清空一级缓存,放入二级缓存,二级缓存使用 TransactionalCacheManager(tcm)管理

     public void commit(boolean required) throws SQLException {// 首先调用 BaseExecutor#commit 方法,【清空一级缓存】delegate.commit(required);tcm.commit();}
    
  • TransactionalCacheManager#commit:将查询出的数据放入二级缓存

     public void commit() {// 获取所有的缓存事务,挨着进行提交for (TransactionalCache txCache : transactionalCaches.values()) {txCache.commit();}}public void commit() {if (clearOnCommit) {delegate.clear();}// 将 entriesToAddOnCommit 中的数据放入二级缓存flushPendingEntries();// 清空相关集合reset();}private void flushPendingEntries() {for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {// 将数据放入二级缓存delegate.putObject(entry.getKey(), entry.getValue());}}
    

增删改操作会清空缓存:

  • update():CachingExecutor 的更新操作

    public int update(MappedStatement ms, Object parameterObject) throws SQLException {flushCacheIfRequired(ms);// 回调 BaseExecutor#update 方法,也会清空一级缓存return delegate.update(ms, parameterObject);
    }
    
  • flushCacheIfRequired():判断是否需要清空二级缓存

     private void flushCacheIfRequired(MappedStatement ms) {Cache cache = ms.getCache();// 判断二级缓存是否存在,然后判断标签的 flushCache 的值,增删改操作的 flushCache 属性默认为 trueif (cache != null && ms.isFlushCacheRequired()) {// 清空二级缓存tcm.clear(cache);}}
    


4、自定义

自定义缓存

  <cache type="com.domain.something.MyCustomCache"/>

type 属性指定的类必须实现 org.apache.ibatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器

  public interface Cache {String getId();int getSize();void putObject(Object key, Object value);Object getObject(Object key);boolean hasKey(Object key);Object removeObject(Object key);void clear();}

缓存的配置,只需要在缓存实现中添加公有的 JavaBean 属性,然后通过 cache 元素传递属性值,例如在缓存实现上调用一个名为 setCacheFile(String file) 的方法:

<cache type="com.domain.something.MyCustomCache"><property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>
  • 可以使用所有简单类型作为 JavaBean 属性的类型,MyBatis 会进行转换。

  • 可以使用占位符(如 ${cache.file}),以便替换成在配置文件属性中定义的值


MyBatis 支持在所有属性设置完毕之后,调用一个初始化方法, 如果想要使用这个特性,可以在自定义缓存类里实现 org.apache.ibatis.builder.InitializingObject 接口

  public interface InitializingObject {void initialize() throws Exception;}


注意:对缓存的配置(如清除策略、可读或可读写等),不能应用于自定义缓存


对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新,在多个命名空间中共享相同的缓存配置和实例,可以使用 cache-ref 元素来引用另一个缓存

 <cache-ref namespace="com.someone.application.data.SomeMapper"/>


测试案例一:

我们在一个SqlSession中放了三个一样的查询

结果:

执行顺序是先二级再一级没错,但是你会发现之后的查询依旧没走二级缓存而是走了一级缓存,表示二级缓存中没查到

为啥会这样?不是都放进缓存了吗?


二级缓存不是有结果后立刻存储的,而是在事务commit之后才会存储,所以查不到


测试案例二:

与上面相反用了两个SqlSession

结果:

很明显第二个查询走了二级缓存,这也正好印证了上面我们说的


5、缓存执行顺序:

二级缓存 --> 一级缓存 ---> 数据库


总结

  • 一级缓存在BaseExecutor中,作用域是SqlSession

  • 二级缓存在CachingExecutor中,作用域是namespace

  • 两者都会在修改操作时删除缓存,事务回滚时清除缓存

  • 执行顺序为:二级缓存 --> 一级缓存 ---> 数据库

  • 一级缓存默认开启,二级缓存默认关闭

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/907396.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JS梳理之es6异步async await 协程 迭代器

es6异步 promise 链式调用 是对回调炼狱的一种优化 这次梳理一下async await async function fetchData() {const response = await fetch(https://api.example.com/data);const data = await response.json();return data; }// async 声明这是一个异步函数 // await 会暂停函数…

百度云同步盘 登录失败【%d】【155010】

前言全局说明百度云同步盘在2016前后升级了一次,修改了接口,但是没有发布完整安装包,当时可以自动升级来解决,后来自动升级失效,就之能手动打补丁来解决了。详细解决过程:https://www.cnblogs.com/wutou/p/18799043一、说明 1.1 环境: Windows 11 家庭版 23H2 22631.3737二…

grpc实现Aop

创建项目服务端:微软官方自带的ASP.NET.Core.gRPC服务项目。 客户端:ASP.NET.Core.WebApi项目。 公共类库:主要为AOP自定义拦截器类。依赖包导入 客户端:Grpc.AspNetCore、Grpc.Core.Api、Grpc.Net.ClientFactory、Grpc.Tools。公共类库:Grpc.Core.Api公共类库项目配置 创…

Win 11 安装百度云同步盘

前言全局说明百度网盘最早出来叫“百度云管家”,可以上传下载东西,后来大概在2012年,百度云同步盘上线,后来将云管家和同步盘放到一起,叫百度云,也就是现在用的这个。下面介绍 如何在 Win11上用百度云同步盘一、说明 1.1 环境: Windows 11 家庭版 23H2 22631.3737二、下载…

C语言打卡学习第7天(2025.3.26)(补发)

![](https://img2024.cnblogs.com/blog/3622651/202503/3622651- 20250329002951976-79699626.jpg)1.换个网站把题简单做了几道 2,把积存的问题好好问了一下,明天“亡羊补牢”:冒泡排序、数组指针简单用法、之前网站的简单题 明天贪一点,起码把原来网站那些题啃了

VGG

VGG 网络LRN(Local Response Normalization)来自于AlexNet现在已经不怎么使用,因为经过很多实验并没有较大的作用 conv的stride为1,padding为1 maxpool的size为2,stride为2感受野叠加 论文中一个比较重要的使用就是感受野的叠加 感受野(Receptive Field)是卷积神经网络中一…

日语声调

日语声调的记忆 方法1方法2方法3日语声调的标记方法 方法1:划线规律1:第1个音 和 第2个音不是同音 规律2:出现降音就不会升回去 规律3:“高-低”在第几个音出现,就是几型 方法2:数字

荧光灯下的“绚烂”

“绚烂”这一可以令人愉悦的词汇,在航空发动机研制过程中,却给人另外的意味。荧光检测荧光检测 荧光检测是一种在零件表面进行的无损检测,可用于检测航空发动机零部件因疲劳、撞击、机加、淬火、锻造、铸造过载等因素造成的各种裂纹、接缝等表面缺陷。 当荧光检测应用在航…

课堂里的人工智能,或者说,狂野西部闯进了教育界

诺米科托博士(Normi Coto, PhD)配图来自 Unsplash 的 Element5 Digital3 月 15 日星期六,我参加了一场名为“人文学科中的 AI”的职业发展工作坊。会场人满为患,坐满了来自弗吉尼亚州中学和高中的英语和历史老师。来自弗吉尼亚大学和朗伍德大学的教授主持了这次工作坊,主题…

LED数码管显示独立按键次数

前言 目标 2个独立按键,按下K1,数码管显示的数字加1 按下K2,数码管显示的数字减1 效果 https://www.bilibili.com/video/BV1aXo9YxEhY原理独立按键,用于控制数字的加减把完整的数字,分成若干数位显示构造一个函数 show_digit(pos,digit) , 可以在指定位置(0<=pos<=7…

Bitcoin部署到openEuler RISC-V

Bitcoin项目源码是用C++写的,我对C++以及它的编译工具又比较熟悉,这次我尝试了在openEuler RISC-V 24.09上面部署Bitcoin。网上编译Bitcoin源码的很多都是以前旧版的,旧版编译是用automake之类的工具,但是在最新版只需要用cmake就行,两者的部署方式不相同,我分别记录一下…

NVIDIA安装程序无法继续

原因 在更新驱动时,手贱,下驱动一半关闭了下载流程。导致下载失败,而且进入不了Geforece 解决方法:官网下载最新版驱动,再尝试。 如果不行,检查Windows更新,更新至最新版本 重启后如果不可行,再关闭杀毒软件,关闭防火墙,再尝试,重启再尝试。 如果还是不行,使用卸载…