《Mybatis》第10章 MyBatis 缓存机制深度剖析
第10章 MyBatis 缓存机制深度剖析缓存是持久层框架提升性能的核心手段。MyBatis 提供了两级缓存一级缓存SqlSession 级别和二级缓存Mapper 级别。本章将从使用、原理、源码和最佳实践四个维度深入剖析 MyBatis 的缓存机制帮助你在实际项目中合理使用缓存提升系统性能同时避免脏数据等陷阱。10.1 缓存架构总览MyBatis 的缓存体系结构清晰两级缓存各司其职核心设计一级缓存SqlSession 持有默认开启生命周期与 SqlSession 相同。二级缓存SqlSessionFactory 持有跨 SqlSession 共享需手动开启。Cache 接口所有缓存实现都遵循org.apache.ibatis.cache.Cache接口PerpetualCache是默认的 HashMap 实现。10.2 ⭐一级缓存详解10.2.1 什么是一级缓存一级缓存是SqlSession 级别的本地缓存。在同一个 SqlSession 中执行两次相同的查询第二次会直接从缓存返回不再查询数据库。代码验证try (SqlSession session sqlSessionFactory.openSession()) { UserMapper mapper session.getMapper(UserMapper.class); User user1 mapper.selectById(1L); // 执行 SQL查询数据库 User user2 mapper.selectById(1L); // 从一级缓存返回不执行 SQL System.out.println(user1 user2); // true同一对象 }日志输出Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connectioneb21112] Preparing: select * from user where id ? Parameters: 1(Long) Columns: id, username Row: 1, 张三 Total: 1 User{id1, username张三} User{id1, username张三} // 第二次无SQL日志10.2.2 一级缓存的存储位置一级缓存存储在BaseExecutor的localCache属性中类型为PerpetualCache其底层就是简单的HashMap。public abstract class BaseExecutor implements Executor { protected PerpetualCache localCache; // 一级缓存 protected BaseExecutor(Configuration configuration, Transaction transaction) { // 在构造方法中初始化 this.localCache new PerpetualCache(LocalCache); } }PerpetualCache源码极其简单本质是对 HashMap 的封装public class PerpetualCache implements Cache { private final MapObject, Object cache new HashMap(); Override public void putObject(Object key, Object value) { cache.put(key, value); } Override public Object getObject(Object key) { return cache.get(key); } }10.2.3 一级缓存的命中条件一级缓存要命中必须满足以下条件同一个 SqlSession必须。相同的 MappedStatement ID即同一个 Mapper 方法。相同的参数包括参数值和分页条件。相同的 RowBounds分页参数。相同的执行环境如数据库方言。MyBatis 会将这些条件组合成一个CacheKey对象作为缓存的 key。CacheKey 的生成规则CacheKey key new CacheKey(); key.update(ms.getId()); // MappedStatement ID key.update(rowBounds.getOffset()); // 分页偏移 key.update(rowBounds.getLimit()); // 分页大小 key.update(parameter); // 参数对象 key.update(environmentId); // 环境ID10.2.4 一级缓存的失效场景一级缓存不是永久有效的以下情况会清空或使缓存失效1、执行更新操作任何INSERT、UPDATE、DELETE都会清空一级缓存。User user1 mapper.selectById(1L); // 缓存 mapper.updateName(1L, 李四); // 更新操作 User user2 mapper.selectById(1L); // 再次查询数据库缓存已清空源码位置BaseExecutor.update()方法会调用clearLocalCache()。2、手动调用clearCache()sqlSession.clearCache(); // 主动清空一级缓存3、SqlSession 关闭会话关闭缓存自然销毁。4、查询参数不同不同的查询条件生成不同的CacheKey。4、设置localCacheScopeSTATEMENTMyBatis 允许配置一级缓存的作用域。setting namelocalCacheScope valueSTATEMENT/ !-- 默认为 SESSION --设置为STATEMENT后每次查询后都会清空一级缓存相当于只在一次查询内部有效。10.2.5 一级缓存的工作流程源码视角核心源码在BaseExecutor.query()方法public E ListE query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 1. 生成 CacheKey CacheKey key createCacheKey(ms, parameter, rowBounds, boundSql); // 2. 从一级缓存查询 ListE list (ListE) localCache.getObject(key); if (list ! null) { // 缓存命中直接返回 return list; } // 3. 缓存未命中查询数据库 list doQuery(ms, parameter, rowBounds, resultHandler, boundSql); // 4. 存入一级缓存 localCache.putObject(key, list); return list; }10.2.6 一级缓存的优缺点优点缺点默认开启无需配置零成本使用作用范围仅限单个 SqlSession无法跨会话共享在事务内重复查询性能提升明显分布式环境下无法共享数据减少数据库重复查询可能产生脏数据其他事务修改后本会话仍返回旧值实现简单基于 HashMap 无额外开销长时间会话可能占用内存10.2.7 一级缓存的最佳实践在 Spring 管理下一级缓存问题较少因为非事务方法每次都会获取新的 SqlSession一级缓存自然失效事务方法内如果需要避免脏读可合理控制事务粒度。对于实时性要求高的数据可设置flushCachetrueselect idselectById flushCachetrue resultTypeUser SELECT * FROM user WHERE id #{id} /select在长事务中注意缓存一致性问题长事务内可能因其他事务修改数据而读到旧值可考虑手动清空缓存或缩小事务范围。避免在同一个 SqlSession 中执行大量查询防止内存占用过高。10.3 ⭐二级缓存详解10.3.1 什么是二级缓存二级缓存是Mapper 级别namespace 级别的缓存可以被多个 SqlSession 共享。它的作用范围比一级缓存更大能够跨会话缓存数据。// 两个不同的 SqlSession相同的 Mapper SqlSession session1 sqlSessionFactory.openSession(); SqlSession session2 sqlSessionFactory.openSession(); UserMapper mapper1 session1.getMapper(UserMapper.class); UserMapper mapper2 session2.getMapper(UserMapper.class); User user1 mapper1.selectById(1L); // 查询数据库 session1.commit(); // 必须提交数据才会进入二级缓存 User user2 mapper2.selectById(1L); // 从二级缓存返回不查询数据库关键点二级缓存的数据是在SqlSession 提交或关闭时才会写入而不是查询后立即写入。10.3.2 二级缓存的配置二级缓存默认是开启的cacheEnabledtrue但需要显式配置cache/标签才能生效。1. 全局开关默认 true可省略settings setting namecacheEnabled valuetrue/ /settings2. Mapper 中启用缓存mapper namespacecom.xyz.mapper.UserMapper !-- 开启二级缓存 -- cache evictionLRU !-- 淘汰策略LRU默认、FIFO、SOFT、WEAK -- flushInterval60000 !-- 刷新间隔毫秒默认不设置即仅当更新时刷新 -- size512 !-- 最多缓存对象个数默认 1024 -- readOnlytrue !-- 是否只读默认 false -- blockingfalse/ !-- 是否阻塞默认 false -- select idselectById resultTypeUser useCachetrue SELECT * FROM user WHERE id #{id} /select /mapper配置参数详解参数说明默认值eviction淘汰策略LRU最近最少使用、FIFO先进先出、SOFT软引用、WEAK弱引用LRUflushInterval刷新间隔毫秒超过该时间自动清空缓存无仅更新时刷新size缓存对象个数上限1024readOnlytrue返回相同实例速度快但可能被修改false返回拷贝对象需序列化falseblockingtrue当缓存未命中时阻塞其他线程直到数据填充false3. 方法级别的缓存控制select idselectById useCachetrue flushCachefalse !-- useCache是否使用二级缓存默认 true -- !-- flushCache执行前是否清空二级缓存默认 false -- /select insert idinsert flushCachetrue/ !-- 更新操作默认 flushCachetrue -- update idupdate flushCachetrue/ delete iddelete flushCachetrue/10.3.3 二级缓存的工作流程关键源码CachingExecutor是Executor的装饰器在开启二级缓存时MyBatis 会使用它包装真正的 Executor。public class CachingExecutor implements Executor { private final Executor delegate; private final TransactionalCacheManager tcm new TransactionalCacheManager(); Override public E ListE query(MappedStatement ms, Object parameter, ...) throws SQLException { Cache cache ms.getCache(); // 获取二级缓存 if (cache ! null) { // 从二级缓存查询 ListE list tcm.getObject(cache, key); if (list null) { // 未命中委托给真实 Executor list delegate.query(ms, parameter, ...); // 暂存到事务缓存管理器提交时才写入 tcm.putObject(cache, key, list); } return list; } return delegate.query(ms, parameter, ...); } }10.3.4 二级缓存与事务二级缓存有一个容易被忽略的特性事务提交后才真正写入缓存。这是通过TransactionalCacheManager实现的。public class TransactionalCache { private final Cache delegate; private boolean clearOnCommit; // 提交时是否清空 private final MapObject, Object entriesToAddOnCommit; // 提交时添加的缓存项 public void commit() { if (clearOnCommit) { delegate.clear(); } // 提交时才真正写入二级缓存 delegate.putAll(entriesToAddOnCommit); } }这意味着查询后不提交事务数据不会进入二级缓存。事务回滚时暂存的缓存数据会被丢弃。10.3.5 二级缓存的优缺点优点缺点跨 SqlSession 共享应用级别缓存配置复杂需谨慎设计减少数据库压力提升整体性能可能产生脏数据尤其多表关联时支持第三方缓存Redis、Ehcache序列化/反序列化开销可配置淘汰策略、过期时间分布式环境下需额外处理同步10.3.6 二级缓存的核心问题脏数据二级缓存最大的陷阱是脏数据尤其在多表关联场景中。典型场景学生表student和班级表class关联查询。!-- StudentMapper.xml -- cache/ select idselectStudentWithClass resultMapstudentClassMap SELECT s.*, c.* FROM student s LEFT JOIN class c ON s.class_id c.id /select !-- ClassMapper.xml -- cache/ !-- 班级表也开启了缓存 -- update idupdateClassName UPDATE class SET name #{name} WHERE id #{id} /update问题StudentMapper查询学生班级信息结果被缓存在StudentMapper的 namespace 中。ClassMapper更新了班级名称。ClassMapper的更新清空了它自己的缓存但没有清空 StudentMapper 的缓存。后续StudentMapper查询返回旧班级名称 →脏数据。根源MyBatis 的二级缓存以 namespace 为单位无法感知跨 namespace 的数据变更。解决方案使用cache-ref共享缓存让关联的 Mapper 使用同一个缓存区域。!-- StudentMapper.xml -- cache-ref namespacecom.xyz.mapper.ClassMapper/ !-- 或者反过来让 ClassMapper 引用 StudentMapper --这样任何一方的更新都会清空共享缓存。1、避免在多表查询中使用二级缓存只对单表且极少变更的数据使用二级缓存。2、设置合理的过期时间即使有脏数据也能在一定时间后自动刷新。cache flushInterval60000/ !-- 60秒后自动过期 --3、使用第三方缓存实现更精细的控制如 Redis可以手动控制缓存键的清理。10.3.7 二级缓存与序列化当设置readOnlyfalse可读写缓存时缓存的对象必须实现 Serializable 接口。public class User implements Serializable { private static final long serialVersionUID 1L; // 建议显式指定 private Long id; private String name; // getter/setter }原因可读写缓存会在返回前对对象进行序列化拷贝防止多个会话修改同一缓存对象导致数据混乱。如果不实现 Serializable会抛出异常Error serializing object. Cause: java.io.NotSerializableException: com.xyz.entity.User注意事项实体类继承的父类也要实现 Serializable。显式指定serialVersionUID防止实体类变更后反序列化失败。如果使用 Spring Boot Devtools需要注意 ClassLoader 问题可能导致反序列化类型不匹配。10.3.8 二级缓存的最佳实践适用场景判断仅对读远大于写、数据量小、一致性要求不高的表使用二级缓存如字典表、配置表。关联表必须共享缓存如果表之间存在关联涉及这些表的 Mapper 应使用cache-ref共享同一个缓存区域。设置合理的过期时间即使业务上认为数据不变也应设置flushInterval防止极端情况下的脏数据长期存在。避免缓存大对象控制size参数防止内存溢出。理解事务行为二级缓存的数据在事务提交后才可见长事务可能影响其他会话看到最新数据。测试环境验证在生产环境开启二级缓存前务必在测试环境模拟多会话并发场景验证数据一致性。监控缓存命中率通过日志或监控工具观察二级缓存的命中率如果命中率过低20%应考虑关闭缓存或调整策略。10.4 两级缓存对比总结对比维度一级缓存二级缓存作用范围SqlSession 级别Mappernamespace级别跨 SqlSession默认状态开启无法关闭关闭需手动配置存储位置BaseExecutor 的 localCacheMappedStatement 的 Cache 对象生命周期随 SqlSession 结束而销毁随应用结束而销毁共享性同一会话内共享同一 Mapper 所有会话共享失效时机更新操作、手动清空、会话关闭更新操作、超时、手动清空、应用重启适用场景事务内重复查询读多写少、跨会话共享数据主要风险长事务脏读多表关联脏数据、内存溢出10.5 ⭐面试题1. 请详细解释 MyBatis 一级缓存和二级缓存的区别以及它们各自可能引发的问题。考察点对两级缓存的理解深度以及实际应用中可能遇到的问题。完整答复区别作用范围一级缓存是 SqlSession 级别仅在同一个会话内有效二级缓存是 Mapper 级别可以跨多个 SqlSession 共享。开启方式一级缓存默认开启且无法关闭二级缓存需要手动配置cache/标签。存储位置一级缓存在BaseExecutor的localCache中二级缓存在MappedStatement的Cache对象中由CachingExecutor管理。生命周期一级缓存随 SqlSession 关闭而销毁二级缓存随应用生命周期存在。写入时机一级缓存查询后立即写入二级缓存需要事务提交后才真正写入。一级缓存可能引发的问题脏读在同一个 SqlSession 中两次查询之间其他事务修改了数据并提交但本会话未执行更新操作第二次查询会返回缓存中的旧数据。内存占用长事务中查询了大量数据一级缓存一直持有这些对象可能导致内存压力。二级缓存可能引发的问题多表关联脏数据当多个 Mapper 操作同一张表时如果一个 Mapper 更新了数据只会清空自己的缓存其他 Mapper 的缓存中仍保留旧数据导致脏读。序列化开销可读写缓存需要对象序列化带来性能开销。内存溢出缓存对象过多或过大且淘汰策略配置不当可能导致 OOM。分布式一致性问题在分布式环境中多个应用节点之间的缓存需要同步否则会产生数据不一致。2. 二级缓存中readOnlytrue 和 readOnlyfalse 有什么区别如何选择考察点对二级缓存内部机制的理解。完整答复readOnlytrue只读缓存返回缓存对象的直接引用所有获取该缓存对象的会话都共享同一个实例。优点性能高无需序列化和拷贝。缺点如果某个会话修改了该对象setter 方法会导致其他会话获取到的对象也被修改可能引发数据混乱。适用场景缓存对象不会被修改或者即使被修改也无所谓如只用于展示的数据。readOnlyfalse可读写缓存返回缓存对象的序列化拷贝每次获取都会通过反序列化创建一个新对象。优点线程安全每个会话获得独立的对象副本互不影响。缺点有序列化/反序列化性能开销且要求对象必须实现Serializable接口。适用场景缓存对象可能被修改或者需要确保线程安全的场景。选择建议如果缓存对象是不可变对象如字典数据、配置数据且不会被修改可选用readOnlytrue提升性能。如果缓存对象可能被修改或不确定应选用readOnlyfalse并确保对象可序列化。在分布式缓存如 Redis中即使设置readOnlyfalse也要注意序列化一致性。3. 一级缓存和二级缓存同时存在时查询的执行顺序是怎样的事务未提交时数据会进入二级缓存吗考察点对缓存执行流程的掌握。完整答复查询执行顺序先查询二级缓存如果开启了。二级缓存未命中再查询一级缓存。一级缓存也未命中才查询数据库。查询结果先存入一级缓存。事务提交时一级缓存的数据才会被写入二级缓存由TransactionalCacheManager管理。事务与二级缓存的关系事务未提交时数据不会进入二级缓存。这是通过TransactionalCache实现的它会在事务提交时才将暂存的缓存项写入真正的二级缓存。如果事务回滚暂存的缓存数据会被丢弃不会污染二级缓存。这意味着即使在同一个事务中多次查询如果事务未提交其他会话无法看到该事务产生的二级缓存数据。设计意义保证二级缓存的事务隔离性防止未提交事务的数据被其他会话读到避免脏读。

相关新闻

stm32f103c8t6呼吸灯

stm32f103c8t6呼吸灯

江科大的例子

2026/7/4 4:59:33 阅读更多 →
Transformer

Transformer

Transformer 定义: Transformer是一种更现代的深度学习模型,专为处理序列数据而设计,最初用于自然语言处理任务。它不依赖于RNN或CNN等传统结构,而是引入了注意力机制。 结构: Transformer模型主要由编码器和解码器组…

2026/7/4 9:49:29 阅读更多 →
烽火HG680-KF/KE/KD/移动高清盒子7A 通用强刷固件及保姆级救砖教程(海思MV320芯片)

烽火HG680-KF/KE/KD/移动高清盒子7A 通用强刷固件及保姆级救砖教程(海思MV320芯片)

摘要: 本文提供适用于烽火HG680-KF、HG680-KE、HG680-KD以及移动高清盒子7A的通用刷机固件。该方案基于海思Hi3798MV320芯片,支持安卓9.0系统。教程详细介绍了U盘强刷(短接)的方法,适用于解决机顶盒卡顿、开机卡死、系…

2026/5/17 11:52:29 阅读更多 →

最新新闻

如何在Windows和Linux上获得完整的AirPods体验:免费开源工具终极指南

如何在Windows和Linux上获得完整的AirPods体验:免费开源工具终极指南

如何在Windows和Linux上获得完整的AirPods体验:免费开源工具终极指南 【免费下载链接】AirPodsDesktop ☄️ AirPods desktop user experience enhancement program, for Windows and Linux (WIP) 项目地址: https://gitcode.com/gh_mirrors/ai/AirPodsDesktop …

2026/7/4 17:04:56 阅读更多 →
FanControl如何解决现代PC散热控制的技术挑战?

FanControl如何解决现代PC散热控制的技术挑战?

FanControl如何解决现代PC散热控制的技术挑战? 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Trending/fa/FanCon…

2026/7/4 17:04:56 阅读更多 →
Web自动化测试全流程解析:从Selenium基础到CI/CD集成实战

Web自动化测试全流程解析:从Selenium基础到CI/CD集成实战

1. 项目概述:为什么我们需要Web自动化测试?在软件开发,尤其是Web应用开发的日常工作中,测试是一个绕不开的环节。想象一下,你刚刚完成了一个新功能的开发,比如一个复杂的用户注册表单。你需要验证它在Chrom…

2026/7/4 17:02:56 阅读更多 →
YOLOv5模型构建与优化:从架构解析到注意力机制实战

YOLOv5模型构建与优化:从架构解析到注意力机制实战

1. YOLOv5模型构建原理深度解析 在目标检测领域,YOLOv5以其优异的性能和易用性广受欢迎。要真正掌握模型优化技巧,首先需要理解其构建机制的核心三要素: 1.1 模型架构定义文件(yaml) yolov5s.yaml 文件相当于建筑的…

2026/7/4 17:02:56 阅读更多 →
构建定制化Frida工具链:对抗检测与深度优化的移动安全实战

构建定制化Frida工具链:对抗检测与深度优化的移动安全实战

1. 项目概述:为什么我们需要一个“魔改”的Frida工具链?如果你在移动安全、应用逆向或者动态分析这个圈子里待过一阵子,Frida这个名字对你来说肯定不陌生。它就像一把瑞士军刀,能让你在运行时“为所欲为”——注入脚本、Hook函数、…

2026/7/4 17:02:56 阅读更多 →
炉石传说自动化脚本终极指南:如何快速上手智能游戏助手

炉石传说自动化脚本终极指南:如何快速上手智能游戏助手

炉石传说自动化脚本终极指南:如何快速上手智能游戏助手 【免费下载链接】Hearthstone-Script Hearthstone script(炉石传说脚本) 项目地址: https://gitcode.com/gh_mirrors/he/Hearthstone-Script 厌倦了炉石传说中重复的点击操作&am…

2026/7/4 16:56:54 阅读更多 →

日新闻

Memcached 1.6.43 发布:关键安全修复版本,多项问题得到解决

Memcached 1.6.43 发布:关键安全修复版本,多项问题得到解决

Memcached 1.6.43 正式发布,这是一个关键的安全修复版本,修复了多个方面的问题,还对部分功能进行了优化。 安全修复亮点 此次发布在安全修复上表现突出。binprot 避免了项目引用计数溢出,mcmc 因安全问题提升了上游版本号&#xf…

2026/7/4 0:04:29 阅读更多 →
终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案

终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案

终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案 【免费下载链接】HMCL A Minecraft Launcher which is multi-functional, cross-platform and popular 项目地址: https://gitcode.com/gh_mirrors/hm/HMCL HMCL(Hello Minecraft! Lau…

2026/7/4 0:06:29 阅读更多 →
KMX63与PIC18F66K40在嵌入式HMI中的硬件协同与低功耗设计

KMX63与PIC18F66K40在嵌入式HMI中的硬件协同与低功耗设计

1. KMX63与PIC18F66K40的硬件协同架构解析KMX63作为一款三轴加速度计和磁力计组合传感器,与PIC18F66K40微控制器的搭配堪称嵌入式HMI开发的黄金组合。这套硬件组合的核心优势在于KMX63提供的高精度运动感知能力与PIC18F66K40强大的信号处理能力形成了完美互补。KMX6…

2026/7/4 0:06:29 阅读更多 →

周新闻

月新闻