跳到主要内容

缓存机制

问题​

MyBatis 的一级缓存和二级缓存是什么?它们的区别和使用注意事项?

答案​

缓存体系概览​

查询顺序

二级缓存 → 一级缓存 → 数据库。注意先查二级再查一级。

一级缓存(Local Cache)​

属性说明
作用域SqlSession 级别(默认)
默认开启是,无需配置
存储结构HashMap(key = statementId + params + rowBounds + SQL)
生命周期随 SqlSession 创建而生,关闭而亡

失效条件:

  1. 执行了 insert/update/delete(同一 SqlSession 内)
  2. 调用了 sqlSession.clearCache()
  3. 调用了 sqlSession.commit() 或 sqlSession.close()
  4. 不同的 SqlSession
一级缓存示例
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);

// 第一次查询:走数据库
User user1 = mapper.getById(1L);
// 第二次查询:走一级缓存(同一 SqlSession,相同 SQL 和参数)
User user2 = mapper.getById(1L);

System.out.println(user1 == user2); // true(同一个对象)

// 更新操作会清空一级缓存
mapper.updateName(1L, "newName");

// 第三次查询:重新走数据库
User user3 = mapper.getById(1L);
}
Spring 中一级缓存的陷阱

在 Spring 中,默认每次 Mapper 调用都创建新的 SqlSession(通过 SqlSessionTemplate),因此一级缓存基本不生效。只有在同一个 @Transactional 事务方法内,多次相同查询才能命中一级缓存。

二级缓存(Second Level Cache)​

属性说明
作用域Mapper(namespace)级别
默认开启需要手动配置
存储结构序列化存储(实体类需实现 Serializable)
生命周期随应用存在

启用二级缓存:

UserMapper.xml
<mapper namespace="com.example.mapper.UserMapper">
<!-- 开启二级缓存 -->
<cache
eviction="LRU" <!-- 淘汰策略:LRU/FIFO/SOFT/WEAK -->
flushInterval="60000" <!-- 刷新间隔(毫秒) -->
size="1024" <!-- 最大缓存对象数 -->
readOnly="false" <!-- 是否只读 -->
/>
</mapper>
application.yml
mybatis:
configuration:
cache-enabled: true # 全局开关(默认 true)

二级缓存失效条件:

  1. 同一 namespace 下执行了 insert/update/delete
  2. 手动清空缓存
  3. 设置了 flushCache="true" 的 select 语句

一级缓存 vs 二级缓存​

对比一级缓存二级缓存
作用域SqlSessionMapper namespace
默认状态开启关闭
存储方式内存对象引用序列化(跨 Session 共享)
线程安全无需(单线程内)需要(多 Session 共享)
实体要求无需实现 Serializable
清空触发当前 Session 增删改当前 namespace 增删改

二级缓存的问题​

多表关联查询的脏读

二级缓存按 namespace 隔离。如果 UserMapper 中有关联查询包含 Order 表数据,当 OrderMapper 更新了 Order 数据时,UserMapper 的二级缓存不会失效,导致脏数据。

解决方案:

  1. 关联查询不使用二级缓存(useCache="false")
  2. 使用 <cache-ref> 让多个 namespace 共享缓存(但会降低缓存命中率)
  3. 放弃 MyBatis 二级缓存,使用 Redis 等外部缓存

常见面试问题​

Q1: MyBatis 一级缓存和二级缓存的区别?​

答案:

一级缓存是 SqlSession 级别,默认开启,同一 SqlSession 内相同 SQL 直接返回缓存对象。二级缓存是 Mapper namespace 级别,需手动开启,跨 SqlSession 共享,数据以序列化形式存储。查询顺序是:二级缓存 → 一级缓存 → 数据库。

Q2: 为什么不推荐使用 MyBatis 二级缓存?​

答案:

  1. 多表查询脏读:缓存按 namespace 隔离,关联查询可能读到过期数据
  2. 粒度太粗:任何增删改都清空整个 namespace 的缓存,命中率低
  3. 分布式场景失效:默认只是本地缓存,多实例间不共享

生产环境推荐使用 Redis 等分布式缓存替代 MyBatis 二级缓存,可以精确控制缓存粒度和失效策略。

Q3: Spring 中 MyBatis 一级缓存的表现?​

答案:

Spring 默认每次 Mapper 方法调用创建新的 SqlSession,因此一级缓存几乎不生效。只有在 @Transactional 事务方法内,Spring 会复用同一个 SqlSession,此时多次相同查询才会命中一级缓存。

Q4: 如何自定义二级缓存实现?​

答案:

实现 org.apache.ibatis.cache.Cache 接口,然后在 XML 中指定:

<cache type="com.example.cache.RedisCache"/>

或使用 mybatis-redis 等第三方集成包。

相关链接​