MyBatis之缓存机制详解

    • 一、MyBatis缓存的基本概念
      • 1.1 缓存的核心价值
      • 1.2 MyBatis的两级缓存体系
    • 二、一级缓存(SqlSession级别缓存)
      • 2.1 工作原理
      • 2.2 实战案例:一级缓存演示
        • 2.2.1 基础用法(默认开启)
        • 2.2.2 一级缓存失效场景
      • 2.3 一级缓存的特点与适用场景
    • 三、二级缓存(Mapper级别缓存)
      • 3.1 工作原理
      • 3.2 二级缓存的开启与配置
        • 3.2.1 全局配置(可选)
        • 3.2.2 Mapper接口开启缓存
        • 3.2.3 实体类序列化(必须)
      • 3.3 实战案例:二级缓存演示
      • 3.4 二级缓存的特点与适用场景
    • 四、二级缓存的高级配置
      • 4.1 禁用特定查询的二级缓存
      • 4.2 强制刷新二级缓存
      • 4.3 整合第三方缓存(如Redis)
        • 4.3.1 引入依赖(Redis+MyBatis-Redis)
        • 4.3.2 配置Redis缓存
    • 五、缓存使用的常见问题与避坑指南
      • 5.1 一级缓存导致的脏读问题
      • 5.2 二级缓存的序列化问题
      • 5.3 缓存与事务的一致性问题
      • 5.4 过度使用缓存导致内存溢出

缓存是提升数据库查询性能的关键技术,MyBatis内置了两级缓存机制,能有效减少重复查询的数据库交互,降低数据库压力。

一、MyBatis缓存的基本概念

1.1 缓存的核心价值

数据库查询是应用性能的常见瓶颈(磁盘IO比内存IO慢10^6倍以上),缓存通过将频繁查询的结果存储在内存中,避免重复访问数据库,从而:

  • 减少数据库连接和SQL执行次数;
  • 降低数据库服务器压力;
  • 提升应用响应速度(从内存读取比数据库查询快100倍以上)。

1.2 MyBatis的两级缓存体系

MyBatis提供两级缓存,工作流程如下:

  1. 一级缓存(SqlSession级别):默认开启,缓存当前会话(SqlSession)的查询结果;
  2. 二级缓存(Mapper级别):需手动开启,缓存Mapper接口的查询结果,可被多个SqlSession共享。

查询数据时,MyBatis的缓存查询顺序:

二级缓存 → 一级缓存 → 数据库

即先查二级缓存,若未命中则查一级缓存,仍未命中才查询数据库。

二、一级缓存(SqlSession级别缓存)

一级缓存是MyBatis的默认缓存,绑定到SqlSession(会话),生命周期与SqlSession一致。

2.1 工作原理

  • 缓存范围:每个SqlSession拥有独立的一级缓存,不同SqlSession的缓存互不影响;
  • 缓存时机SqlSession执行select查询后,会将结果存入一级缓存;
  • 命中条件:相同的Mapper方法+相同的参数+相同的SQL
  • 失效场景SqlSession执行insert/update/delete(会清空当前SqlSession的一级缓存)、SqlSession关闭或提交。

2.2 实战案例:一级缓存演示

2.2.1 基础用法(默认开启)
// 获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);// 第一次查询(未命中缓存,查询数据库)
User user1 = userMapper.selectById(1); // 第二次查询(相同SqlSession+相同参数,命中一级缓存,不查数据库)
User user2 = userMapper.selectById(1); System.out.println(user1 == user2); // true(同一对象,从缓存获取)sqlSession.close(); // 关闭会话,一级缓存失效
2.2.2 一级缓存失效场景
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);User user1 = userMapper.selectById(1); // 执行更新操作(insert/update/delete),清空一级缓存
userMapper.updateAge(1, 25); 
sqlSession.commit(); // 提交事务(触发缓存清空)// 再次查询(缓存已清空,重新查询数据库)
User user2 = userMapper.selectById(1); 
System.out.println(user1 == user2); // false(不同对象,从数据库获取)

2.3 一级缓存的特点与适用场景

