Executor 执行链
Executor 执行链:doQuery、StatementHandler 与三种执行器
Executor 是 SqlSession 和 JDBC 之间的执行引擎:SqlSession 只是门面,SQL 怎么变成 Statement、参数怎么设、结果怎么映射,全在这条链上。
核心思路是三段分工——BaseExecutor 用模板方法定死流程骨架(缓存、事务、延迟加载),子类只实现 doQuery/doUpdate 决定 Statement 的生命周期策略,StatementHandler 再把「建 Statement、设参数、执行、映射结果」封装成对 JDBC 的最后一层适配。
// 基于 MyBatis 3.5.x (本地 JavaSourceReadingLab 仓), org.apache.ibatis.session.defaults.DefaultSqlSession
// 入口:selectList 只做一件事——用语句 id 换出 MappedStatement,然后甩给 executor
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement); // id -> 这条 SQL 的全部静态信息
dirty |= ms.isDirtySelect();
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}// 基于 MyBatis 3.5.x (本地 JavaSourceReadingLab 仓), org.apache.ibatis.executor.BaseExecutor
// 模板方法层:query 定流程(缓存判断、嵌套查询计数、延迟加载),把真正落库的 doQuery 留给子类
public abstract class BaseExecutor implements Executor {
protected Transaction transaction;
protected Executor wrapper; // 指向最外层装饰器(通常是 CachingExecutor),见原理串讲
protected PerpetualCache localCache; // 一级缓存,02 已详讲,本篇只看它在链路里的位置
protected int queryStack; // 嵌套查询深度:>0 说明当前在处理子查询/延迟加载
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter); // 动态 SQL 在这里解析成最终 SQL + 参数映射
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); // id+分页+SQL+参数值+环境 五要素拼 key
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
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; // 自定义 handler 时跳过缓存
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); // 只对存储过程 OUT 参数有意义
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) { // 回到最外层:所有子查询都已入缓存,才能安全做延迟加载
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache(); // STATEMENT 级:一级缓存只活一次查询
}
}
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER); // 先占位:告诉嵌套查询「这个 key 正在查,别递归进来」
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); // 钩子,子类各显神通
} finally {
localCache.removeObject(key); // 无论成败先摘掉占位符,失败时不能留脏占位
}
localCache.putObject(key, list); // 真结果落一级缓存
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
// 四个抽象钩子:SIMPLE/REUSE/BATCH 的差别全部体现在这里的实现
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;
// ...
}// 基于 MyBatis 3.5.x (本地 JavaSourceReadingLab 仓), org.apache.ibatis.executor.SimpleExecutor
// 默认执行器:一次查询 = 新建 Statement -> 执行 -> 立刻关闭,最简单也最不容易出错
public class SimpleExecutor extends BaseExecutor {
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler,
boundSql); // 注意传的是 wrapper 不是 this,嵌套查询要从最外层装饰器重新进
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler); // 执行 + 结果映射
} finally {
closeStatement(stmt); // SIMPLE 的标志:用完即关,不复用
}
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) {
return Collections.emptyList(); // 没有攒任何 Statement,flush 无事可做
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog); // 从 Transaction 拿连接,开 DEBUG 会包一层日志代理
stmt = handler.prepare(connection, transaction.getTimeout()); // 建 Statement + 设超时/fetchSize
handler.parameterize(stmt); // 给 ? 占位符设值
return stmt;
}
// ...
}// 基于 MyBatis 3.5.x (本地 JavaSourceReadingLab 仓), org.apache.ibatis.executor.statement 包
// RoutingStatementHandler:构造时按 statementType 一次性选好真实 handler,之后所有方法纯转发
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED: // 默认值,绝大多数场景走这里
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout); // 之后全是这种一行转发
}
// ...
}
// BaseStatementHandler:prepare 的公共骨架,建 Statement 的动作留给子类
public abstract class BaseStatementHandler implements StatementHandler {
protected final ResultSetHandler resultSetHandler; // 结果集 -> Java 对象
protected final ParameterHandler parameterHandler; // 参数 -> ? 占位符
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection); // 子类决定建哪种 Statement
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement); // 建到一半失败必须关,否则漏 Statement
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
// ...
}
// PreparedStatementHandler:终点站,这里之下就是纯 JDBC
public class PreparedStatementHandler extends BaseStatementHandler {
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql(); // 此时 SQL 里动态部分已解析完,只剩 ? 占位符
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); // useGeneratedKeys 的底层
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
}
if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql); // 最常见的一行:MyBatis 到 JDBC 的交接点
} else {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(),
ResultSet.CONCUR_READ_ONLY);
}
}
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement); // 逐个走 TypeHandler 调 ps.setXxx(i, value)
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute(); // 真正发 SQL 到数据库
return resultSetHandler.handleResultSets(ps); // ResultSet -> List<E>,另一条大链路
}
// ...
}原理串讲
一次 selectList 的完整链路:DefaultSqlSession.selectList 用语句 id 从 Configuration 取出 MappedStatement,调 executor.query。
四参数版 query 先做两件准备:ms.getBoundSql(parameter) 把动态 SQL 解析成带 ? 的最终 SQL,createCacheKey 用语句 id、RowBounds、SQL 文本、每个参数值、环境 id 拼出缓存 key——参与执行结果的每个变量都进了 key,这是缓存能安全命中的前提。
六参数版 query 查一级缓存,未命中则进 queryFromDatabase,占位、调 doQuery、回填缓存。
SimpleExecutor.doQuery 通过 configuration.newStatementHandler 拿到 RoutingStatementHandler(内部按 statementType 选出 PreparedStatementHandler),然后三步走:
代码块收起展开
`handler.prepare` 经 `BaseStatementHandler.prepare` 调子类 `instantiateStatement`,落到 `connection.prepareStatement(sql)`;
`handler.parameterize` 委托 `ParameterHandler.setParameters` 给占位符设值;handler.query 调 ps.execute() 后把 ResultSet 交给 ResultSetHandler 映射成对象。
finally 里 closeStatement 关掉 Statement。
第一个值得问为什么的地方是 queryFromDatabase 里的 EXECUTION_PLACEHOLDER。
MyBatis 支持嵌套查询:映射结果时某个属性可能触发另一条 select,而那条 select 可能又指回当前正在查的 key(循环引用)。
先往一级缓存放占位符,DeferredLoad.canLoad() 就能识别「这个 key 正在查、结果还没出来」,把加载推迟到 queryStack 归零之后统一执行,而不会递归发起重复查询甚至死循环。
queryStack 计数同理:flushCache="true" 的清缓存和延迟加载的触发都只在最外层(queryStack == 0)进行,否则子查询会破坏外层查询正依赖的缓存状态。
第二个是 doQuery 里传给 newStatementHandler 的为什么是 wrapper 而不是 this。
开启二级缓存时真实 Executor 被 CachingExecutor 装饰,wrapper 保存的就是这个最外层引用(由 setExecutorWrapper 设置)。
StatementHandler 里的 ResultSetHandler 处理嵌套查询时要再次发起 executor.query——如果拿到的是被装饰的内层 this,子查询就会绕过二级缓存,造成同一条语句主查询走缓存、作为子查询却直查库的不一致。
装饰器模式的代价就在这:被装饰者内部的「自调用」必须显式持有最外层引用才能不丢装饰逻辑,这和 Spring AOP 里 this 调用不走代理是同一类问题。
RoutingStatementHandler 也值得一句:它在构造函数里做一次 switch 就把 delegate 定死,之后所有方法纯转发,没有任何逻辑。
这层看似多余的壳让 Configuration.newStatementHandler 只需要 new 一个类型,也给插件拦截 StatementHandler 提供了统一的代理点——拦截器只用包一个 Routing,不用关心底下三种实现。
三种 Executor 的差别全部落在 doQuery/doUpdate/doFlushStatements 这三个钩子上。
SIMPLE 如上,每次新建 Statement、用完即关,doFlushStatements 直接返回空列表。
REUSE(ReuseExecutor)维护一个 statementMap(SQL 文本 → Statement),prepareStatement 先查 map,同一条 SQL 第二次执行就复用已 prepare 的 Statement 只重新 parameterize,省掉重复的 prepare 往返;这些 Statement 攒到 doFlushStatements 时统一关闭清空。
BATCH(BatchExecutor)只对写生效:
doUpdate 不执行 SQL,只调 handler.batch(stmt)(底层是 ps.addBatch())把参数攒进当前 Statement,返回假行数 BATCH_UPDATE_RETURN_VALUE(Integer.MIN_VALUE + 1002),真正的执行推迟到 doFlushStatements 里逐个 stmt.executeBatch();
而且只有「连续且相同」的语句(sql.equals(currentSql) && ms.equals(currentStatement))才共享一个 Statement,写操作交错会切出多个批次。
BATCH 的 doQuery 会先调 flushStatements() 把攒着的写全部刷出去,否则同一会话里查询会读不到自己刚「写」的数据。
设计取舍
- SIMPLE 是默认值不是性能最优:MyBatis 选它因为语义最简单——没有跨语句共享的 Statement 状态,任何一步失败影响面都只有当前这一次执行。
- REUSE 的收益依赖「同一 SqlSession 内反复执行同一条 SQL」这个前提,Web 应用里 SqlSession 通常一请求一个,命中机会有限;连接池 + 驱动层 PreparedStatement 缓存往往已经覆盖了这个优化。
- BATCH 的 update 返回值是假的(BATCH_UPDATE_RETURN_VALUE),拿它判断影响行数是经典误区,真实行数要等 flushStatements 返回的 BatchResult。
- BATCH 只有连续相同的语句才进同一批次,循环里交替 insert 两张表会退化成逐条切换 Statement,批量收益归零。
- Executor 钩子(doQuery)和 StatementHandler 分成两层不是过度设计:前者管 Statement 的生命周期策略(建几个、何时关),后者管单个 Statement 的操作细节(怎么建、怎么设参),三种 Executor 才能共享同一套 StatementHandler。