思维导图

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.localCacheMappedStatement.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绕过缓存,直接访问数据库。
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