特点说明
默认开启无需配置,开箱即用
会话隔离不同SqlSession的缓存独立,避免数据冲突
自动管理增删改自动清空缓存,保证数据一致性

适用场景

  • 单会话内的频繁查询(如同一请求中多次查询相同用户信息);
  • 读多写少的场景(避免频繁查询数据库)。

三、二级缓存(Mapper级别缓存)

二级缓存是跨SqlSession的全局缓存,绑定到Mapper接口(同一Mapper的所有方法共享),需手动开启。

3.1 工作原理

  • 缓存范围:同一Mapper接口的所有SqlSession共享二级缓存;
  • 缓存时机SqlSession关闭(close())或提交(commit())后,一级缓存的结果会写入二级缓存;
  • 命中条件:相同的Mapper接口+相同的方法+相同的参数
  • 失效场景:Mapper接口执行insert/update/delete(会清空当前Mapper的二级缓存)。

3.2 二级缓存的开启与配置

3.2.1 全局配置(可选)

mybatis-config.xml中开启二级缓存(默认已开启,可省略):

<settings><setting name="cacheEnabled" value="true"/> <!-- 全局二级缓存开关 -->
</settings>
3.2.2 Mapper接口开启缓存

在需要使用二级缓存的Mapper XML中添加<cache>标签:

<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper"><!-- 开启二级缓存 --><cache eviction="LRU" <!-- 淘汰策略:LRU(最近最少使用) -->flushInterval="60000" <!-- 自动刷新间隔(毫秒,60秒) -->size="1024" <!-- 最大缓存条目 -->readOnly="false"/> <!-- 是否只读(false:缓存对象副本) --><!-- 查询语句(默认使用二级缓存) --><select id="selectById" resultType="User">SELECT id, username, age FROM user WHERE id = #{id}</select>
</mapper>

<cache>标签属性说明:

  • eviction:缓存淘汰策略(LRU:移除最近最少使用;FIFO:先进先出);
  • flushInterval:缓存自动刷新时间(毫秒,0表示不自动刷新);
  • size:最大缓存数量(过多会占用内存);
  • readOnlytrue(返回缓存对象本身,性能好但线程不安全);false(返回副本,安全但性能略低)。
3.2.3 实体类序列化(必须)

二级缓存可能将对象写入磁盘(如使用第三方缓存),因此实体类需实现Serializable接口:

// 实现Serializable接口
public class User implements Serializable {private Integer id;private String username;private Integer age;// getter/setter
}

3.3 实战案例:二级缓存演示

// 第一个SqlSession
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.selectById(1);
sqlSession1.close(); // 关闭会话,将一级缓存写入二级缓存// 第二个SqlSession
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
// 命中二级缓存(无需查询数据库)
User user2 = userMapper2.selectById(1); 
sqlSession2.close();System.out.println(user1 == user2); // false(二级缓存返回副本,readOnly=false时)
System.out.println(user1.getId().equals(user2.getId())); // true(数据一致)

3.4 二级缓存的特点与适用场景

特点说明
手动开启需要在Mapper中配置<cache>标签
跨会话共享同一Mapper的所有SqlSession可共享缓存
支持序列化可配置第三方缓存(如Redis)持久化缓存

适用场景

  • 多会话共享的高频查询(如商品分类、字典表等不常变化的数据);
  • 读多写少的场景(避免频繁更新导致缓存失效)。

四、二级缓存的高级配置

4.1 禁用特定查询的二级缓存

若某查询不需要使用二级缓存(如实时性要求高的数据),可通过useCache="false"禁用:

<select id="selectLatestOrder" resultType="Order" useCache="false">SELECT * FROM `order` ORDER BY create_time DESC LIMIT 1
</select>

4.2 强制刷新二级缓存

若需在查询时强制刷新缓存(忽略现有缓存,重新查询数据库并更新缓存),可使用flushCache="true"

<select id="selectUserWithForceRefresh" resultType="User" flushCache="true">SELECT * FROM user WHERE id = #{id}
</select>

4.3 整合第三方缓存(如Redis)

MyBatis的默认二级缓存是内存缓存(重启后失效),生产环境通常整合Redis等分布式缓存,实现缓存持久化和分布式共享。

