本文目录导读:

这是一个关于MyBatis二级缓存的核心特性问题,二级缓存(Second Level Cache)的设计初衷就是为了跨会话(SqlSession)共享数据。
下面我从工作原理、配置、注意事项和实际效果几个方面为你详细解析。
核心结论
是的,MyBatis二级缓存默认就是跨SqlSession共享的。
- 一级缓存(Local Cache):作用域是
SqlSession,同一个SqlSession中,多次执行相同的查询,会从缓存中取。会话关闭或提交后,一级缓存即失效。 - 二级缓存(Global Cache):作用域是
Mapper的命名空间(Namespace),不同的SqlSession只要查询同一个 Mapper 下的方法,且该数据在缓存中,就能共享使用。
工作原理(跨会话共享流程)
为了充分理解,你可以看下面这个典型场景:
- 用户A 开启会话
sqlSession1,执行select * from user where id = 1。 - MyBatis 检查二级缓存(
namespace=com.example.mapper.UserMapper),没有数据。 - 去数据库查询,返回结果。
- 关键步骤:
sqlSession1关闭或提交(session1.close())时,查询结果才会被序列化并存入二级缓存(通常是一个全局的Map)。 - 用户B 开启会话
sqlSession2,执行select * from user where id = 1。 - MyBatis 检查二级缓存(
namespace=UserMapper),发现数据,直接返回缓存对象(注意:是反序列化后的新对象,避免线程安全问题)。 - 二级缓存生效,无需查询数据库。
如何启用二级缓存(三步走)
要让它正常工作,需要完成以下配置:
第一步:全局配置(mybatis-config.xml)
开启二级缓存的总开关(默认是开启的,但建议显式设置):
<settings>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
第二步:Mapper 映射文件配置(XXXMapper.xml)
在 <mapper> 标签内添加 <cache/> 标签,这表示当前命名空间下的所有查询结果都将被缓存。
<mapper namespace="com.example.mapper.UserMapper">
<!-- 启用二级缓存 -->
<cache
eviction="LRU" <!-- 回收策略:最近最少使用 -->
flushInterval="60000" <!-- 刷新间隔:60秒,不写则按SQL执行时机刷新 -->
size="512" <!-- 引用数目 -->
readOnly="false" /> <!-- 是否只读:false表示可读写(需序列化) -->
<!-- ... select / insert / update / delete ... -->
</mapper>
第三步:实体类实现序列化接口
由于二级缓存需要跨会话存储和读取,通常需要将对象序列化后存储(特别是当 readOnly=false 时)。
public class User implements Serializable {
private static final long serialVersionUID = 1L; // 强烈建议显式定义
private Integer id;
private String name;
// getters and setters ...
}
关键特性与注意事项
1 缓存刷新(Flush)
- 任何在同一个Mapper下的
INSERT、UPDATE、DELETE操作,都会清空该Mapper下的二级缓存。 - 目的:防止读到脏数据,这是MyBatis保证最终一致性的简单策略。
2 跨命名空间查询的“坑” (重点)
如果使用多表联合查询(UserMapper 中有个查询关联了 Role 表),但在 RoleMapper 中更新了数据,UserMapper 的缓存不会自动失效。
解决方案:
使用 cache-ref 标签让两个 Mapper 共享同一个缓存命名空间。
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 引用 RoleMapper 的缓存,这样更新 Role 时也会刷新 User 的缓存 -->
<cache-ref namespace="com.example.mapper.RoleMapper"/>
<!-- ... -->
</mapper>
3 线程安全与对象拷贝
readOnly=true:直接返回缓存中的对象实例(速度快),但可能被其他线程修改,存在线程安全问题,适合不常修改的常量数据。readOnly=false(默认/推荐):返回缓存对象的反序列化副本(新对象),每个会话拿到的是独立的拷贝,线程安全。缺点:需要对象实现Serializable,且序列化/反序列化有性能开销。
二级缓存 vs Redis/外部缓存
MyBatis 自带的二级缓存是本地缓存(存储在应用的内存中),对于分布式应用:
- 问题:如果你的应用部署在多个服务器上,服务器A的缓存和服务器B的缓存是不共享的。
- 解决方案:实现
org.apache.ibatis.cache.Cache接口,将数据存入 Redis 等集中式缓存,MyBatis 官方提供了 Redis 和 Ehcache 的集成方案。
| 特性 | 二级缓存 |
|---|---|
| 共享范围 | 跨 SqlSession(同一实例的多个会话) |
| 生命周期 | 与应用实例共存亡(或直到缓存被刷新) |
| 触发条件 | sqlSession.close() 或 sqlSession.commit()(查询结果才会写入) |
| 刷新条件 | 同Mapper下的增删改(insert/update/delete) |
| 主要风险 | 多表关联时的脏数据、分布式不共享、内存占用 |
| 适用场景 | 读多写少、数据一致性要求不高(如查字典数据、分类信息) |
一句话概括:MyBatis二级缓存本质上是一个跨会话、按Mapper命名空间隔离的本地缓存,它通过“读写分离”和“增删改即失效”的简单机制,在单机应用里提升性能,但在分布式或多表关联复杂场景下,需要谨慎使用或替换为外部缓存。