1. 项目概述:MyBatis三级缓存机制深度剖析
如果你用过MyBatis,肯定对“缓存”这个词不陌生。面试的时候,面试官也总爱问:“聊聊MyBatis的一级缓存和二级缓存?” 但很多人可能不知道,或者只是模糊地听说过,MyBatis其实存在一个“三级缓存”的概念。这并不是官方文档里明确划分的一个独立层级,而是我们开发者根据其缓存的作用域和生命周期,结合Spring等框架的集成,在实践中总结出来的一套完整的缓存视图。今天,我们就来彻底拆解这个“三级缓存”体系,搞明白每一级缓存是什么、怎么工作、以及最关键的——它们在实际项目中是怎么相互配合,又是怎么给我们“挖坑”的。
简单来说,MyBatis的三级缓存可以理解为:
- 一级缓存(SqlSession级别) :默认开启,作用域最小,生命周期与一次数据库会话绑定。
- 二级缓存(Mapper/Namespace级别) :需要手动配置开启,作用域跨SqlSession,可以被多个会话共享。
- 三级缓存(应用/框架集成级别) :这通常指的是集成如Spring Cache、Redis等外部缓存框架后形成的应用级缓存,其作用域最大,可以跨应用实例共享。
理解这套机制,不仅能帮你解决“为什么我查的数据是旧的?”、“开了事务怎么查不到最新数据?”这类诡异问题,更是优化应用性能、设计高可用数据访问层的必备知识。无论你是正在被MyBatis缓存问题困扰的开发者,还是准备面试想深入原理的求职者,这篇文章都将带你从使用到源码,从配置到避坑,完整地走一遍。
2. 三级缓存整体架构与核心设计思想
在深入每一级缓存之前,我们得先站在高处,看看MyBatis设计这套缓存机制的初衷和整体蓝图。MyBatis作为一个半自动化的ORM框架,其核心价值之一就是在对象和关系数据库之间提供灵活、高效的映射。缓存,就是为了减少对数据库的直接访问,这个目标服务的。
2.1 为什么需要多级缓存?
想象一下图书馆借书。一级缓存就像你手边正在看的这本书,取用最快,但离开座位(关闭SqlSession)就得还回去。二级缓存像是这个阅览室的书架,同一个房间(同一个Mapper)的人都能看,但别的阅览室(别的Mapper)的人拿不到。三级缓存则像是图书馆的中央书库,所有读者(甚至其他分馆的应用)在权限内都可以借阅。
MyBatis采用这种分层设计,核心思想是 在数据一致性、性能与资源消耗之间取得平衡 。
- 性能与速度 :一级缓存速度最快,因为数据就在内存中的SqlSession对象里。二级缓存次之,需要序列化/反序列化和跨会话查找。三级缓存(如Redis)可能涉及网络IO,速度最慢,但共享能力最强。
- 数据一致性 :缓存层级越高,数据共享范围越广,保持一致性就越复杂。一级缓存只影响自己,很容易通过关闭会话来清空。二级缓存需要处理多个会话的并发更新。三级缓存则要面对分布式环境下的数据同步难题。
- 作用域与生命周期 :这是分级的关键。从一次请求(SqlSession)、到一个业务模块(Mapper)、再到整个应用乃至集群,缓存的生命周期和作用域逐级扩大,应对不同的场景需求。
2.2 各级缓存的核心职责与交互关系
三级缓存并非完全独立,它们在执行一次查询时,遵循着一个清晰的查询链,这通常被称为 缓存查询顺序 。
当执行一条查询语句时(例如 select * from user where id = #{id} ),MyBatis的 Executor 执行器会按以下顺序查找数据:
- 首先查询一级缓存 :检查当前
SqlSession中是否存在该查询的缓存结果。如果命中,直接返回,不会执行后续步骤。这是最快的一条路径。 - 然后查询二级缓存 :如果一级缓存未命中,且当前
Mapper配置启用了二级缓存,则查询二级缓存。二级缓存底层是一个PerpetualCache对象,但被各种装饰器(如序列化、LRU淘汰、同步锁等)包装,存储的是序列化后的数据。如果命中,将数据反序列化后返回,同时 放入当前SqlSession的一级缓存中 (注意这点,很重要)。 - 最后查询数据库 :如果一、二级缓存均未命中,则执行JDBC操作,访问数据库获取数据。拿到数据后:
- 将结果放入 当前SqlSession的一级缓存 。
- 如果启用了二级缓存,同时会将结果放入 二级缓存 (同样需要序列化)。
至于 三级缓存 ,它通常不在MyBatis这个默认查询链里。它更像是一个“旁路缓存”,由开发者通过Spring的 @Cacheable 注解或手动操作Redis客户端来实现。它的优先级和集成方式由应用架构决定,可能在一、二级缓存之前拦截,也可能作为二级缓存的分布式存储后端。
注意 :这个顺序解释了为什么有时你开启了二级缓存,感觉效果却不明显。因为如果你的操作都在同一个SqlSession内(比如一个事务方法中多次查询同一数据),请求会被一级缓存拦截,根本走不到二级缓存那一步。
3. 一级缓存:SqlSession级别的“私人工作区”
一级缓存是MyBatis中最基础、最直接的缓存,理解它是理解所有缓存问题的起点。
3.1 工作原理与生命周期
一级缓存本质上是一个HashMap,它的键是 CacheKey 。这个 CacheKey 由多个要素共同决定,确保查询的唯一性:
- Mapper的Id(即命名空间+方法名)
- 查询的偏移量(分页参数)
- 查询的SQL语句本身
- 传递给SQL的实际参数值
- 环境Id(比如你配置的多数据源)
只要这些要素完全相同,MyBatis就认为这是同一次查询,会尝试从一级缓存中直接返回结果。
它的生命周期与 SqlSession 绑定:
- 创建 :当调用
SqlSessionFactory.openSession()方法时,一个新的SqlSession被创建,同时一个全新的一级缓存(一个PerpetualCache对象)也随之创建。 - 存活 :在该
SqlSession存活期间,所有查询的结果(除非配置了flushCache=true)都会被存入这个HashMap。 - 销毁 :调用
SqlSession.close()方法,或者这个SqlSession对象被垃圾回收时,一级缓存随之销毁。这也是为什么我们常说“一级缓存默认是开启的”


656

被折叠的 条评论
为什么被折叠?



