我们来说一下 Mybatis 的缓存机制

简介: 我是小假 期待与你的下一次相遇 ~


核心概览

  • 一级缓存:默认开启,作用范围在 同一个 SqlSession 内。
  • 二级缓存:需要手动配置开启,作用范围在 同一个 Mapper 命名空间(即同一个 Mapper 接口)内,可以被多个 SqlSession 共享。

一级缓存

1. 作用域

  • SqlSession 级别:当同一个 SqlSession 执行相同的 SQL 查询时,MyBatis 会优先从缓存中获取数据,而不是直接查询数据库。
  • 它是 默认开启 的,无法关闭,但可以配置其作用范围(SESSIONSTATEMENT)。

2. 工作机制

  1. 第一次执行查询后,查询结果会被存储到 SqlSession 关联的一级缓存中。
  2. 在同一个 SqlSession 中,再次执行 完全相同的 SQL 查询(包括语句和参数)时,会直接返回缓存中的对象,而不会去数据库查询。
  3. 如果 SqlSession 执行了 增(INSERT)、删(DELETE)、改(UPDATE) 操作,或者调用了 commit()close()rollback() 方法,该 SqlSession 的一级缓存会被清空。这是为了防止读取到脏数据。

3. 示例说明

// 假设获取的 SqlSession 和 UserMapper
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    // 第一次查询,会发送 SQL 到数据库
    User user1 = mapper.selectUserById(1L);
    System.out.println(user1);
    // 第二次查询,SQL 和参数完全相同,直接从一级缓存返回,不查询数据库
    User user2 = mapper.selectUserById(1L);
    System.out.println(user2);
    // 判断是否为同一个对象(是,因为从缓存中返回的是同一个对象的引用)
    System.out.println(user1 == user2); // 输出:true
    // 执行一个更新操作
    mapper.updateUser(user1);
    // 此时,一级缓存被清空
    // 第三次查询,因为缓存被清空,会再次发送 SQL 到数据库
    User user3 = mapper.selectUserById(1L);
    System.out.println(user3 == user1); // 输出:false (虽然是同一条数据,但已是新对象)
}

image.gif

4. 注意事项

  • 对象相同:一级缓存返回的是 同一个对象的引用,因此在同一个 SqlSession 内,你操作的都是同一个 Java 对象。
  • 分布式环境:一级缓存无法在多个应用服务器之间共享,因为它绑定在单个请求的 SqlSession 上。

二级缓存

1. 作用域

  • Mapper 级别 / Namespace 级别:多个 SqlSession 在访问同一个 Mapper 的查询时,可以共享其缓存。
  • 它是 默认关闭 的,需要在全局配置中开启,并在具体的 Mapper XML 中显式配置。

2. 开启与配置

a. 全局配置文件 (mybatis-config.xml)

必须显式设置开启二级缓存(虽然默认是 true,但显式声明是个好习惯)。

<configuration>
<settings>
<!-- 开启全局二级缓存,默认就是 true,但建议写明 -->
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>

image.gif

b. Mapper XML 文件

在需要开启二级缓存的 Mapper.xml 中添加 <cache/> 标签。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<!-- 开启本 Mapper 的二级缓存 -->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<!-- 其他 SQL 定义 -->
<select id="selectUserById" parameterType="long" resultType="User" useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>

image.gif

  • <cache/> 标签属性:
  • eviction:缓存回收策略。
  • LRU(默认):最近最少使用。
  • FIFO:先进先出。
  • SOFT:软引用,基于垃圾回收器状态和软引用规则移除。
  • WEAK:弱引用,更积极地移除。
  • flushInterval:缓存刷新间隔(毫秒),默认不清空。
  • size:缓存存放多少元素。
  • readOnly:是否为只读。
  • true:返回相同的缓存对象实例,性能好,但不允许修改。
  • false(默认):通过序列化返回缓存对象的拷贝,安全,性能稍差。

3. 工作机制

  1. 当一个 SqlSession 执行查询后,在关闭或提交时,其查询结果会被存入二级缓存。
  2. 另一个 SqlSession 执行相同的查询时,会先从二级缓存中查找数据。如果找到,则直接返回,否则再去数据库查询。
  3. 任何一个 SqlSession 执行了 增、删、改 操作并 commit() 后,会清空 整个对应 Mapper 的二级缓存,以保证数据一致性。

4. 示例说明

// 第一个 SqlSession
try (SqlSession sqlSession1 = sqlSessionFactory.openSession()) {
    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    User user1 = mapper1.selectUserById(1L); // 查询数据库
    sqlSession1.close(); // 关闭时,数据存入二级缓存
}
// 第二个 SqlSession(与第一个不同)
try (SqlSession sqlSession2 = sqlSessionFactory.openSession()) {
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    // 查询相同的 SQL,直接从二级缓存获取,不查询数据库
    User user2 = mapper2.selectUserById(1L);
}
// 第三个 SqlSession,执行了更新
try (SqlSession sqlSession3 = sqlSessionFactory.openSession()) {
    UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
    User user = mapper3.selectUserById(1L);
    user.setName("New Name");
    mapper3.updateUser(user); // 执行更新
    sqlSession3.commit(); // 提交时,清空 UserMapper 的二级缓存
}
// 第四个 SqlSession
try (SqlSession sqlSession4 = sqlSessionFactory.openSession()) {
    UserMapper mapper4 = sqlSession4.getMapper(UserMapper.class);
    // 因为缓存已被清空,所以会再次查询数据库
    User user4 = mapper4.selectUserById(1L);
}

image.gif

