【ORM 框架】MyBatis 核心组件、一级缓存 vs 二级缓存、缓存失效场景

简介: 本文系统解析MyBatis核心分层架构(构建层、会话层、代理层、执行层、处理层、支撑层)与两级缓存机制,深入对比一级缓存(SqlSession级、默认开启)与二级缓存(namespace级、需手动配置)的原理、生命周期、失效场景及底层实现,并给出分布式环境下的工程实践建议。

思维导图

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
  • 工作流程
    1. 执行查询前,根据SQL、参数、分页等信息生成唯一CacheKey
    2. 先查询本地缓存,命中则直接返回结果
    3. 未命中则查询数据库,将结果写入本地缓存后返回
  • 核心配置localCacheScope,可选值:
    • SESSION(默认):整个会话期间共享缓存
    • STATEMENT:每次SQL执行后清空缓存,相当于禁用一级缓存
  • 特点:强制开启无法完全关闭;无序列化要求;性能损耗极低。

2. 二级缓存(全局缓存)详解

  • 作用范围:同一个namespace(Mapper)下的所有SqlSession共享
  • 生命周期:与应用程序同生命周期,按namespace独立存储
  • 实现位置:存储于MappedStatement中,由CachingExecutor装饰器管理
  • 工作流程
    1. 开启二级缓存后,Executor被包装为CachingExecutor
    2. 查询时优先查询二级缓存,命中直接返回
    3. 未命中则查询一级缓存,再未命中则访问数据库
    4. 查询结果先存入一级缓存,SqlSession事务提交后,才将数据刷入二级缓存
  • 开启三条件
    1. 全局开关:<setting name="cache-enabled" value="true"/>(默认开启)
    2. Mapper配置:对应Mapper.xml中添加<cache/>标签
    3. 实体要求:缓存的POJO必须实现Serializable接口
  • 可配置项:淘汰策略、刷新间隔、缓存大小、是否只读等。

3. 核心维度对比表

对比维度 一级缓存(本地缓存) 二级缓存(全局缓存)
缓存级别 SqlSession会话级 Mapper/namespace级
默认状态 默认开启,不可完全关闭 默认关闭,需手动开启
共享范围 单个SqlSession私有 同namespace下所有会话共享
生命周期 与SqlSession同生共死 与应用程序同生命周期
实现位置 BaseExecutor.localCache MappedStatement.Cache
写入时机 查询完成后立即写入 SqlSession事务提交后才写入
序列化要求 自带实现要求POJO可序列化
性能开销 极低,无跨会话成本 有一定开销,需维护全局缓存
脏数据风险 低,仅单会话内 高,跨namespace易产生脏数据
适用场景 单次请求内重复查询 读多写少、单表、一致性要求低的场景

三、缓存失效场景全梳理

1. 一级缓存失效的7种场景

  1. 跨SqlSession访问:一级缓存是会话私有,不同SqlSession之间缓存完全隔离,无法互相命中。
  2. 同会话执行增删改:同一个SqlSession中执行insert/update/delete时,无论事务是否提交,都会立即清空整个一级缓存。
  3. 手动清空缓存:调用sqlSession.clearCache()方法,主动清空当前会话的一级缓存。
  4. 查询条件不一致:缓存Key由SQL、参数、分页、statementId等共同决定,任意要素不同则Key不同,无法命中。
  5. localCacheScope设为STATEMENT:全局配置将本地缓存范围设为STATEMENT,每次查询后自动清空一级缓存。
  6. 查询语句设置flushCache=true<select>标签中配置flushCache="true",每次执行该查询前强制清空一、二级缓存。
  7. SqlSession关闭或提交:SqlSession调用close()或commit()后,会话销毁,一级缓存随之清空。

2. 二级缓存失效/脏数据的8种场景

  1. 未正确开启配置:全局cache-enabled=false,或Mapper未配置<cache/>标签,二级缓存不生效。
  2. 跨namespace查询:二级缓存以namespace为边界,不同Mapper之间缓存不共享,跨Mapper查询无法命中。
  3. 对应namespace执行增删改:某个namespace下执行写操作时,会清空该namespace下的所有二级缓存。
  4. 事务未提交:二级缓存必须等事务提交后才会写入,未提交事务的查询结果不会进入全局缓存。
  5. 跨namespace多表修改:A Mapper的多表查询关联了B表,B表在B Mapper中被修改时,A的二级缓存不会自动刷新,产生脏数据。
  6. 实体类未序列化:使用自带二级缓存时,POJO未实现Serializable接口,会抛出序列化异常,缓存无法存储。
  7. 查询设置useCache=false<select>标签中配置useCache="false",该查询结果不会写入二级缓存。
  8. 分布式多节点环境:自带二级缓存是进程内缓存,集群下不同节点缓存不同步,出现数据不一致与失效。

四、缓存底层核心机制

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范围内有效)。

五、工程实践最佳建议

  1. 一级缓存默认即可:无需额外配置,单次请求内重复查询可自动命中;避免长会话持有缓存,及时关闭SqlSession。
  2. 二级缓存谨慎使用:不建议在复杂业务、多表关联场景使用,极易产生脏数据;仅适用于单表、读多写少、一致性要求低的场景(如字典表、配置表)。
  3. 分布式场景替代方案:集群/分布式系统中,禁用MyBatis自带二级缓存,改用Redis等分布式缓存,保证缓存一致性与共享性。
  4. 脏数据规避:若使用二级缓存,尽量将关联表放在同一个namespace下,减少跨namespace修改。
  5. 实时性查询优化:对数据一致性要求高的实时查询,设置useCache=false绕过缓存,直接访问数据库。
相关文章
JavaScript API 开发工具
152 3
Windows
455 1
弹性计算
91 0
SQL Java 数据库连接
26 2
SQL Java 数据库连接
27 1
SQL Java 数据库连接
21 0
人工智能 监控 开发者
39 0
消息中间件 存储 弹性计算
23 0
人工智能 应用服务中间件 PHP
34 4
Web App开发 人工智能 前端开发
32 1

热门文章

最新文章