4.3.1 引入依赖(Redis+MyBatis-Redis)
<dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-redis</artifactId><version>1.0.0-beta2</version>
</dependency>
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.7.0</version>
</dependency>
4.3.2 配置Redis缓存
<!-- UserMapper.xml:指定缓存实现为Redis -->
<cache type="org.mybatis.caches.redis.RedisCache"><property name="host" value="localhost"/> <!-- Redis主机 --><property name="port" value="6379"/> <!-- Redis端口 --><property name="timeout" value="30000"/> <!-- 超时时间 --><property name="expiration" value="3600000"/> <!-- 缓存过期时间(毫秒) -->
</cache>

优势

  • 缓存持久化(应用重启后缓存不丢失);
  • 分布式共享(多实例应用共享缓存);
  • 支持缓存过期策略(自动清理过期数据)。

五、缓存使用的常见问题与避坑指南

5.1 一级缓存导致的脏读问题

问题:多SqlSession场景下,一级缓存可能读取到旧数据。

// SqlSession1查询数据并缓存
SqlSession sqlSession1 = sqlSessionFactory.openSession();
User user1 = sqlSession1.getMapper(UserMapper.class).selectById(1);// SqlSession2更新数据并提交
SqlSession sqlSession2 = sqlSessionFactory.openSession();
sqlSession2.getMapper(UserMapper.class).updateAge(1, 26);
sqlSession2.commit();
sqlSession2.close();// SqlSession1再次查询(一级缓存未更新,读取到旧数据)
User user2 = sqlSession1.getMapper(UserMapper.class).selectById(1);
System.out.println(user2.getAge()); // 25(旧值,而非更新后的26)

解决方案

  • 避免长生命周期的SqlSession(如Web应用中,一个请求对应一个SqlSession);
  • 对实时性要求高的查询,禁用一级缓存(通过flushCache="true");
  • 使用二级缓存(跨SqlSession共享,更新后会同步)。

5.2 二级缓存的序列化问题

错误:实体类未实现Serializable接口,二级缓存报错NotSerializableException

解决方案

  • 确保所有存入二级缓存的实体类实现Serializable接口;
  • 若使用第三方缓存(如Redis),需保证对象可被序列化(如避免循环引用)。

5.3 缓存与事务的一致性问题

问题:事务未提交时,其他SqlSession可能读取到未提交的缓存数据(脏读)。

原因

  • 一级缓存在事务内生效,未提交的更新不会刷新其他SqlSession的缓存;
  • 二级缓存仅在事务提交后才更新,避免此问题。

解决方案

  • 优先使用二级缓存(事务提交后才写入,保证数据一致性);
  • 关键业务(如支付)避免依赖缓存,直接查询数据库。

5.4 过度使用缓存导致内存溢出

问题:缓存大量数据(如全表查询结果),导致JVM内存溢出(OOM)。

解决方案

  • 限制缓存大小(size属性,如size="1024");
  • 设置缓存过期时间(flushInterval);
  • 避免缓存大集合(如分页查询,只缓存当前页数据);
  • 使用分布式缓存(如Redis),利用外部内存存储缓存。

总结:缓存机制的最佳实践
MyBatis的两级缓存各有适用场景,合理使用能显著提升性能,核心实践原则

  1. 一级缓存
  • 无需额外配置,充分利用其默认特性;
  • 注意SqlSession的生命周期(一个请求一个SqlSession),避免脏读;
  • 增删改操作后,一级缓存会自动清空,无需手动处理。
  1. 二级缓存
  • 仅对“读多写少、实时性要求低”的数据开启(如字典表、商品分类);
  • 必须实现实体类序列化,避免缓存失败;
  • 生产环境推荐整合Redis等分布式缓存,支持持久化和分布式共享;
  • 对实时性要求高的查询(如订单状态),禁用二级缓存。
  1. 通用原则
  • 缓存粒度越小越好(优先缓存单条数据,而非全表);
  • 避免缓存大对象和频繁变化的数据;
  • 结合监控工具(如MyBatis Log)分析缓存命中率,优化缓存策略。

若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ

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

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

相关文章

