高 进阶
MyBatis一二级缓存#
一句话答案#
一级缓存 SqlSession 级别默认开启,二级缓存 Mapper 级别需手动开启,分布式环境建议关闭或用 Redis 替代。
核心要点
一级缓存(本地缓存):
- 作用域:SqlSession(
BaseExecutor.localCache,一个 HashMap),默认开启;localCacheScope可设SESSION(默认)或STATEMENT(等于关闭) - 缓存 key:MappedStatement id + 分页参数(offset/limit) + SQL + 参数值 + 环境 id
- 失效:同一 SqlSession 执行 insert/update/delete、
commit()/rollback()、clearCache()、close();语句配置flushCache="true"也会清空 - Spring 整合下:非事务时每次 Mapper 调用都由
SqlSessionTemplate新开并关闭一个 SqlSession,一级缓存基本不生效;同一个@Transactional事务内共享 SqlSession,一级缓存才生效
二级缓存(namespace 级别):
- 作用域:Mapper namespace,跨 SqlSession 共享;全局开关
cacheEnabled默认 true,但还要在 Mapper XML 里加<cache/>(或注解@CacheNamespace)才真正启用 - 实现:
CachingExecutor装饰 Executor,查询顺序是二级缓存 → 一级缓存 → 数据库 - 写入时机:查询结果先放
TransactionalCache暂存,SqlSession commit/close 后才真正写入二级缓存(避免读到未提交数据) - 失效:该 namespace 下任意 insert/update/delete 会清空整个 namespace 的缓存;可用
<cache-ref>让多个 namespace 共用一个缓存 - 要求:默认
readOnly=false时通过序列化返回副本,实体类需实现Serializable
问题与生产建议:
- 两级缓存都是本地 JVM 内存,多实例部署下各节点缓存互不感知 → 脏读
- 多表关联查询跨 namespace,关联表更新不会清掉当前 namespace 的缓存 → 脏读
- 生产通常关闭二级缓存,用 Redis 等分布式缓存在业务层做缓存
面试回答(2分钟版)
MyBatis 的缓存分两级。一级缓存是 SqlSession 级别的,默认开启,同一个 SqlSession 内相同的查询只会执行一次 SQL,后续直接从缓存取结果。一级缓存在执行 update/insert/delete 操作、手动调用 clearCache 或 SqlSession 关闭时失效。但在 Spring 中,没有事务时每次 Mapper 调用都会新建 SqlSession,一级缓存基本不生效,只有同一个事务内才能命中。二级缓存是 Mapper 级别的,需要在 XML 中配置 cache 标签手动开启,它可以跨 SqlSession 共享数据,但只在同一个 namespace 内有效。二级缓存的最大问题是多表联查时容易产生脏数据,因为关联表的更新不会触发当前 namespace 缓存失效。所以生产环境通常建议关闭 MyBatis 二级缓存,改用 Redis 做分布式缓存,既能跨服务共享又能精确控制失效策略。
追问与易错
追问方向:
- “一级缓存什么时候失效?”→ 执行 update/insert/delete 操作后自动清空、手动调用 sqlSession.clearCache()、SqlSession 关闭或提交时失效;Spring 中非事务下每次 Mapper 调用都新建 SqlSession,只有同一事务内才会命中
- “二级缓存的脏数据问题?”→ 二级缓存按 namespace 隔离,多表联查时关联表的更新不会触发当前 namespace 缓存失效,导致查到过时数据;可用 cache-ref 引用其他 namespace 但会降低缓存效果
- “生产推荐开启二级缓存吗?”→ 通常不推荐,因为脏数据风险高、只是 JVM 级别缓存不支持分布式;生产环境建议关闭二级缓存,用 Redis 做分布式缓存精确控制失效策略
易错点:
- ❌ MyBatis 缓存等于 Redis 缓存——是 JVM 级别的
- ❌ 二级缓存跨 namespace 生效——只在同一 namespace