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

相关推荐
AI人工智能+电脑小能手15 小时前
【大白话说Java面试题 第65题】【JVM篇】第25题:谈谈对 OOM 的认识
java·开发语言·jvm
阿维的博客日记15 小时前
Nacos 为什么能让配置动态生效?(涉及 @RefreshScope 注解)
java·spring
雨辰AI15 小时前
SpringBoot3 + 人大金仓读写分离 + 分库分表 + 集群高可用 全栈实战
java·数据库·mysql·政务
辰海Coding17 小时前
MiniSpring框架学习-完成的 IoC 容器
java·spring boot·学习·架构
小小编程路17 小时前
C++ 多线程与并发
java·jvm·c++
1892280486117 小时前
NY352固态MT29F32T08GWLBHD6-24QJ:B
大数据·服务器·人工智能·科技·缓存
AI视觉网奇17 小时前
linux 检索库 判断库是否支持
java·linux·服务器
她的男孩17 小时前
从零搭一个企业后台,为什么我把能力拆成 Starter 和 Plugin
java·后端·架构
RainCity17 小时前
Java Swing 自定义组件库分享(七)
java·笔记·后端