news 2026/9/13 3:38:56

MyBatis缓存机制详解:一级与二级缓存原理与实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatis缓存机制详解:一级与二级缓存原理与实践

1. MyBatis缓存机制概述

作为Java开发者,MyBatis的缓存机制是我们日常开发中必须掌握的核心知识点。缓存机制的设计初衷是为了减少数据库访问次数,提升系统性能。但在实际应用中,如果对缓存机制理解不透彻,很容易引发各种问题,比如脏数据、缓存穿透等。

MyBatis提供了一级缓存和二级缓存两种机制:

  • 一级缓存是SqlSession级别的缓存,默认开启
  • 二级缓存是Mapper级别的缓存,需要手动配置开启

2. 一级缓存深度解析

2.1 一级缓存工作机制

一级缓存是MyBatis默认开启的缓存机制,它的作用范围仅限于单个SqlSession内部。当同一个SqlSession中执行相同的SQL查询时,MyBatis会优先从缓存中获取结果,而不是直接访问数据库。

一级缓存的工作流程如下:

  1. 执行查询时,MyBatis会根据SQL语句、参数、分页等信息生成CacheKey
  2. 先在本地缓存(LocalCache)中查找是否有对应的缓存结果
  3. 如果命中缓存,直接返回结果
  4. 如果未命中,则查询数据库并将结果存入缓存

2.2 一级缓存配置与实验

一级缓存有两个配置级别:

  • SESSION(默认):同一个SqlSession内的所有查询共享缓存
  • STATEMENT:仅对当前Statement有效
<setting name="localCacheScope" value="SESSION"/>

通过实验可以验证一级缓存的特性:

@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 一级缓存失效场景

以下操作会导致一级缓存失效:

  1. 执行insert/update/delete操作
  2. 调用SqlSession的clearCache()方法
  3. 配置了localCacheScope=STATEMENT
  4. 手动修改了数据库数据(其他SqlSession或外部程序修改)

注意:在分布式环境下,一级缓存容易引发脏读问题,因为不同节点的SqlSession无法感知彼此的修改。

3. 二级缓存全面剖析

3.1 二级缓存工作机制

二级缓存是Mapper级别的缓存,多个SqlSession可以共享同一个Mapper的二级缓存。二级缓存的工作流程如下:

  1. 查询时先检查二级缓存
  2. 如果命中则直接返回
  3. 未命中则查询数据库,并将结果存入二级缓存
  4. 执行更新操作时,清空对应的二级缓存

3.2 二级缓存配置与使用

启用二级缓存需要两步配置:

  1. 全局配置中开启二级缓存:
<setting name="cacheEnabled" value="true"/>
  1. 在Mapper XML中配置缓存策略:
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>

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 二级缓存的局限性

二级缓存存在以下问题需要注意:

  1. 多表关联查询时容易出现脏数据
  2. 分布式环境下需要额外处理缓存同步
  3. 缓存策略配置不当可能导致内存溢出
  4. 事务提交后才会更新缓存

4. 缓存源码深度分析

4.1 一级缓存源码实现

一级缓存的核心实现位于BaseExecutor类中:

public abstract class BaseExecutor implements Executor { protected PerpetualCache localCache; public <E> List<E> query(...) { // 生成CacheKey CacheKey key = createCacheKey(...); // 尝试从缓存获取 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list == null) { // 查询数据库 list = queryFromDatabase(...); // 放入缓存 localCache.putObject(key, list); } return list; } }

CacheKey的生成逻辑考虑了以下因素:

  • MappedStatement的ID
  • 分页参数(offset, limit)
  • SQL语句本身
  • 参数值
  • 环境ID

4.2 二级缓存源码实现

二级缓存通过装饰器模式实现,核心类为CachingExecutor:

public class CachingExecutor implements Executor { private final Executor delegate; private final TransactionalCacheManager tcm = new TransactionalCacheManager(); public <E> List<E> query(...) { // 获取MappedStatement对应的Cache Cache cache = ms.getCache(); if (cache != null) { // 检查是否需要刷新缓存 flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { // 从二级缓存获取 list = (List<E>) tcm.getObject(cache, key); if (list == null) { // 委托给底层Executor查询 list = delegate.query(...); // 放入二级缓存 tcm.putObject(cache, key, list); } } } return list; } }

5. 生产环境缓存最佳实践

5.1 缓存使用建议

  1. 一级缓存:
  • 在单机环境下可以保留默认配置
  • 分布式环境下建议设置为STATEMENT级别
  • 对于读写频繁的场景,考虑手动清空缓存
  1. 二级缓存:
  • 对于读多写少的场景可以考虑使用
  • 需要实现Serializable接口
  • 建议设置合理的回收策略和大小限制

5.2 常见问题解决方案

  1. 脏数据问题:
  • 多表关联查询避免使用二级缓存
  • 可以考虑使用cache-ref建立缓存引用关系
  • 或者手动控制缓存清除
  1. 缓存穿透:
  • 对空结果也进行缓存
  • 使用布隆过滤器预先判断
  1. 缓存雪崩:
  • 设置不同的过期时间
  • 使用多级缓存策略

5.3 性能优化技巧

