MyBatis 缓存机制学习总结
2026/4/27...大约 3 分钟
前言
MyBatis 是常见的 Java 数据库访问层框架,其缓存机制在日常开发中容易被忽视,但使用不当可能引发脏数据问题。本文从应用和源码角度梳理 MyBatis 缓存机制。
一级缓存
概述
一级缓存是 SqlSession 级别的缓存,在一次数据库会话中,执行多次相同 SQL 时会优先命中缓存。
用户查询 → SqlSession → Executor → LocalCache
↓
命中? → 返回结果
↓ 否
查询数据库 → 写入 LocalCache → 返回结果配置
<!-- SESSION(默认): 会话内共享缓存 -->
<!-- STATEMENT: 仅当前 Statement 有效 -->
<setting name="localCacheScope" value="SESSION"/>实验结论
| 场景 | 结果 |
|---|---|
| 同一 SqlSession 多次相同查询 | 只有第一次查数据库,后续命中缓存 |
| 同一 SqlSession 执行更新后查询 | 缓存失效,重新查数据库 |
| 不同 SqlSession 之间 | 缓存不共享,可能读到脏数据 |
源码要点
CacheKey 的构成:
Statement Id + Offset + Limit + Sql + Params核心类:
BaseExecutor:持有PerpetualCache localCachePerpetualCache:内部使用 HashMap 存储缓存
缓存失效时机:
// BaseExecutor.update() 方法中
public int update(MappedStatement ms, Object parameter) {
clearLocalCache(); // 每次 update 前清空缓存
return doUpdate(ms, parameter);
}一级缓存总结
- 生命周期与 SqlSession 一致
- 内部是无限容量的 HashMap,缺乏功能性
- 多 SqlSession 或分布式环境下会产生脏数据,建议设置
STATEMENT级别
二级缓存
概述
二级缓存是 namespace 级别的缓存,可跨 SqlSession 共享。开启后查询流程为:
二级缓存 → 一级缓存 → 数据库配置
<!-- 1. mybatis-config.xml 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- 2. Mapper.xml 中配置 cache 标签 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>cache 标签属性:
| 属性 | 说明 |
|---|---|
| type | 缓存实现类,默认 PerpetualCache |
| eviction | 回收策略:LRU、FIFO、SOFT、WEAK |
| flushInterval | 自动刷新间隔(毫秒) |
| size | 最大缓存对象数 |
| readOnly | 是否只读 |
| blocking | 缓存未命中时是否阻塞 |
实验结论
| 场景 | 结果 |
|---|---|
| SqlSession 不提交 | 二级缓存不生效 |
| SqlSession 提交后 | 其他 SqlSession 可命中缓存 |
| 同 namespace 下更新操作 | 刷新该 namespace 的缓存 |
| 多表查询 | 可能出现脏数据 |
多表查询的脏数据问题
// StudentMapper 查询学生+班级信息(多表查询)
studentMapper.getStudentByIdWithClassInfo(1);
// ClassMapper 更新班级名(不同 namespace)
classMapper.updateClassName("特色一班", 1);
// StudentMapper 再次查询 → 返回脏数据!
// 原因:StudentMapper 的缓存未感知到 ClassMapper 的更新解决方案:使用 cache-ref 引用其他 namespace 的缓存
<!-- ClassMapper.xml -->
<cache-ref namespace="mapper.StudentMapper"/>但这样缓存粒度变粗,影响更大。
源码要点
装饰器模式的缓存链:
SynchronizedCache → LoggingCache → SerializedCache → LruCache → PerpetualCache各装饰器作用:
| 装饰器 | 功能 |
|---|---|
| SynchronizedCache | 同步锁 |
| LoggingCache | 记录命中率 |
| SerializedCache | 序列化存储,保证线程安全 |
| LruCache | LRU 淘汰策略 |
| PerpetualCache | 基础实现,HashMap 存储 |
事务与缓存:
TransactionalCacheManager管理事务级缓存- 只有 commit() 后才真正写入二级缓存
- 未提交或回滚则不影响缓存
二级缓存总结
- 实现了 SqlSession 间缓存共享,粒度更细(namespace 级别)
- 多表查询极易出现脏数据,安全使用条件苛刻
- 分布式环境下需要自定义 Cache 实现(如 Redis),成本较高
最佳实践建议
建议
生产环境中关闭 MyBatis 缓存,让其作为单纯的 ORM 框架使用。分布式缓存需求直接使用 Redis、Memcached 等方案更安全可靠。
| 缓存类型 | 适用场景 | 风险 |
|---|---|---|
| 一级缓存(SESSION) | 单 SqlSession 内重复查询 | 分布式脏数据 |
| 一级缓存(STATEMENT) | 安全但无缓存效果 | - |
| 二级缓存 | 单表简单 CRUD | 多表查询脏数据 |
核心知识点速记
一级缓存:
├── 作用域:SqlSession
├── 实现:PerpetualCache (HashMap)
├── 失效:update/insert/delete 执行时
└── 问题:多 Session 脏数据
二级缓存:
├── 作用域:namespace
├── 实现:装饰器链
├── 条件:commit 后生效
└── 问题:多表查询脏数据
CacheKey = StatementId + Offset + Limit + Sql + Params