MyBatis 执行器(Executor)深度解析:从架构到实战
Executor 是 MyBatis 的核心执行引擎,负责数据库操作的调度、缓存管理与事务控制,是 SqlSession 方法的实际执行者。MyBatis 通过 ExecutorType 提供三种基础执行器(SimpleExecutor/ReuseExecutor/BatchExecutor),并通过 CachingExecutor 实现二级缓存,形成 “基础执行 + 缓存增强” 的装饰器架构。从 “架构设计→核心实现→缓存机制→实战选型” 四个维度,彻底拆解 Executor 的工作原理。
Executor 核心定位与架构
核心职责
Executor 是 MyBatis 与数据库交互的 “总指挥”,核心职责包括:
- 调度
StatementHandler/ParameterHandler/ResultSetHandler 完成 SQL 执行;
- 管理一级缓存(会话级)和二级缓存(应用级);
- 控制事务提交 / 回滚,处理批量 SQL 执行;
- 生成缓存键(
CacheKey),实现缓存命中逻辑。
架构设计:接口 + 抽象基类 + 具体实现
MyBatis 采用 “接口定义规范 + 抽象类封装通用逻辑 + 具体类实现差异化功能” 的设计,同时通过装饰器模式(CachingExecutor)增强缓存能力:
- 接口(Executor):定义数据库操作的核心方法(
update/query/commit/rollback 等);
- 抽象基类(BaseExecutor):实现通用逻辑(一级缓存、事务管理、延迟加载),暴露
doUpdate/doQuery 等抽象方法由子类实现;
- 具体执行器:实现差异化 SQL 执行逻辑(如 Statement 复用、批量处理);
- 装饰器(CachingExecutor):包装基础执行器,添加二级缓存功能,不改变原有执行逻辑。
ExecutorType 枚举:执行器类型
MyBatis 通过 ExecutorType 枚举指定执行器类型,决定 SQL 执行策略:
| ExecutorType |
对应执行器 |
核心特点 |
适用场景 |
SIMPLE |
SimpleExecutor |
每次执行 SQL 新建 Statement,执行后关闭 |
大多数单条 SQL 场景(默认类型) |
REUSE |
ReuseExecutor |
按 SQL 缓存 Statement,复用避免重建 |
重复执行相同 SQL(如循环查询) |
BATCH |
BatchExecutor |
缓存多条 SQL,批量提交 |
批量插入 / 更新 / 删除(减少网络开销) |
Executor 的创建流程
Executor 通过 Configuration.newExecutor() 方法创建,核心逻辑是 “根据 ExecutorType 生成基础执行器,若开启二级缓存则用 CachingExecutor 包装”:
1. 核心源码:Configuration.newExecutor ()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor; switch (executorType) { case BATCH: executor = new BatchExecutor(this, transaction); break; case REUSE: executor = new ReuseExecutor(this, transaction); break; case SIMPLE: default: executor = new SimpleExecutor(this, transaction); break; }
if (cacheEnabled) { executor = new CachingExecutor(executor); }
executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
|
关键细节:
- 装饰器模式:
CachingExecutor 不直接执行 SQL,而是委托给包装的基础执行器(如 SimpleExecutor),仅在执行前后添加缓存逻辑;
- 插件支持:通过
interceptorChain.pluginAll() 生成代理对象,支持拦截 Executor 方法(如 PageHelper 拦截 query 方法实现分页)。
BaseExecutor:抽象基类的通用逻辑
BaseExecutor 是所有基础执行器的父类,封装了一级缓存、事务管理、延迟加载等通用逻辑,暴露 4 个抽象方法(doUpdate/doQuery/doQueryCursor/doFlushStatements)由子类实现。
1. 核心属性与一级缓存
1 2 3 4 5 6 7 8 9 10
| public abstract class BaseExecutor implements Executor { protected Transaction transaction; protected Executor wrapper; protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads; protected PerpetualCache localCache; protected PerpetualCache localOutputParameterCache; protected Configuration configuration; protected int queryStack; private boolean closed; }
|
一级缓存(LocalCache)核心机制:
一级缓存是会话级缓存,生命周期与 SqlSession 一致(即与 Executor 生命周期一致),默认开启,无需额外配置。
(1)缓存命中逻辑(query 方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
public <E> List<E> query(...) throws SQLException { if (closed) throw new ExecutorException("Executor was closed."); if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); }
List<E> list; try { queryStack++; list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; }
if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { clearLocalCache(); } } return list; }
|
(2)缓存键(CacheKey)的组成
缓存命中的关键是 CacheKey,由 6 个维度组成,确保 “相同查询” 的唯一性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public CacheKey createCacheKey(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) { CacheKey cacheKey = new CacheKey(); cacheKey.update(ms.getId()); cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); cacheKey.update(boundSql.getSql()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); for (ParameterMapping pm : parameterMappings) { if (pm.getMode() != ParameterMode.OUT) { Object value = boundSql.hasAdditionalParameter(pm.getProperty()) ? boundSql.getAdditionalParameter(pm.getProperty()) : (parameter == null ? null : typeHandlerRegistry.hasTypeHandler(parameter.getClass()) ? parameter : configuration.newMetaObject(parameter).getValue(pm.getProperty())); cacheKey.update(value); } } if (configuration.getEnvironment() != null) { cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey; }
|
(3)一级缓存清空时机
一级缓存会在以下场景自动清空,避免脏读:
- 执行
update/insert/delete 方法时(BaseExecutor.update() 先调用 clearLocalCache());
- 调用
SqlSession.commit()/rollback() 时;
- 调用
SqlSession.clearCache() 时;
- 本地缓存作用域为
STATEMENT(语句级)时,执行完 SQL 立即清空。
2. 延迟加载(DeferredLoad)
MyBatis 支持嵌套查询的延迟加载(如一对一 / 一对多关联查询),deferredLoads 队列用于暂存未加载的关联属性,待外层查询结束后统一加载:
1 2 3 4 5 6 7 8
| public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) { DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType); if (deferredLoad.canLoad()) { deferredLoad.load(); } else { deferredLoads.add(deferredLoad); } }
|
- 触发时机:外层查询结束(
queryStack == 0)时,遍历 deferredLoads 执行 load(),从一级缓存中获取关联数据并设置到外层对象。
3. 抽象方法:子类实现差异化逻辑
BaseExecutor 暴露 4 个抽象方法,由子类实现具体的 SQL 执行逻辑:
1 2 3 4 5 6 7 8
| protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException;
|
三大基础执行器:差异化实现
1. SimpleExecutor:默认简易执行器
SimpleExecutor 是 MyBatis 的默认执行器,每次执行 SQL 都创建新的 Statement,执行后立即关闭,逻辑简单但无复用优化。
核心源码:doQuery 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class SimpleExecutor extends BaseExecutor { @Override public <E> List<E> doQuery(...) throws SQLException { Statement stmt = null; try { Configuration config = ms.getConfiguration(); StatementHandler handler = config.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } }
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Connection conn = getConnection(statementLog); Statement stmt = handler.prepare(conn, transaction.getTimeout()); handler.parameterize(stmt); return stmt; } }
|
特点与适用场景
| 优点 |
缺点 |
适用场景 |
| 逻辑简单,无额外缓存开销 |
频繁创建 / 关闭 Statement,性能损耗较大 |
单条 SQL 执行、短期查询(默认场景) |
| 避免 Statement 复用导致的线程安全问题 |
不适合循环执行相同 SQL(如批量查询) |
开发环境、小流量场景 |
2. ReuseExecutor:复用 Statement 执行器
ReuseExecutor 通过 Map 缓存 Statement(key=SQL 语句,value=Statement),相同 SQL 可复用 Statement,减少创建 / 关闭开销。
核心源码:缓存与复用逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| public class ReuseExecutor extends BaseExecutor { private final Map<String, Statement> statementMap = new HashMap<>();
@Override public <E> List<E> doQuery(...) throws SQLException { Statement stmt = null; try { Configuration config = ms.getConfiguration(); StatementHandler handler = config.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.query(stmt, resultHandler); } finally { } }
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { BoundSql boundSql = handler.getBoundSql(); String sql = boundSql.getSql(); Statement stmt;
if (hasStatementFor(sql)) { stmt = getStatement(sql); applyTransactionTimeout(stmt); } else { Connection conn = getConnection(statementLog); stmt = handler.prepare(conn, transaction.getTimeout()); statementMap.put(sql, stmt); }
handler.parameterize(stmt); return stmt; }
private boolean hasStatementFor(String sql) { try { Statement stmt = statementMap.get(sql); return stmt != null && !stmt.getConnection().isClosed(); } catch (SQLException e) { return false; } }
@Override public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException { for (Statement stmt : statementMap.values()) { closeStatement(stmt); } statementMap.clear(); return Collections.emptyList(); } }
|
特点与适用场景
| 优点 |
缺点 |
适用场景 |
| 复用 Statement,减少创建 / 关闭开销 |
缓存需占用内存,多线程下需注意连接关闭问题 |
循环执行相同 SQL(如批量查询) |
| 提升重复 SQL 的执行效率 |
不支持批量更新(需用 BatchExecutor) |
高频重复查询场景(如商品列表查询) |
3. BatchExecutor:批量执行器
BatchExecutor 专为批量 insert/update/delete 设计,通过缓存多条 SQL 并一次性提交,减少网络通信开销(JDBC 批量仅支持 DML,不支持 SELECT)。
核心源码:批量缓存与提交逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| public class BatchExecutor extends BaseExecutor { private final List<Statement> statementList = new ArrayList<>(); private final List<BatchResult> batchResultList = new ArrayList<>(); private String currentSql; private MappedStatement currentStatement;
@Override public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Configuration config = ms.getConfiguration(); StatementHandler handler = config.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); BoundSql boundSql = handler.getBoundSql(); String sql = boundSql.getSql(); Statement stmt;
if (sql.equals(currentSql) && ms.equals(currentStatement)) { int lastIdx = statementList.size() - 1; stmt = statementList.get(lastIdx); applyTransactionTimeout(stmt); handler.parameterize(stmt); BatchResult batchResult = batchResultList.get(lastIdx); batchResult.addParameterObject(parameter); } else { Connection conn = getConnection(ms.getStatementLog()); stmt = handler.prepare(conn, transaction.getTimeout()); handler.parameterize(stmt); currentSql = sql; currentStatement = ms; statementList.add(stmt); batchResultList.add(new BatchResult(ms, sql, parameter)); }
handler.batch(stmt); return BATCH_UPDATE_RETURN_VALUE; }
@Override public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException { try { List<BatchResult> results = new ArrayList<>(); if (!isRollback) { for (int i = 0; i < statementList.size(); i++) { Statement stmt = statementList.get(i); applyTransactionTimeout(stmt); BatchResult batchResult = batchResultList.get(i); try { batchResult.setUpdateCounts(stmt.executeBatch()); MappedStatement ms = batchResult.getMappedStatement(); KeyGenerator keyGenerator = ms.getKeyGenerator(); if (keyGenerator instanceof Jdbc3KeyGenerator) { ((Jdbc3KeyGenerator) keyGenerator).processBatch(ms, stmt, batchResult.getParameterObjects()); } results.add(batchResult); } catch (BatchUpdateException e) { throw new BatchExecutorException( "Batch execution failed for " + ms.getId(), e, results, batchResult); } finally { closeStatement(stmt); } } } return results; } finally { for (Statement stmt : statementList) { closeStatement(stmt); } currentSql = null; currentStatement = null; statementList.clear(); batchResultList.clear(); } } }
|
关键细节:
- 返回值:
doUpdate 返回 BATCH_UPDATE_RETURN_VALUE(-2147482646),表示批量执行无实时行数,需通过 flushStatements() 获取结果;
- 提交时机:批量 SQL 仅在
flushStatements()(手动调用或事务提交时)执行,减少网络请求;
- 主键处理:支持批量获取自增主键(如 MySQL 的
LAST_INSERT_ID()),需配置 useGeneratedKeys=true。
特点与适用场景
| 优点 |
缺点 |
适用场景 |
| 批量提交 SQL,减少网络开销 |
仅支持 DML(insert/update/delete),不支持 SELECT |
批量数据导入(如 Excel 导入) |
| 支持批量获取自增主键 |
需手动调用 flush 或提交事务才执行 |
高频批量更新(如订单状态同步) |
CachingExecutor:二级缓存装饰器
CachingExecutor 是 Executor 的装饰器实现,不改变基础执行器的 SQL 执行逻辑,仅在执行前后添加二级缓存(应用级缓存)的查询与更新。
1. 二级缓存核心架构
二级缓存通过 “CachingExecutor + TransactionalCacheManager + TransactionalCache” 实现,核心是 “事务提交时才写入缓存,回滚时丢弃”,确保数据一致性:
- TransactionalCacheManager:管理多个二级缓存(按 Mapper 命名空间区分),每个缓存对应一个
TransactionalCache;
- TransactionalCache:事务级缓存代理,暂存待提交的缓存项(
entriesToAddOnCommit),事务提交时写入实际缓存,回滚时丢弃;
- Delegate Cache:实际缓存实现(默认
PerpetualCache,可通过 <cache type="xxx"> 自定义,如 Redis 缓存)。
2. 核心源码:二级缓存查询与提交
(1)查询逻辑(先查二级缓存,再查一级缓存 / 数据库)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class CachingExecutor implements Executor { private final Executor delegate; private final TransactionalCacheManager tcm = new TransactionalCacheManager();
@Override public <E> List<E> query(...) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
public <E> List<E> query(...) throws SQLException { Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } }
|
(2)事务提交:写入二级缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public void commit() { if (clearOnCommit) { delegate.clear(); } flushPendingEntries(); reset(); }
private void flushPendingEntries() { for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) { delegate.putObject(entry.getKey(), entry.getValue()); } for (Object key : entriesMissedInCache) { if (!entriesToAddOnCommit.containsKey(key)) { delegate.putObject(key, null); } } }
|
(3)事务回滚:丢弃缓存项
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public void rollback() { unlockMissedEntries(); reset(); }
private void unlockMissedEntries() { for (Object key : entriesMissedInCache) { try { delegate.removeObject(key); } catch (Exception e) { log.warn("Unexpected exception while notifying a rollback to the cache adapter.", e); } } }
|
3. 二级缓存配置开关
二级缓存需满足以下 3 个条件才生效:
全局开关:mybatis-config.xml中cacheEnabled=true(默认 true):
1 2 3
| <settings> <setting name="cacheEnabled" value="true"/> </settings>
|
Mapper 开关:在 Mapper XML 中配置<cache>或<cache-ref>:
<cache>:为当前命名空间创建独立二级缓存(默认 PerpetualCache);
<cache-ref namespace="com.xxx.OtherMapper">:共享其他命名空间的缓存。
1 2 3 4 5
| <mapper namespace="com.example.mybatis.mapper.UserMapper"> <cache eviction="LRU" flushInterval="60000" size="1024" readOnly="true"/> </mapper>
|
语句开关:<select>标签的useCache=true(默认 true,可单独关闭):
1 2 3 4
| <select id="selectUser" resultType="user" useCache="false"> SELECT * FROM user WHERE id = #{id} </select>
|
一级缓存 vs 二级缓存:核心差异
| 对比维度 |
一级缓存(LocalCache) |
二级缓存(CachingExecutor) |
| 作用范围 |
会话级(SqlSession/Executor 生命周期) |
应用级(MyBatis 启动到关闭) |
| 存储位置 |
内存(PerpetualCache),不可自定义 |
可自定义(内存 / Redis/EHCache) |
| 共享性 |
仅当前 SqlSession 可见,不共享 |
所有 SqlSession 共享(按命名空间隔离) |
| 生效条件 |
默认开启,无需额外配置 |
需开启 cacheEnabled + 配置 <cache> |
| 清空时机 |
update/commit/rollback/clearCache |
事务提交时写入,回滚时丢弃;<cache flushInterval> 自动刷新 |
| 适用场景 |
单次会话内重复查询 |
多会话共享的高频查询(如商品分类列表) |
Executor 实战选型与最佳实践
1. 执行器选型建议
| 业务场景 |
推荐执行器 |
理由 |
| 单条 SQL 执行(如详情查询) |
SimpleExecutor |
逻辑简单,无额外开销 |
| 循环执行相同 SQL(如批量查询) |
ReuseExecutor |
复用 Statement,减少创建 / 关闭开销 |
| 批量 insert/update/delete(如导入) |
BatchExecutor |
批量提交,减少网络请求 |
| 多会话共享高频查询(如首页数据) |
CachingExecutor + 二级缓存 |
应用级缓存,减少数据库访问 |
2. 缓存使用注意事项
- 一级缓存避免脏读:若同一会话内有更新操作,需注意一级缓存未清空导致的脏读(可通过
flushCache="true" 强制清空);
- 二级缓存谨慎共享:通过
cache-ref 共享缓存时,需确保关联命名空间的更新操作会同步清空缓存(避免跨命名空间脏读);
- 自定义二级缓存:生产环境建议用 Redis/EHCache 替代默认内存缓存(避免应用重启缓存丢失),需实现
Cache 接口;
- 禁用不必要的缓存:对实时性要求高的数据(如订单状态),禁用二级缓存(
useCache="false")。
3. 批量执行最佳实践
v1.3.10