尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
MyBatis缓存机制详解:一级与二级缓存原理与实践
1. MyBatis缓存机制概述作为Java开发者MyBatis的缓存机制是我们日常开发中必须掌握的核心知识点。缓存机制的设计初衷是为了减少数据库访问次数提升系统性能。但在实际应用中如果对缓存机制理解不透彻很容易引发各种问题比如脏数据、缓存穿透等。MyBatis提供了一级缓存和二级缓存两种机制一级缓存是SqlSession级别的缓存默认开启二级缓存是Mapper级别的缓存需要手动配置开启2. 一级缓存深度解析2.1 一级缓存工作机制一级缓存是MyBatis默认开启的缓存机制它的作用范围仅限于单个SqlSession内部。当同一个SqlSession中执行相同的SQL查询时MyBatis会优先从缓存中获取结果而不是直接访问数据库。一级缓存的工作流程如下执行查询时MyBatis会根据SQL语句、参数、分页等信息生成CacheKey先在本地缓存(LocalCache)中查找是否有对应的缓存结果如果命中缓存直接返回结果如果未命中则查询数据库并将结果存入缓存2.2 一级缓存配置与实验一级缓存有两个配置级别SESSION(默认)同一个SqlSession内的所有查询共享缓存STATEMENT仅对当前Statement有效setting namelocalCacheScope valueSESSION/通过实验可以验证一级缓存的特性Test public void testLocalCache() { SqlSession sqlSession sqlSessionFactory.openSession(); try { UserMapper mapper sqlSession.getMapper(UserMapper.class); // 第一次查询会访问数据库 User user1 mapper.selectById(1); // 第二次查询直接从缓存获取 User user2 mapper.selectById(1); // user1 user2 为true } finally { sqlSession.close(); } }2.3 一级缓存失效场景以下操作会导致一级缓存失效执行insert/update/delete操作调用SqlSession的clearCache()方法配置了localCacheScopeSTATEMENT手动修改了数据库数据(其他SqlSession或外部程序修改)注意在分布式环境下一级缓存容易引发脏读问题因为不同节点的SqlSession无法感知彼此的修改。3. 二级缓存全面剖析3.1 二级缓存工作机制二级缓存是Mapper级别的缓存多个SqlSession可以共享同一个Mapper的二级缓存。二级缓存的工作流程如下查询时先检查二级缓存如果命中则直接返回未命中则查询数据库并将结果存入二级缓存执行更新操作时清空对应的二级缓存3.2 二级缓存配置与使用启用二级缓存需要两步配置全局配置中开启二级缓存setting namecacheEnabled valuetrue/在Mapper XML中配置缓存策略cache evictionLRU flushInterval60000 size512 readOnlytrue/3.3 二级缓存实验验证通过实验可以验证二级缓存的特性Test public void testSecondLevelCache() { SqlSession sqlSession1 sqlSessionFactory.openSession(); SqlSession sqlSession2 sqlSessionFactory.openSession(); try { UserMapper mapper1 sqlSession1.getMapper(UserMapper.class); UserMapper mapper2 sqlSession2.getMapper(UserMapper.class); // 第一次查询访问数据库 User user1 mapper1.selectById(1); sqlSession1.commit(); // 必须提交才会放入二级缓存 // 第二次查询从二级缓存获取 User user2 mapper2.selectById(1); // user1 user2 为false因为二级缓存默认是序列化存储 } finally { sqlSession1.close(); sqlSession2.close(); } }3.4 二级缓存的局限性二级缓存存在以下问题需要注意多表关联查询时容易出现脏数据分布式环境下需要额外处理缓存同步缓存策略配置不当可能导致内存溢出事务提交后才会更新缓存4. 缓存源码深度分析4.1 一级缓存源码实现一级缓存的核心实现位于BaseExecutor类中public abstract class BaseExecutor implements Executor { protected PerpetualCache localCache; public E ListE query(...) { // 生成CacheKey CacheKey key createCacheKey(...); // 尝试从缓存获取 list resultHandler null ? (ListE) localCache.getObject(key) : null; if (list null) { // 查询数据库 list queryFromDatabase(...); // 放入缓存 localCache.putObject(key, list); } return list; } }CacheKey的生成逻辑考虑了以下因素MappedStatement的ID分页参数(offset, limit)SQL语句本身参数值环境ID4.2 二级缓存源码实现二级缓存通过装饰器模式实现核心类为CachingExecutorpublic class CachingExecutor implements Executor { private final Executor delegate; private final TransactionalCacheManager tcm new TransactionalCacheManager(); public E ListE query(...) { // 获取MappedStatement对应的Cache Cache cache ms.getCache(); if (cache ! null) { // 检查是否需要刷新缓存 flushCacheIfRequired(ms); if (ms.isUseCache() resultHandler null) { // 从二级缓存获取 list (ListE) tcm.getObject(cache, key); if (list null) { // 委托给底层Executor查询 list delegate.query(...); // 放入二级缓存 tcm.putObject(cache, key, list); } } } return list; } }5. 生产环境缓存最佳实践5.1 缓存使用建议一级缓存在单机环境下可以保留默认配置分布式环境下建议设置为STATEMENT级别对于读写频繁的场景考虑手动清空缓存二级缓存对于读多写少的场景可以考虑使用需要实现Serializable接口建议设置合理的回收策略和大小限制5.2 常见问题解决方案脏数据问题多表关联查询避免使用二级缓存可以考虑使用cache-ref建立缓存引用关系或者手动控制缓存清除缓存穿透对空结果也进行缓存使用布隆过滤器预先判断缓存雪崩设置不同的过期时间使用多级缓存策略5.3 性能优化技巧选择合适的回收策略LRU(最近最少使用)FIFO(先进先出)SOFT(软引用)WEAK(弱引用)批量操作优化// 批量插入优化示例 insert idbatchInsert parameterTypejava.util.List INSERT INTO user(name,age) VALUES foreach collectionlist itemitem separator, (#{item.name},#{item.age}) /foreach /insert局部缓存控制// 指定方法不使用缓存 select idselectById resultMapuserMap useCachefalse SELECT * FROM user WHERE id #{id} /select // 强制刷新缓存 select idselectAll resultMapuserMap flushCachetrue SELECT * FROM user /select6. 高级应用与扩展6.1 自定义缓存实现MyBatis缓存支持自定义实现只需要实现Cache接口public class RedisCache implements Cache { private final ReadWriteLock readWriteLock new ReentrantReadWriteLock(); private final String id; private final RedisTemplateString, Object redisTemplate; public RedisCache(String id) { this.id id; // 初始化RedisTemplate } public void putObject(Object key, Object value) { redisTemplate.opsForValue().set(key.toString(), value); } public Object getObject(Object key) { return redisTemplate.opsForValue().get(key.toString()); } // 实现其他方法... }配置使用自定义缓存cache typecom.example.RedisCache/6.2 整合第三方缓存整合Ehcachedependency groupIdorg.mybatis.caches/groupId artifactIdmybatis-ehcache/artifactId version1.2.1/version /dependency cache typeorg.mybatis.caches.ehcache.EhcacheCache/整合Redisdependency groupIdorg.mybatis.caches/groupId artifactIdmybatis-redis/artifactId version1.0.0-beta2/version /dependency cache typeorg.mybatis.caches.redis.RedisCache/6.3 多级缓存架构对于高性能场景可以考虑多级缓存架构一级缓存SqlSession级别二级缓存Mapper级别分布式缓存Redis等本地缓存Caffeine等实现思路自定义Cache实现采用装饰器模式组合多种缓存设置合理的缓存过期策略实现缓存同步机制7. 常见面试问题解析7.1 基础问题MyBatis的一级缓存和二级缓存有什么区别作用范围不同一级缓存是SqlSession级别二级缓存是Mapper级别配置方式不同一级缓存默认开启二级缓存需要显式配置存储结构不同一级缓存使用HashMap二级缓存可配置多种实现什么情况下会导致MyBatis缓存失效执行insert/update/delete操作调用clearCache方法配置了flushCachetrue事务提交(二级缓存)缓存达到上限或过期7.2 进阶问题MyBatis缓存是如何解决并发问题的一级缓存SqlSession是线程独享的天然避免并发问题二级缓存通过SynchronizedCache装饰器实现线程安全事务缓存通过TransactionalCache管理事务期间的缓存操作如何设计一个高性能的MyBatis缓存方案根据业务特点选择合适的缓存粒度读多写少场景使用二级缓存写多读少场景考虑禁用或缩短缓存时间分布式环境使用集中式缓存对热点数据实现多级缓存7.3 实战问题如何处理多表关联查询的缓存问题方案1避免在关联查询上使用二级缓存方案2使用cache-ref建立缓存关联方案3手动控制相关缓存的清除方案4将关联查询拆分为多个单表查询MyBatis缓存与Spring Cache如何选择MyBatis缓存ORM层缓存自动管理粒度较细Spring Cache业务层缓存更灵活支持多种实现最佳实践MyBatis缓存处理数据库访问优化Spring Cache处理业务逻辑优化8. 个人实践经验分享在实际项目中使用MyBatis缓存时我总结了以下几点经验缓存监控很重要需要实时监控缓存命中率、内存占用等指标我们使用PrometheusGrafana搭建了监控系统。缓存key设计对于复杂查询需要精心设计cacheKey避免因为参数顺序不同导致缓存未命中。批量操作优化对于批量插入场景我们重写了Executor实现了批量操作的缓存优化。动态缓存配置根据系统负载动态调整缓存策略高峰期放宽缓存限制低谷期收紧。故障处理建立缓存降级机制当缓存出现问题时可以快速切换为直接访问数据库。一个实用的工具类示例public class CacheHelper { public static void clearCache(SqlSessionFactory sqlSessionFactory, Class? mapperClass) { try (SqlSession sqlSession sqlSessionFactory.openSession()) { Configuration config sqlSession.getConfiguration(); Cache cache config.getCache(mapperClass.getName()); if (cache ! null) { cache.clear(); } } } public static void clearAllCache(SqlSessionFactory sqlSessionFactory) { try (SqlSession sqlSession sqlSessionFactory.openSession()) { sqlSession.clearCache(); Configuration config sqlSession.getConfiguration(); for (String name : config.getCacheNames()) { config.getCache(name).clear(); } } } }最后关于是否在生产环境使用MyBatis缓存我的建议是对于简单的单表CRUD可以使用一级缓存对于复杂的查询场景建议使用专门的缓存解决方案如Redis使用缓存前一定要做好充分的测试和监控
RELATED

相关推荐

C语言与Python协同实现高效算法与网络操作

C语言与Python协同实现高效算法与网络操作

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/13 3:39:00
Vibe Kanban 如何为仓库配置 Setup 脚本,让代理启动前自动安装依赖并准备环境

Vibe Kanban 如何为仓库配置 Setup 脚本,让代理启动前自动安装依赖并准备环境

Vibe Kanban 如何为仓库配置 Setup 脚本,让代理启动前自动安装依赖并准备环境 【免费下载链接】vibe-kanban Get 10X more out of Claude Code, Codex or any coding agent 项目地址: https://gitcode.com/GitHub_Trending/vi/vibe-kanban 在 Vibe Kanban 中…

📅 2026/9/13 3:39:00
若依集成MyBatis-Plus实战:Spring Boot 2/3双版本兼容方案

若依集成MyBatis-Plus实战:Spring Boot 2/3双版本兼容方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/13 3:34:00
MORE NEWS

更多资讯

📰

树莓派Pico呼吸灯:MicroPython硬件PWM与伽马校正实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Qwen3与Claude3.5的Thinking Mode推理对比实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

WSL+VS Code Server+Ollama:构建Web化AI开发环境

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

.NET构建与发布革新:NativeAOT、单文件、剪裁与源生成器实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Android面试能力解码:基础穿透力与场景决策力实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Zabbix保姆级部署教程:从环境准备到主机接入与报错排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