# 体系结构与工作原理
作者:Ethan.Yang
博客:https://blog.ethanyang.cn (opens new window)
# 工作流程分析
解析配置文件
在MyBatis启动要去解析配置文件, 包括全局配置文件和映射器配置文件, 会将它解析为 Configuration 对象。
提供操作接口
解析完配置文件后, 会提供 SqlSession 对象, 它可以代表应用程序和数据库之间的一次连接, MyBatis提供了一个 SqlSessionFactory 工厂, 用来获取一个 SqlSession
执行SQL操作
SqlSession 会持有一个 Executor 对象, 该对象用于封装对数据库的操作。Executor 在执行query 或 update等操作时, 会生成一系列对象用来处理结果集, 可以统一为 StatementHandler 即对JDBC Statement 的封装。

# 总架构分层与模块划分

接口层
核心对象是 SqlSession, 它是上层应用和 MyBatis 打交道的桥梁, SqlSession 上定义了非常多的对数据库的操作方法。接口层在接收到调用请求的时候,会调用核心处理层的相应模块来完成具体的数据库操作。
核心处理层
和数据库操作相关的动作是在这一层完成的。大致步骤如下
- 把接口中传入的参数解析并且映射成JDBC 类型:
- 解析 xml 文件中的 SQL语句, 包括插入参数, 和动态 SQL 的生成;
- 执行 SQL语句;
- 处理结果集, 并映射成 Java 对象。
基础支持层
基础支持层主要是一些抽取出来的通用的功能为了复用, 用来支持核心处理层的功能。
# MyBatis 缓存详解
# 1. MyBatis 缓存体系结构
MyBatis 跟缓存相关的类都在cache 包里面,其中有一个 Cache 接口, 其有一个默认实现 PerpetualCache, 该缓存由HashMap实现。使用了装饰器模式对Cache 接口进行包装, 从而实现一系列额外功能的缓存。
# 2. 一级缓存(Local Cache)
MyBatis 的一级缓存是作用在 SqlSession 层级的本地缓存,默认开启,不需要任何配置。其主要作用是避免在同一个会话中对相同 SQL 重复访问数据库。
# 开关配置
若希望关闭一级缓存,可以通过设置 LocalCacheScope.STATEMENT 实现。此配置可在 MyBatis 全局配置文件或 Java 配置类中设置:
configuration.setLocalCacheScope(LocalCacheScope.STATEMENT);
或者配置
mybatis:
configuration:
local-cache-scope: STATEMENT
2
3
底层实现位于 BaseExecutor:
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
2
3
4
# 缓存结构与位置
一级缓存由 Executor 管理。DefaultSqlSession 包含 Configuration 和 Executor 两个核心成员,其中:
Configuration是全局配置,多个会话共享;Executor是每个会话(SqlSession)独立持有;- 缓存由
BaseExecutor抽象类中的PerpetualCache实现。
如下为其构造器中的部分代码:
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
2
3
4
5
6
7
8
9
# 缓存生效条件
当在 同一个 SqlSession 中多次执行相同 SQL(包括参数和分页条件),且未显式清除缓存时,将直接命中缓存,避免重复访问数据库。
注意:不同的 SqlSession 实例不共享一级缓存,每个 SqlSession 都会维护自己的 Executor 和缓存。
对于分库或多数据源配置场景:
# Spring 多数据源或分库配置
jdbc:mysql://localhost:3306/user_db_0
jdbc:mysql://localhost:3306/user_db_1
2
3
在一次请求中访问多个库时,MyBatis 会创建多个 SqlSession,每个库维护独立的缓存。

# 一级缓存相关机制分析
缓存的读取与写入时机
查询方法中,MyBatis 会优先尝试从本地缓存获取结果,如果获取不到则会访问数据库并将结果写入缓存。
关键代码如下:
// BaseExecutor.query() 尝试从一级缓存获取 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list == null) { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } // queryFromDatabase 内部实现 localCache.putObject(key, EXECUTION_PLACEHOLDER); // 占位符防止缓存穿透 try { list = doQuery(...); // 执行 SQL 查询 } finally { localCache.removeObject(key); // 清除占位符 } localCache.putObject(key, list); // 写入一级缓存1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16缓存的清除时机
执行 update 操作时自动清除缓存:
@Override public int update(MappedStatement ms, Object parameter) throws SQLException { clearLocalCache(); // 更新操作时清除缓存 return doUpdate(ms, parameter); }1
2
3
4
5查询时若配置了 flushCache=true,也会清除缓存:
if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); // 主查询时根据配置决定是否清除缓存 }1
2
3
缓存作用域为 SqlSession,会话之间不共享
由于一级缓存是基于 SqlSession 存储的,不同会话之间不共享缓存。如果某个会话中更新了数据,而另一个会话中仍存在缓存数据,则可能导致脏读或过期读。
这也是一级缓存在实际分布式或高并发场景中存在的局限性之一,通常通过启用 二级缓存 来扩大作用范围并保持一致性。
# 3. 二级缓存
二级缓存的范围是 namespace 级别的, 可以被多个 SqlSession 共享(同一个接口里的相同方法都可以共享), 生命周期和应用同步。
既然二级缓存的作用范围比一级缓存大, 那么肯定是放在BaseExecutor外层, MyBatis通过CachingExecutor装饰器类来维护, 如果启用了二级缓存, 在创建Executor对象后会对Executor进行装饰, 在查询时会先判断二级缓存中是否有缓存结果, 如果没有会委派给真正的查询器Executor实现类来执行, 比如SimpleExecutor, 查询后将结果缓存并返回
# 开关配置
全局开关
MyBatis 默认启用二级缓存,通过
cacheEnabled控制是否启用全局二级缓存功能:<settings> <setting name="cacheEnabled" value="true"/> </settings>1
2
3若设置为
false,所有 Mapper 的二级缓存功能都将被禁用。Mapper 层开关(必须显式配置)
默认情况下,只有在 Mapper.xml 中添加
<cache/>标签,才会为该 Mapper 创建对应的缓存实例:<cache eviction="LRU" flushInterval="60000" size="512" readOnly="false"/>1SQL 级别控制
useCache="false":禁用当前select语句的二级缓存功能flushCache="true":默认用于insert/update/delete,表示操作后清空该 namespace 下的二级缓存 可用于select强制刷新缓存<select id="query" useCache="true" flushCache="false"> SELECT * FROM user WHERE id = #{id} </select>1
2
3
# 缓存结构与位置
缓存作用域
- 一级缓存(localCache):基于
SqlSession作用域,每次请求结束后即失效 - 二级缓存:基于 Mapper(namespace)级别,所有
SqlSession实例共享缓存数据
缓存位置
- 一级缓存存储在
BaseExecutor的成员变量localCache中(本地内存) - 二级缓存并不直接存在于
Executor中,而是通过CachingExecutor来代理操作 Mapper 对应的Cache实例(装饰器)
缓存的持久化结构
- 默认使用
PerpetualCache作为存储容器(Map 结构) - 可通过组合如
LRUCache、FIFO、SoftCache、WeakCache等实现策略装饰 - 支持自定义实现
org.apache.ibatis.cache.Cache接口,集成 Redis、Ehcache 等第三方缓存中间件
一般建议自定义二级缓存, mybatis的二级缓存容量不可控, 并且存储的数据一般都是查询语句的结果, 常用的字典等缓存生产中多是Map<key,value>结构, 并且在多节点难以维护一致性, 需要结合redis等组件使用。
源码解读 →