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 执行 insert、update、delete。
  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 下跨会话复用结果。缓存能不能用,不只看能不能命中,更要看数据更新后能不能及时失效。

相关推荐
小羊没烦恼!2 天前
微服务化的基石——持续集成
java·大数据·word·powerpoint·.net
俊昭喜喜里2 天前
java中的继承和多态的区别
java
小羊没烦恼!2 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
譕痕2 天前
JSONObject与JSONArray封装数据格式区别
java·json
胡写代码2 天前
别再前后端各写一套表单校验了
java·后端
小鱼能吃糖2 天前
缺陷修复总览 · mall电商项目:5类缺陷,1个病根,4个业务域
java·电商
此时不提桶,更待何时2 天前
01-06-A-JVM排查实战详解
java·jvm
vipxieliang3 天前
ValidX 在 DDD 领域驱动设计中的实践
java·spring boot
ba_pi3 天前
mysql 查询所有表名并授权
java
ProcessOn官方账号3 天前
java函数式编程--入门基础
java·编程·函数式编程