
MyBatis核心体系与缓存机制全解
一、MyBatis核心组件体系(分层架构)
MyBatis采用分层架构设计,核心组件按职责可分为6层,各组件职责单一、协作完成SQL解析、执行、结果映射全流程。
1. 构建层:配置与工厂初始化
- SqlSessionFactoryBuilder:采用建造者模式,解析XML/注解配置文件,构建全局
Configuration对象,最终生成SqlSessionFactory。仅用于系统初始化,用完即弃。 - Configuration:MyBatis全局配置容器,承载数据源、Mapper映射、全局设置、插件等所有配置信息,应用生命周期内单例。
2. 会话层:用户交互入口
- SqlSessionFactory:工厂模式的核心,负责创建
SqlSession实例,同时管理数据库连接池。应用运行期间单例,线程安全。 - SqlSession:用户与数据库交互的会话入口,提供CRUD、事务控制等API。代表一次数据库连接会话,线程不安全,每次请求独立创建、用完即关闭。
3. 代理层:Mapper动态代理
- MapperProxyFactory:为Mapper接口创建动态代理对象的工厂
- MapperProxy:动态代理实现类,拦截Mapper接口方法调用,将方法映射为对应的
MappedStatement,转发给执行层处理。
4. 执行层:SQL执行调度
Executor是SQL执行的调度核心,负责缓存管理、事务管理、语句调度,有3种原生实现:
- SimpleExecutor:默认执行器,每次执行都创建新的Statement对象
- ReuseExecutor:复用预编译Statement,提升重复查询性能
- BatchExecutor:批量执行优化器,专门用于批量增删改场景
- CachingExecutor:装饰器模式实现,为Executor增加二级缓存能力,开启二级缓存时自动包装。
5. 处理层:JDBC交互核心
- StatementHandler:JDBC Statement处理器,负责Statement创建、参数设置、SQL执行,对应三种实现:SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler。
- ParameterHandler:参数处理器,将Java类型参数转换为JDBC类型,绑定到Statement中。
- ResultSetHandler:结果集处理器,将JDBC ResultSet结果集映射为Java实体对象。
6. 支撑层:通用扩展能力
- TypeHandler:类型处理器,完成Java类型与JDBC类型的双向转换,内置常用类型处理器,支持自定义扩展。
- Interceptor:插件拦截器,基于责任链模式,可拦截Executor、StatementHandler、ParameterHandler、ResultSetHandler四大对象,实现功能扩展。
7. 完整调用链路
SqlSessionFactoryBuilder → 解析配置生成Configuration → 创建SqlSessionFactory → 开启SqlSession → 获取Mapper代理对象 → MapperProxy转发给Executor → Executor查询缓存 → 无缓存则调用StatementHandler → ParameterHandler设置参数 → 执行SQL → ResultSetHandler映射结果 → 结果写入缓存 → 返回业务层。
二、一级缓存 vs 二级缓存:深度对比
MyBatis内置两级缓存机制,查询优先级为:二级缓存 → 一级缓存 → 数据库。一级缓存为会话级默认开启,二级缓存为全局级需手动开启。
1. 一级缓存(本地缓存)详解
- 作用范围:单个SqlSession内部私有,同一会话内共享
- 生命周期:随SqlSession创建初始化,随SqlSession关闭销毁
- 实现位置:
BaseExecutor类的localCache属性,底层为PerpetualCache - 工作流程:
- 执行查询前,根据SQL、参数、分页等信息生成唯一CacheKey
- 先查询本地缓存,命中则直接返回结果
- 未命中则查询数据库,将结果写入本地缓存后返回
- 核心配置:
localCacheScope,可选值:SESSION(默认):整个会话期间共享缓存STATEMENT:每次SQL执行后清空缓存,相当于禁用一级缓存
- 特点:强制开启无法完全关闭;无序列化要求;性能损耗极低。
2. 二级缓存(全局缓存)详解
- 作用范围:同一个namespace(Mapper)下的所有SqlSession共享
- 生命周期:与应用程序同生命周期,按namespace独立存储
- 实现位置:存储于
MappedStatement中,由CachingExecutor装饰器管理 - 工作流程:
- 开启二级缓存后,Executor被包装为CachingExecutor
- 查询时优先查询二级缓存,命中直接返回
- 未命中则查询一级缓存,再未命中则访问数据库
- 查询结果先存入一级缓存,SqlSession事务提交后,才将数据刷入二级缓存
- 开启三条件:
- 全局开关:
<setting name="cache-enabled" value="true"/>(默认开启) - Mapper配置:对应Mapper.xml中添加
<cache/>标签 - 实体要求:缓存的POJO必须实现
Serializable接口
- 全局开关:
- 可配置项:淘汰策略、刷新间隔、缓存大小、是否只读等。
3. 核心维度对比表
| 对比维度 | 一级缓存(本地缓存) | 二级缓存(全局缓存) |
|---|---|---|
| 缓存级别 | SqlSession会话级 | Mapper/namespace级 |
| 默认状态 | 默认开启,不可完全关闭 | 默认关闭,需手动开启 |
| 共享范围 | 单个SqlSession私有 | 同namespace下所有会话共享 |
| 生命周期 | 与SqlSession同生共死 | 与应用程序同生命周期 |
| 实现位置 | BaseExecutor.localCache | MappedStatement.Cache |
| 写入时机 | 查询完成后立即写入 | SqlSession事务提交后才写入 |
| 序列化要求 | 无 | 自带实现要求POJO可序列化 |
| 性能开销 | 极低,无跨会话成本 | 有一定开销,需维护全局缓存 |
| 脏数据风险 | 低,仅单会话内 | 高,跨namespace易产生脏数据 |
| 适用场景 | 单次请求内重复查询 | 读多写少、单表、一致性要求低的场景 |
三、缓存失效场景全梳理
1. 一级缓存失效的7种场景
- 跨SqlSession访问:一级缓存是会话私有,不同SqlSession之间缓存完全隔离,无法互相命中。
- 同会话执行增删改:同一个SqlSession中执行insert/update/delete时,无论事务是否提交,都会立即清空整个一级缓存。
- 手动清空缓存:调用
sqlSession.clearCache()方法,主动清空当前会话的一级缓存。 - 查询条件不一致:缓存Key由SQL、参数、分页、statementId等共同决定,任意要素不同则Key不同,无法命中。
- localCacheScope设为STATEMENT:全局配置将本地缓存范围设为STATEMENT,每次查询后自动清空一级缓存。
- 查询语句设置flushCache=true:
<select>标签中配置flushCache="true",每次执行该查询前强制清空一、二级缓存。 - SqlSession关闭或提交:SqlSession调用close()或commit()后,会话销毁,一级缓存随之清空。
2. 二级缓存失效/脏数据的8种场景
- 未正确开启配置:全局
cache-enabled=false,或Mapper未配置<cache/>标签,二级缓存不生效。 - 跨namespace查询:二级缓存以namespace为边界,不同Mapper之间缓存不共享,跨Mapper查询无法命中。
- 对应namespace执行增删改:某个namespace下执行写操作时,会清空该namespace下的所有二级缓存。
- 事务未提交:二级缓存必须等事务提交后才会写入,未提交事务的查询结果不会进入全局缓存。
- 跨namespace多表修改:A Mapper的多表查询关联了B表,B表在B Mapper中被修改时,A的二级缓存不会自动刷新,产生脏数据。
- 实体类未序列化:使用自带二级缓存时,POJO未实现Serializable接口,会抛出序列化异常,缓存无法存储。
- 查询设置useCache=false:
<select>标签中配置useCache="false",该查询结果不会写入二级缓存。 - 分布式多节点环境:自带二级缓存是进程内缓存,集群下不同节点缓存不同步,出现数据不一致与失效。
四、缓存底层核心机制
1. 缓存Key生成规则
MyBatis通过CacheKey对象唯一标识一个查询,生成要素包括:
- MappedStatement的ID(SQL语句唯一标识)
- 分页参数(offset、limit)
- 原生SQL语句
- 用户传递的查询参数值
- 运行环境ID
任意要素不同,生成的CacheKey就不同,缓存无法命中。
2. 存储实现与淘汰策略
- 底层存储:默认
PerpetualCache实现,本质是HashMap<Object, Object>,简单内存存储。 - 淘汰策略:二级缓存支持4种内置淘汰策略:
- LRU(默认):最近最少使用,移除最长时间未访问的对象
- FIFO:先进先出,按对象进入缓存的顺序移除
- SOFT:软引用,基于GC状态和软引用规则回收
- WEAK:弱引用,更积极地基于弱引用规则回收
3. 事务与缓存的同步机制
- 一级缓存:增删改执行时立即清空缓存,与事务是否提交无关,保证同会话内数据一致性。
- 二级缓存:查询结果先暂存一级缓存,事务提交后才刷入二级缓存;回滚则直接丢弃,避免脏数据进入全局缓存。
- 写操作同步:执行增删改时,同步清空对应namespace的二级缓存(仅单namespace范围内有效)。
五、工程实践最佳建议
- 一级缓存默认即可:无需额外配置,单次请求内重复查询可自动命中;避免长会话持有缓存,及时关闭SqlSession。
- 二级缓存谨慎使用:不建议在复杂业务、多表关联场景使用,极易产生脏数据;仅适用于单表、读多写少、一致性要求低的场景(如字典表、配置表)。
- 分布式场景替代方案:集群/分布式系统中,禁用MyBatis自带二级缓存,改用Redis等分布式缓存,保证缓存一致性与共享性。
- 脏数据规避:若使用二级缓存,尽量将关联表放在同一个namespace下,减少跨namespace修改。
- 实时性查询优化:对数据一致性要求高的实时查询,设置
useCache=false绕过缓存,直接访问数据库。