面试知识库
高 进阶

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