  1. 选择合适的回收策略:
  • LRU(最近最少使用)
  • FIFO(先进先出)
  • SOFT(软引用)
  • WEAK(弱引用)
  1. 批量操作优化:
// 批量插入优化示例 <insert id="batchInsert" parameterType="java.util.List"> INSERT INTO user(name,age) VALUES <foreach collection="list" item="item" separator=","> (#{item.name},#{item.age}) </foreach> </insert>
  1. 局部缓存控制:
// 指定方法不使用缓存 <select id="selectById" resultMap="userMap" useCache="false"> SELECT * FROM user WHERE id = #{id} </select> // 强制刷新缓存 <select id="selectAll" resultMap="userMap" flushCache="true"> SELECT * FROM user </select>

6. 高级应用与扩展

6.1 自定义缓存实现

MyBatis缓存支持自定义实现,只需要实现Cache接口:

public class RedisCache implements Cache { private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private final String id; private final RedisTemplate<String, 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 type="com.example.RedisCache"/>

6.2 整合第三方缓存

  1. 整合Ehcache:
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.1</version> </dependency> <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
  1. 整合Redis:
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-redis</artifactId> <version>1.0.0-beta2</version> </dependency> <cache type="org.mybatis.caches.redis.RedisCache"/>

6.3 多级缓存架构

对于高性能场景,可以考虑多级缓存架构:

  1. 一级缓存:SqlSession级别
  2. 二级缓存:Mapper级别
  3. 分布式缓存:Redis等
  4. 本地缓存:Caffeine等

实现思路:

  • 自定义Cache实现
  • 采用装饰器模式组合多种缓存
  • 设置合理的缓存过期策略
  • 实现缓存同步机制

7. 常见面试问题解析

7.1 基础问题

  1. MyBatis的一级缓存和二级缓存有什么区别?
  • 作用范围不同:一级缓存是SqlSession级别,二级缓存是Mapper级别
  • 配置方式不同:一级缓存默认开启,二级缓存需要显式配置
  • 存储结构不同:一级缓存使用HashMap,二级缓存可配置多种实现
  1. 什么情况下会导致MyBatis缓存失效?
  • 执行insert/update/delete操作
  • 调用clearCache方法
  • 配置了flushCache="true"
  • 事务提交(二级缓存)
  • 缓存达到上限或过期

7.2 进阶问题

  1. MyBatis缓存是如何解决并发问题的?
  • 一级缓存:SqlSession是线程独享的,天然避免并发问题
  • 二级缓存:通过SynchronizedCache装饰器实现线程安全
  • 事务缓存:通过TransactionalCache管理事务期间的缓存操作
  1. 如何设计一个高性能的MyBatis缓存方案?
  • 根据业务特点选择合适的缓存粒度
  • 读多写少场景使用二级缓存
  • 写多读少场景考虑禁用或缩短缓存时间
  • 分布式环境使用集中式缓存
  • 对热点数据实现多级缓存

7.3 实战问题

  1. 如何处理多表关联查询的缓存问题?
  • 方案1:避免在关联查询上使用二级缓存
  • 方案2:使用cache-ref建立缓存关联
  • 方案3:手动控制相关缓存的清除
  • 方案4:将关联查询拆分为多个单表查询
  1. MyBatis缓存与Spring Cache如何选择?
  • MyBatis缓存:ORM层缓存,自动管理,粒度较细
  • Spring Cache:业务层缓存,更灵活,支持多种实现
  • 最佳实践:MyBatis缓存处理数据库访问优化,Spring Cache处理业务逻辑优化

8. 个人实践经验分享

在实际项目中使用MyBatis缓存时,我总结了以下几点经验:

  1. 缓存监控很重要:需要实时监控缓存命中率、内存占用等指标,我们使用Prometheus+Grafana搭建了监控系统。

  2. 缓存key设计:对于复杂查询,需要精心设计cacheKey,避免因为参数顺序不同导致缓存未命中。

  3. 批量操作优化:对于批量插入场景,我们重写了Executor,实现了批量操作的缓存优化。

  4. 动态缓存配置:根据系统负载动态调整缓存策略,高峰期放宽缓存限制,低谷期收紧。

  5. 故障处理:建立缓存降级机制,当缓存出现问题时可以快速切换为直接访问数据库。

一个实用的工具类示例:

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
  • 使用缓存前一定要做好充分的测试和监控
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 3:36:13

SpringBoot+Vue构建高校在线教育平台实战

/* 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:32:50

Pixelle-Video 使用 uv 安装依赖失败时如何处理?

Pixelle-Video 使用 uv 安装依赖失败时如何处理&#xff1f; 【免费下载链接】Pixelle-Video &#x1f680; AI 全自动短视频引擎 | AI Fully Automated Short Video Engine 项目地址: https://gitcode.com/GitHub_Trending/pi/Pixelle-Video 在 Pixelle-Video 从源码安…

作者头像 李华
网站建设 2026/9/13 3:25:19

电子元器件视觉质检系统:YOLOv11与VLM工业落地实践

/* 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:22:11

fhevm Gateway 协议暂停机制(Pausing Mechanism)实战指南

fhevm Gateway 协议暂停机制&#xff08;Pausing Mechanism&#xff09;实战指南 【免费下载链接】fhevm FHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications 项目地址: https://gitcode.com/GitHub_Trending/…

作者头像 李华