深入理解 MyBatis 的一级缓存与二级缓存机制

共计 2879 个字符,预计需要花费 8 分钟才能阅读完成。

在 MyBatis 的性能优化体系中,缓存机制(一级缓存 & 二级缓存) 是最常见却又容易被误用的部分。本文将从 概念、执行流程、源码原理、失效机制、性能陷阱 以及 自定义实现 多角度,深入解析 MyBatis 缓存体系,帮助你在企业开发中更合理地用好它。

一、一级缓存:SqlSession 级别的本地缓存

1.1 基本概念

一级缓存是 MyBatis 默认开启的本地缓存,它存在于当前 SqlSession 的生命周期中。简单来说,只要在一个 SqlSession 内部执行相同的 SQL 且参数一致,第二次查询就不会再访问数据库。

User user1 = sqlSession.selectOne("selectUserById", 1);
User user2 = sqlSession.selectOne("selectUserById", 1);
System.out.println(user1 == user2); // true

1.2 底层结构解析

MyBatis 的一级缓存本质上是一个 PerpetualCache,包装在 BaseExecutorlocalCache 中:

protected PerpetualCache localCache = new PerpetualCache("LocalCache");

缓存的 key 是 CacheKey 对象,由以下因素构建:

  • MappedStatement ID
  • SQL 字符串
  • 参数值(通过 MetaObject)
  • RowBounds 分页信息
CacheKey key = executor.createCacheKey(ms, parameterObject, rowBounds, boundSql);

1.3 一级缓存失效的几种情况

不同的 SqlSession

  • 一级缓存作用域仅限当前 SqlSession

执行了更新操作(INSERT、UPDATE、DELETE)

  • 会清空一级缓存,防止脏读(注意:如果你“直接修改数据库”(绕过 MyBatis),MyBatis 的缓存(一级、二级)都不会感知到这些变更,因此缓存数据不会被更新或清除。)

手动清除缓存

  • sqlSession.clearCache();

查询使用了 flushCache=true 的配置

  • 例如 <select flushCache="true">

二、二级缓存:Mapper 映射级别的跨 SqlSession 缓存

2.1 基本概念

二级缓存是 MyBatis 提供的可选缓存机制,其作用域是 Mapper 映射级别。它可以跨多个 SqlSession 实现数据共享,提高数据库访问效率。

开启方式如下:

<!-- 全局配置 -->
<settings>
  <setting name="cacheEnabled" value="true"/>
</settings>

<!-- 映射器中开启二级缓存 -->
<mapper namespace="com.xxx.UserMapper">
  <cache/>
</mapper>

2.2 数据流与缓存路径

select 查询:

Client SqlSession Executor 一级缓存 miss 二级缓存 check 二级缓存 miss查询数据库放入一级和二级缓存

insert/update/delete:

清除当前 Mapper 下的二级缓存(标记为 dirty)

2.3 底层结构与源码分析

MyBatis 二级缓存通过装饰器模式构建缓存链:

Cache cache = new PerpetualCache("com.simon.UserMapper");
cache = new LoggingCache(cache);
cache = new SynchronizedCache(cache);
cache = new SerializedCache(cache);
cache = new LruCache(cache);
组件 功能说明
PerpetualCache 最基础的 HashMap 实现
LruCache 最近最少使用缓存策略
SerializedCache 支持序列化存储
LoggingCache 打印缓存日志
TransactionalCache 控制事务提交时再写入缓存

2.4 缓存的写入时机

二级缓存不会在查询时立即写入,而是通过 TransactionalCacheManager 延迟到事务提交时。

所以只有在 sqlSession.commit() 后,查询结果才会写入二级缓存。

三、缓存的失效机制与风险点

3.1 二级缓存默认不开启刷新策略

<cache /> 默认不会根据数据库变化自动刷新,可能会造成 脏读风险

解决方式:

  • 配合使用数据库通知机制(如 Binlog + Canal)主动刷新
  • 使用过期时间控制 <cache eviction="LRU" flushInterval="60000"/>

延申知识:

默认情况下:MyBatis 的二级缓存无法主动刷新:因为它的设计非常简单:基于 namespace 的内存 Map,没有监听机制,也不支持外部主动更新缓存。

正确的做法:清除/刷新当前 namespace 的二级缓存

方法一:手动清除缓存(推荐方式)

sqlSession.clearCache();

方法二:调用写操作(insert/update/delete)时,MyBatis 会自动清除二级缓存

<update id="updateUser" flushCache="true">...</update>

3.2 查询方法的 flushCache 默认值

<select flushCache="false"/>
<insert|update|delete flushCache="true"/>

更新操作自动刷新缓存,查询操作默认不刷新,除非手动指定。

四、自定义二级缓存:整合 Redis 缓存

MyBatis 支持自定义缓存实现,只需实现 org.apache.ibatis.cache.Cache 接口。如集成Redis,Caffeine等。

具体实现后续写文章详细描述

五、一级与二级缓存对比总结

对比项 一级缓存 二级缓存
生命周期 SqlSession 生命周期 Mapper 映射级别,跨 SqlSession
默认开启 否,需要显式声明 <cache/>
并发安全 否(需手动实现线程安全方案)
持久化支持 是(可整合 Redis、Ehcache)
清除机制 自动(更新即清除) Mapper 层更新时清除
数据一致性 弱(延迟提交、更新不可见)

六、最佳实践建议

  1. 默认使用一级缓存即可满足大部分场景需求
  2. 开启二级缓存需谨慎,配合更新操作及时清除缓存或设置 TTL
  3. 对于复杂系统,建议将二级缓存接入 Redis,并搭建统一缓存策略
  4. 避免在并发密集的微服务场景下使用二级缓存(可能造成数据一致性风险)
  5. 开启 SQL 日志,观察是否命中缓存(LoggingCache 会打印命中信息)

七、总结

MyBatis 的一级缓存是轻量、可靠的默认特性,而二级缓存则提供了更多性能可能,但也伴随一致性挑战。理解缓存的实现原理与生命周期,是避免误用的关键。

合理使用缓存机制,可以在不增加数据库负载的前提下,大幅提升系统响应性能。建议在大型项目中建立统一的缓存策略与架构规范,使 MyBatis 缓存体系服务于整体性能目标。


我是 李卷卷,专注Java相关知识输出。感谢阅读!

正文完
 0
admin
版权声明:本站原创文章,由 admin 于2024-07-15发表,共计2879字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)