云服务器搭建自己的FRP服务。为什么客户端的项目需要用Docker启动,服务端才能够访问到?

简单回答&#xff1a;在云服务器搭建FRP服务时&#xff0c;客户端项目用Docker启动并非必需&#xff0c;而是因为Docker的特性简化了配置&#xff1a; Docker通过端口映射&#xff08;如-p 本地端口:容器端口&#xff09;能固定项目对外暴露的端口&#xff0c;减少本地端口冲突…

6 STM32单片机的智能家居安防系统设计(STM32代码+手机APP设计+PCB设计+Proteus仿真)

系列文章目录 文章目录 系列文章目录前言1 资料获取与演示视频1.1 资料介绍1.2 资料获取1.3 演示视频 2 系统框架3 硬件3.1 主控制器3.2 显示屏3.3 WIFI模块3.4 DHT11温湿度传感器3.5 烟雾/燃气传感器模块&#xff1a;MQ-23.6 火焰传感器3.7 门磁模块MC-38 4 设计PCB4.1 安装下…

DevOps落地的终极实践:8大关键路径揭秘!

本文来自腾讯蓝鲸智云社区用户: CanWay当前&#xff0c;DevOps因其能够降低IT运营成本、提高软件质量并加快上市时间的能力而在全球范围内引起广泛关注。它打破了传统软件开发与运营的界限&#xff0c;消除了新功能发布延迟和软件质量下降的障碍。DevOps通过实施持续集成、持续…

react - 根据路由生成菜单