5. 注意事项

  • 实体类序列化:如果二级缓存的 readOnly="false",那么对应的实体类必须实现 Serializable 接口。
  • 事务提交:只有在 SqlSession 执行 commit()close() 时,数据才会从一级缓存转存到二级缓存。
  • 缓存粒度:二级缓存是 Mapper 级别的,有时会显得比较粗粒度。可以通过 <cache-ref> 让多个 Mapper 共享一个缓存,但不推荐,容易引起数据混乱。

缓存顺序与总结

当发起一个查询请求时,MyBatis 的缓存查询顺序是:

  1. 先查二级缓存:查看当前 Mapper 的二级缓存中是否有数据。
  2. 再查一级缓存:如果二级缓存没有,再查看当前 SqlSession 的一级缓存中是否有数据。
  3. 最后查数据库:如果两级缓存都没有,才发送 SQL 语句到数据库执行查询。

查询到的数据会 先存入一级缓存,在 SqlSession 关闭或提交时,再转存到二级缓存

特性

一级缓存

二级缓存

作用域

SqlSession

Mapper (Namespace)

默认状态

开启

关闭

是否共享

否,Session 独享

是,跨 Session 共享

清空时机

UPDATE/INSERT/DELETE, commit(), close()

同 Mapper 的 UPDATE/INSERT/DELETE + commit()

使用建议

  • 查询多,修改少的数据适合使用二级缓存,如字典表、配置项。
  • 数据实时性要求高的场景(如交易、订单)应谨慎使用二级缓存,或者设置较短的刷新间隔。
  • 在分布式环境中,默认的二级缓存(基于内存)是无法共享的,需要集成 Redis、Ehcache 等第三方缓存中间件来替代。
  • 理解缓存机制有助于解决一些“诡异”的问题,比如在同一个事务中,先后查询和更新,但由于一级缓存的存在,后续查询可能看不到其他线程的更新。


相关文章
|
28天前
|
JavaScript 安全 前端开发
智能随访系统源码,如何使用Java Spring Boot,Vue,Ant Design快速开发一套医院随访系统
基于Spring Boot + Vue + Ant Design Vue技术栈开发的医疗随访系统,涵盖患者管理、随访计划与执行、统计报表及系统管理模块。前后端分离架构,支持多渠道随访,数据安全可控,具备良好的扩展性与开发效率。
108 0
|
Kubernetes Docker 容器
Docker 与 K8S学习笔记(番外篇)—— 搭建本地私有Docker镜像仓库
我们在学习K8S时会有个问题,那就是我自己做的应用镜像如何在K8S中部署呢?如果我们每做一个镜像都要推送到公共镜像仓库那未免太麻烦了,这就需要我们搭一个私有镜像仓库,通过私有仓库,K8S集群便可以从中拉取镜像了。 一、拉取并部署docker register 私有镜像仓库部署也很简单,Docker
1638 0
|
27天前
|
移动开发 数据可视化 安全
数字孪生云渲染终极指南(一):什么是实时云渲染?
将三维可视化程序上云可以有效解决上述问题。通常可采用WebGL、HTML5、threejs、虚拟化、云桌面,以及云渲染等各类技术方案,实现客户端与服务端的分离,从一定程度上降低客户端硬件配置要求,提高程序兼容性。实时云渲染技术是目前最先进的云化技术之一,也是数字孪生三维可视化场景商业模式破局的关键。
277 155
数字孪生云渲染终极指南(一):什么是实时云渲染?
|
26天前
|
弹性计算 运维 安全
阿里云轻量服务器是什么?200M带宽配置价格清单,轻量详细介绍
阿里云轻量应用服务器(Simple Application Server)是面向个人开发者及中小企业的轻量级云服务,适用于网站建设、开发测试、小程序后端等场景。采用套餐式售卖,不支持自定义CPU、内存等配置,内置WordPress、宝塔面板、LNMP等应用镜像,支持一键部署。默认200M峰值带宽,最低38元/年起,操作简单、成本低,是快速搭建应用的理想选择。
362 11
|
26天前
|
数据采集 前端开发 Java
职责分离的艺术:剖析主从Reactor模型如何实现极致的并发性能
Reactor单线程模型中,I/O操作由单一线程处理,但业务逻辑若同步执行会阻塞线程,影响性能。为此,引入工作者线程池模型,将非I/O任务剥离至独立线程池,提升响应速度。进一步发展为主从多线程模型:MainReactor处理连接建立,SubReactor多线程管理读写,并结合过滤器链实现数据预处理,异步编程提升并发效率。该架构职责分明、扩展性强,广泛应用于Netty等高性能框架,支持百万级并发。
150 11
|
Web App开发
修改chrome插件
背景 例子为:ModHeader插件,顾名思义可以修改request header的插件,官方地址为:https://chrome.google.com/webstore/detail/modheader/idgpnmonknjnojddfkpgkljpfnnfcklj 研发通过新增/修改req...
3750 0
|
Linux Perl
Centos8 yum源配置方法
本文介绍了Centos8 版本中yum的配置
11450 30
Centos8 yum源配置方法
|
6月前
|
存储 JSON Java
你会不会5种牛犇的yml文件读取方式?
我是小假 期待与你的下一次相遇 ~
157 5
|
安全 Linux 编译器
Centos 7.9如何使用源码编译安装curl最新版本
通过上述步骤,您就能在CentOS 7.9上成功地从源代码编译并安装curl的最新版本。这种方法不仅提供了灵活性,允许您定制编译选项,还确保了软件的最新功能和安全更新得到应用。
827 2