MyBatis 一级缓存、二级缓存与清理机制

MyBatis 缓存面试常见问法是:"一级缓存和二级缓存用过吗?"不要只背"一级缓存是 SqlSession 级别,二级缓存是 namespace 级别"。更好的回答是讲清楚 缓存作用域、什么时候命中、什么时候清空、为什么二级缓存要谨慎使用

命中
未命中

命中
未命中

Mapper 查询
先查一级缓存
直接返回结果
是否开启

二级缓存
查询 namespace

二级缓存
查询数据库
写入一级缓存
SqlSession 关闭或提交后

写入二级缓存

一级缓存是什么

一级缓存是 MyBatis 默认开启的本地缓存,作用域是 SqlSession

它底层基于 PerpetualCache,本质上可以理解成一个 HashMap

同一个 SqlSession 中,执行完全相同的查询,第一次查数据库,第二次可能直接命中缓存。

java 复制代码
SqlSession sqlSession = sqlSessionFactory.openSession();

UserMapper mapper1 = sqlSession.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession.getMapper(UserMapper.class);

User user1 = mapper1.selectById(6);
User user2 = mapper2.selectById(6);

sqlSession.close();

如果中间没有刷新缓存,两个查询在同一个 SqlSession 内,通常只会执行一次 SQL。

一级缓存会在这些情况下清空:

  1. SqlSession 执行 insertupdatedelete
  2. 手动执行 clearCache()
  3. SqlSession 关闭。
  4. 查询配置要求刷新缓存。

二级缓存是什么

二级缓存的作用域是 namespace,也就是 Mapper 级别。它不依赖单个 SqlSession,不同 SqlSession 之间可以共享。

java 复制代码
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = mapper1.selectById(6);
sqlSession1.close();

SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = mapper2.selectById(6);
sqlSession2.close();

如果二级缓存开启,并且第一次会话关闭后数据进入二级缓存,第二个会话查询相同数据时就可能命中缓存。

二级缓存默认不开启,需要两步:

xml 复制代码
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

在 Mapper 映射文件中开启:

xml 复制代码
<mapper namespace="com.example.UserMapper">
    <cache/>
</mapper>

同时,二级缓存中的对象通常需要实现 Serializable 接口。

一级缓存和二级缓存对比

维度 一级缓存 二级缓存
作用域 SqlSession namespace / Mapper
是否默认开启 默认开启 默认不开启
是否跨会话 不跨会话 可以跨会话
底层实现 PerpetualCache 默认也是 PerpetualCache
清理时机 session flush、更新、关闭 当前 namespace 发生增删改后清理
使用建议 默认即可 谨慎使用,关注数据一致性

二级缓存什么时候清理

缓存最大的问题不是能不能命中,而是数据什么时候失效。




执行 select 查询
结果进入缓存
后续相同查询

命中缓存
同作用域是否

发生写操作
继续复用缓存
insert

update

delete
清空该作用域缓存
下一次查询

重新访问数据库

MyBatis 默认规则是:

某个作用域发生新增、修改、删除操作后,该作用域下相关 select 缓存会被清空。

比如 UserMapper namespace 下执行了 updateUser,那么 UserMapper 这个 namespace 下的二级缓存会被清理,避免后续读到旧数据。

但这也带来一个问题:如果多个 Mapper 操作的是同一张表,而二级缓存按 namespace 隔离,缓存一致性就容易变复杂。

为什么二级缓存要谨慎

二级缓存看起来很美好,但实际项目中要谨慎:

  1. 数据更新频繁时,缓存很容易被频繁清空,收益不大。
  2. 多个 namespace 操作同一批数据时,缓存一致性不好控制。
  3. 分布式部署下,本地二级缓存无法天然跨节点一致。
  4. 业务已经使用 Redis 这类集中式缓存时,MyBatis 二级缓存容易和业务缓存叠加出复杂问题。

所以真实项目里,MyBatis 一级缓存默认使用即可;跨请求、跨服务的热点数据缓存,通常会交给 Redis 这类专门缓存组件。

面试回答模板

可以这样回答:

MyBatis 一级缓存是 SqlSession 级别的本地缓存,默认开启,底层基于 PerpetualCache,可以理解成 HashMap。同一个 SqlSession 中相同查询可能命中一级缓存;当 session 发生增删改、flush、clear 或 close 时缓存会清空。二级缓存是 namespace 或 Mapper 级别的缓存,默认关闭,需要全局开启 cacheEnabled,并在 Mapper 中配置 <cache/>。二级缓存可以跨 SqlSession,但只有会话提交或关闭后,一级缓存中的数据才会进入二级缓存。

如果继续问清理机制,可以补:

当某个作用域发生新增、修改、删除操作后,MyBatis 默认会清空该作用域下 select 缓存,避免读到旧数据。但二级缓存按 namespace 隔离,多 Mapper、多服务场景下数据一致性比较难控制,所以实际项目中要谨慎使用。

小结

MyBatis 缓存要抓住两个词:作用域失效

一级缓存解决的是同一个 SqlSession 内重复查询;二级缓存解决的是同一个 Mapper namespace 下跨会话复用结果。缓存能不能用,不只看能不能命中,更要看数据更新后能不能及时失效。

相关推荐
莫得感情 o42 分钟前
Redis 05 · 持久化:RDB 与 AOF 怎么保证数据不丢
redis·缓存
zww89491112 小时前
匿名树洞系统开发实战:从需求分析到部署指南
java·eclipse
curd_boy2 小时前
【Redis】Redis从缓存到AI向量平台
人工智能·redis·缓存
kiracrimson2 小时前
从缓存的角度看链表与线性表的差异
数据结构·链表·缓存
黑马程序员毕设3 小时前
基于Java的医院药品管理系统的优化设计与实现
java·开发语言·spring boot·小程序·架构·课程设计·毕设
木井巳3 小时前
【BFS/DFS 解决 FloodFill 算法】太平洋大西洋水流问题
java·算法·leetcode·深度优先·广度优先·宽度优先·推荐算法
白山编程大哥3 小时前
Java 集合算法:从排序、查找到底层原理的实战指南
java·python·算法
devpotato4 小时前
缓存与数据库更新顺序不一致问题
java·数据库·redis
xcl09254 小时前
酒馆预约系统开发实战:从需求分析到上线全流程指南
java·spring boot·需求分析
Kyrie_kk4 小时前
Java--IO--Path文件访问
java·后端