Executor 执行器体系
Executor 是 MyBatis SQL 执行的核心组件,负责创建 Statement、设置参数、执行 SQL、处理结果集。MyBatis 提供三种 Executor 实现,各有不同的性能特点。
执行器层次结构
Executor(接口)
└── BaseExecutor(抽象基类)
├── SimpleExecutor // 简单执行器,默认
├── ReuseExecutor // 可复用执行器
└── BatchExecutor // 批处理执行器
└── CachingExecutor(装饰器)
└── 包装上述任一 BaseExecutor 实现二级缓存
Executor 创建过程
public class Configuration {
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 1. 根据类型创建对应 Executor
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 2. 如果开启二级缓存,包装为 CachingExecutor
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 3. 应用插件拦截
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
}
装饰器模式:CachingExecutor 不改变 Executor 接口,而是在内部持有真正的 Executor,先查二级缓存,未命中再委托给底层 Executor 执行。
BaseExecutor 核心职责
三种 Executor 都继承 BaseExecutor,共享以下核心功能:
public abstract class BaseExecutor implements Executor {
protected Transaction transaction; // 事务对象
protected Executor executor; // 装饰器模式,指向 CachingExecutor
protected Configuration configuration; // 全局配置
// 一级缓存(PerpetualCache 本质是 HashMap)
protected PerpetualCache localCache;
// 延迟加载参数存储
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
// 嵌套查询参数
protected LocalOutputParameterIndex localOutputParameterIndex;
// 抽象方法,由子类实现具体执行逻辑
protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
// 公共方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) {
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建缓存 key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 从一级缓存获取
List<E> list = (List<E>) localCache.getObject(key);
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
return list;
}
// 缓存未命中,执行查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
return list;
}
private <E> List<E> queryFromDatabase(...) {
localCache.putObject(key, EXECUTION_PLACEHOLDER); // 占位符,防止循环查询
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); // 调用子类
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list); // 放入一级缓存
return list;
}
}
一级缓存机制
CacheKey 组成要素:
├── MappedStatement ID
├── RowBounds(分页偏移量)
├── SQL 语句(含 ? 占位符)
├── 参数值
└── Environment ID
一级缓存作用域:SqlSession 级别,不同 SqlSession 的一级缓存完全隔离。commit/update/close 操作会清空一级缓存。
SimpleExecutor 实现
public class SimpleExecutor extends BaseExecutor {
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 1. 创建 StatementHandler
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 2. 创建 PreparedStatement
stmt = prepareStatement(handler, ms.getStatementLog());
// 3. 执行更新
return handler.update(stmt);
} finally {
closeStatement(stmt); // 每次执行后关闭 Statement
}
}
@Override
public <E> List<E> doQuery(...) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt); // 每次执行后关闭 Statement
}
}
}
特点:每次执行都创建新的 PreparedStatement,执行后立即关闭。适用于执行频率不高、SQL 语句不重复的场景。
ReuseExecutor 实现
public class ReuseExecutor extends BaseExecutor {
// 缓存 PreparedStatement
private final Map<String, Statement> statementMap = new HashMap<>();
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 从缓存获取或创建 Statement
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
}
@Override
public <E> List<E> doQuery(...) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
}
@Override
public void doFlushStatements(boolean isRollback) throws SQLException {
// 关闭所有缓存的 Statement
for (Statement stmt : statementMap.values()) {
closeStatement(stmt);
}
statementMap.clear(); // 清空缓存
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
// 检查是否已缓存
if (statementMap.containsKey(sql)) {
stmt = statementMap.get(sql);
} else {
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
statementMap.put(sql, stmt); // 缓存
}
// 每次都需要设置参数(参数值可能不同)
handler.parameterize(stmt);
return stmt;
}
}
特点:以 SQL 语句为 key 缓存 PreparedStatement,同一 SqlSession 内相同 SQL 重复使用时直接获取缓存。适用于同一 SqlSession 内多次执行相同 SQL 的场景。
注意:statementMap 的 key 是完整的 SQL 字符串,不同参数的相同语句会复用同一 PreparedStatement,但每次执行都会重新设置参数值。
BatchExecutor 实现
public class BatchExecutor extends BaseExecutor {
// 当前批量执行的 Statement 列表
private final List<Statement> statementList = new ArrayList<>();
// 批量执行的批次信息(用于返回值追踪)
private final List<BatchResult> batchResultList = new ArrayList<>();
// 当前批量执行的 SQL
private String currentSql;
// 当前批量执行的 MappedStatement
private MappedStatement currentStatement;
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
// 检查是否可以追加到当前批次
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
// 同一 SQL,追加到当前批次
Statement stmt = handler.prepareStatement(transaction.getConnection());
handler.parameterize(stmt);
// 添加到批量执行队列
int lastIndex = statementList.size() - 1;
statementList.add(stmt);
// 记录批次参数
BatchResult batchResult = batchResultList.get(lastIndex);
batchResult.addParameterObject(parameter);
return BATCH_UPDATE_RETURN_VALUE; // -2 表示批量执行
} else {
// 不同 SQL,启动新批次
Statement stmt = handler.prepareStatement(transaction.getConnection());
handler.parameterize(stmt);
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameter));
currentSql = sql;
currentStatement = ms;
return BATCH_UPDATE_RETURN_VALUE;
}
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
if (isRollback) {
return Collections.emptyList();
}
List<BatchResult> results = new ArrayList<>();
try {
for (int i = 0, n = statementList.size(); i < n; i++) {
Statement stmt = statementList.get(i);
BatchResult batchResult = batchResultList.get(i);
// 执行批量 SQL
int[] updateCounts = stmt.executeBatch();
batchResult.setUpdateCounts(updateCounts);
results.add(batchResult);
}
return results;
} finally {
// 关闭所有 Statement
for (Statement stmt : statementList) {
closeStatement(stmt);
}
statementList.clear();
batchResultList.clear();
currentSql = null;
currentStatement = null;
}
}
}
特点:将相同 SQL 的多次操作累积为批量执行,通过 JDBC addBatch() 和 executeBatch() 实现。适用于批量插入、批量更新场景。
批量提交时机:调用
sqlSession.flushStatements()或sqlSession.commit()时触发实际的批量执行。
三种执行器对比
| 特性 | SimpleExecutor | ReuseExecutor | BatchExecutor |
|---|---|---|---|
| Statement创建 | 每次新建 | SQL缓存复用 | 按SQL分组复用 |
| Statement关闭 | 执行后立即 | SqlSession关闭时 | flushStatements时 |
| 内存占用 | 最低 | 中等(缓存Map) | 最高(累积批次) |
| 适用场景 | 单次执行 | 同SQL多次执行 | 批量插入/更新 |
| 性能特点 | 创建开销大 | 复用减少开销 | 减少网络往返 |
| 返回值 | 实际影响行数 | 实际影响行数 | BATCH_UPDATE_RETURN_VALUE(-2) |
CachingExecutor 二级缓存
public class CachingExecutor implements Executor {
private final Executor delegate; // 真正的执行器
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 1. 从二级缓存获取
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list != null) {
return list;
}
// 2. 委托给底层执行器(会查一级缓存或执行SQL)
list = delegate.query(ms, parameter, rowBounds, resultHandler);
// 3. 放入二级缓存
tcm.putObject(cache, key, list);
return list;
}
@Override
public void commit(boolean required) {
delegate.commit(required);
tcm.commit(); // 提交二级缓存
}
}
二级缓存作用域:Namespace 级别,同一 namespace 的所有 SqlSession 共享。需要在 Mapper XML 中配置
<cache/>开启。
执行器选择时序图
配置 ExecutorType.BATCH
|
v
SqlSessionFactory.newExecutor()
|
+-- 创建 BatchExecutor
|
+-- cacheEnabled? ──Yes──> 包装为 CachingExecutor
|
+-- 应用插件拦截
|
v
返回 Executor 实例
要点总结
- 三种执行器:SimpleExecutor(默认,每次新建)、ReuseExecutor(SQL缓存复用)、BatchExecutor(批量执行)
- 一级缓存在 BaseExecutor:所有执行器共享一级缓存机制,缓存 key 由 MappedStatement ID、SQL、参数等构成
- Statement 生命周期:SimpleExecutor 执行后立即关闭,ReuseExecutor 在 SqlSession 关闭时关闭,BatchExecutor 在 flushStatements 时关闭
- CachingExecutor 是装饰器:内部持有真正的 Executor,先查二级缓存再委托执行
- BatchExecutor 返回值:批量执行的 update 返回 -2(BATCH_UPDATE_RETURN_VALUE),实际行数在 flushStatements 后获取
- 插件拦截时机:所有 Executor 创建后都会经过 pluginAll() 处理,插件可以在执行前后插入逻辑
📝 发现内容有误?点击此处直接编辑