后端返回菜单的格式menuList:[{index: true,name: "",component: "../views/Home",meta: { title: "首页", requiresAuth: true,roles:[user]},},{path: "/admin",name: "admin",meta: { title: "管理页", roles:…

Window延迟更新10000天配置方案

1.点击"开始"菜单&#xff0c;搜索"注册表编辑器"&#xff0c;点击"打开"。2.找到"\HKEY LOCAL MACHINE\SOFTWARE\Microsoft\WindowsUpdate\Ux\Settings"路径。3.右面空白处右键新建一个32位值&#xff0c;命名为FlightSettingsMaxPau…

【OD机试】人民币转换

题目描述 将阿拉伯数字金额转换为中文大写金额格式,需遵循以下规则: 1、 前缀要求:中文大写金额前必须标明“人民币”字样。 2、 用字规范:使用壹、贰、叁、肆、伍、陆、柒、捌、玖、拾、佰、仟、万、亿、元、角、分、零、整等字样。 3、 “整”字规则: 金额到“元”为止…

在ajax中什么时候需要将返回值类型做转换

$.ajax({url: TMSPROC0050/deleteData?accidentIds accidentIds.join(,),type: DELETE,dataType: json,success: function(result) {$(#accidentGrid).datagrid(reload);$.messager.show({title: 成功,msg: result.message})},error: function(result) {$.messager.alert({ti…

Helm常用命令大全(2025最新版)

文章目录Helm常用命令大全&#xff08;2025最新版&#xff09;一、基础命令与环境配置版本与帮助信息安装与升级HelmLinux系统安装版本升级注意事项二、仓库管理命令仓库基础操作OCI仓库支持&#xff08;v3.8新特性&#xff09;三、Chart操作命令Chart创建与打包Chart搜索与下载…

gitlab+jenkins

文章目录架构gitlab和jenkins安装jenkins配置gitlab配置jenkins与gitlab联动参考架构 gitlab和jenkins安装 部署docker 部署jenkins 启动jenkins 用户&#xff1a;admin&#xff0c;对应的密码如下 点击安装自定义推荐的插件 安装gitlab插件 jenkins配置 配置pipline…

Redis字符串操作指南:从入门到实战应用

Redis作为一款高性能的键值存储数据库&#xff0c;其字符串&#xff08;String&#xff09;类型是最基础也最常用的数据类型。它不仅能存储简单的文本信息&#xff0c;还能应对数字计算、二进制数据等多种场景&#xff0c;灵活且高效。接下来&#xff0c;我们就全方位剖析Redis…

SQLite 数据库字段类型-详细说明,数据类型详细说明。

SQLite 数据类型 SQLite字段类型详细说明&#xff0c;包含存储类、亲和类型、布尔类型、日期时间类型的存储方式、取值范围及核心特性。 创建 SQLite3 表时可使用的各种数据类型名称&#xff0c;同时也介绍了相应的亲和类型。 一、核心存储类&#xff08;Storage Classes&am…

Node.js特训专栏-实战进阶:17.会话管理与安全存储

🔥 欢迎来到 Node.js 实战专栏!在这里,每一行代码都是解锁高性能应用的钥匙,让我们一起开启 Node.js 的奇妙开发之旅! Node.js 特训专栏主页 专栏内容规划详情 会话管理与安全存储:从原理到实战的Web安全实践 在Web应用中,会话(Session)是维持用户状态的核心机制—…

【橘子分布式】gRPC(编程篇-中)

一、简介 我们之前已经完成了对于api模块的开发&#xff0c;也就是已经生成了基础的类和对应的接口&#xff0c;现在我们需要完成的是client和server端的开发。其实如同thrift一样&#xff0c;现在要做的就是实现我们之前定义的service里面的hello方法&#xff0c;里面写我们的…

Spring Boot 项目中数据同步之binlog和MQ

在 Spring Boot 项目中&#xff0c;“监听 binlog” 和 “业务代码中集成 MQ” 是实现数据同步、事件驱动的两种主流方法。 简单来说&#xff0c;这个选择可以概括为&#xff1a; 监听 Binlog (如使用 Canal)&#xff1a;像一个数据库的贴身秘书&#xff0c;它忠实地记录数据库…

MySQL 写入性能优化全攻略(附 GitHub 面试题项目链接)

面试中你可能会遇到这样的问题&#xff1a; &#x1f4ac; “假设你的接口一天收到百万级请求&#xff0c;MySQL 撑得住吗&#xff1f;你会怎么优化写入性能&#xff1f;” 刚开始我也懵过&#xff0c;后来不断复盘与总结&#xff0c;现在我可以用结构化方式给出一个相对完整的…

用Dynamic chunk去干掉tokenizer?

一般你们下AR模型的时候&#xff0c;都有这个&#xff0c;也就是tokenzier&#xff0c;tokenizer是干啥的&#xff0c;其实就是你的分词字典不光有specal的token对应的还有实际的对应的分词对应的代码&#xff0c;比如&#xff1a;也有tokenzier没显示的&#xff0c;比如&#…

Linux系统日志管理入门:journalctl命令完全指南

Linux系统日志管理入门&#xff1a;journalctl命令完全指南前言一、journalctl介绍二、基础使用&#xff1a;快速上手1. 查看全部日志2. 查看本次启动的日志3. 按时间筛选日志4. 按服务&#xff08;单元&#xff09;过滤日志三、常用参数与场景四、实战案例&#xff1a;解决实际…

神经网络的基本骨架——nn.Module的使用(torch.nn库)

在 PyTorch 中&#xff0c;nn.Module 是所有神经网络模块的基类&#xff0c;用于构建和组织深度学习模型。它提供了一系列工具和功能&#xff0c;使模型的定义、训练和部署更加高效和灵活。nn Neural Network&#xff08;神经网络&#xff09;核心作用&#xff1a;模块化设计&…

静态住宅IP和节点有什么区别?哪种更适合你的需求?

在跨境电商、社媒运营等业务中&#xff0c;“静态住宅IP”和“节点”常被混淆使用&#xff0c;但两者代表网络架构中不同层级的资源。选错可能导致账号风控、业务效率低下。IPdodo将在本篇文章中&#xff0c;从本质、业务场景到选择策略&#xff0c;为您一文道清两者之间的区别…

AI编程工具对比:Cursor、GitHub Copilot与Claude Code

文章目录AI编程工具对比&#xff1a;Cursor、GitHub Copilot与Claude Code一、产品定位与核心架构1.1 Cursor&#xff1a;AI原生IDE的代表1.2 GitHub Copilot&#xff1a;代码补全的行业标杆1.3 Claude Code&#xff1a;终端Agent的革新者二、核心功能深度对比2.1 代码生成与理…