第5篇:Executor执行器体系详解
1. 学习目标确认
1.0 第4篇思考题解答
在深入学习Executor执行器体系之前,让我们先回顾并解答第4篇中提出的思考题,这将帮助我们更好地理解Executor在整个架构中的作用。
思考题1:为什么MyBatis选择JDK动态代理而非字节码增强(如ASM/CGLIB)?
答案要点:
- 接口代理特性:MyBatis的Mapper接口都是接口,JDK动态代理天然支持接口代理,无需依赖第三方库
- 性能考虑:JDK动态代理在JVM层面有优化,性能优于字节码增强方案
- 兼容性:JDK动态代理是Java标准库的一部分,兼容性更好,无需额外依赖
- 简洁性:实现相对简单,代码量少,维护成本低
- 功能满足:对于MyBatis的需求(方法调用拦截和转发),JDK动态代理完全满足
Executor的作用:Executor作为Mapper代理调用的最终执行者,负责具体的SQL执行和结果处理。
思考题2:多返回值场景下,MapperMethod如何区分集合、游标与映射类型?
答案要点:
- 返回类型检测:通过反射获取方法返回类型,判断是否为Collection、Cursor或Map类型
- 注解识别:通过@MapKey注解识别Map类型,通过泛型参数确定Map的key类型
- 执行路径选择:根据返回类型选择对应的SqlSession方法(selectList、selectCursor、selectMap)
- 结果适配:MapperMethod根据方法签名进行结果类型适配和转换
- 异常处理:对不匹配的返回类型进行异常处理和提示
与Executor的协作:Executor根据不同的执行类型(SELECT/INSERT/UPDATE/DELETE)和返回类型选择合适的执行策略。
思考题3:在开启二级缓存时,哪些因素会导致缓存失效或绕过?
答案要点:
- flushCache配置:非SELECT语句默认flushCache=true,会清空相关缓存
- useCache配置:SELECT语句可配置useCache=false绕过缓存
- ResultHandler使用:使用ResultHandler的查询会绕过二级缓存
- 存储过程OUT参数:带有OUT参数的存储过程会绕过缓存
- 事务边界:事务提交/回滚会影响TransactionalCache的缓存策略
- 缓存Key变化:参数或配置变化导致CacheKey不同,无法命中缓存
Executor的缓存管理:CachingExecutor负责二级缓存的读写管理,SimpleExecutor等负责一级缓存的维护。
1.1 本篇学习目标
通过本文你将能够:
- 深入理解MyBatis Executor执行器体系的设计思想和架构模式
- 掌握BaseExecutor模板方法模式的实现原理和优势
- 理解SimpleExecutor、ReuseExecutor、BatchExecutor的具体实现和适用场景
- 掌握CachingExecutor装饰器模式在缓存管理中的应用
- 了解Executor与StatementHandler、ParameterHandler、ResultSetHandler的协作关系
- 具备自定义Executor扩展开发的能力
2. Executor执行器体系总览
2.1 执行器继承关系图
classDiagram class Executor { <<interface>> +update(MappedStatement, Object) int +query(MappedStatement, Object, RowBounds, ResultHandler) List~E~ +query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql) List~E~ +queryCursor(MappedStatement, Object, RowBounds) Cursor~E~ +flushStatements() List~BatchResult~ +commit(boolean) void +rollback(boolean) void +createCacheKey(MappedStatement, Object, RowBounds, BoundSql) CacheKey +clearLocalCache() void +deferLoad(MappedStatement, MetaObject, String, CacheKey, Class) void +getTransaction() Transaction +close(boolean) void +isClosed() boolean } class BaseExecutor { <<abstract>> #doQuery(MappedStatement, Object, RowBounds, ResultHandler, BoundSql) List~E~ #doUpdate(MappedStatement, Object) int #doFlushStatements(boolean) List~BatchResult~ #doQueryCursor(MappedStatement, Object, RowBounds, BoundSql) Cursor~E~ #queryFromDatabase(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql) List~E~ #localCache PerpetualCache #localOutputParameterCache PerpetualCache #deferredLoads List~DeferredLoad~ #queryStack int #closed boolean } class SimpleExecutor { +doQuery(MappedStatement, Object, RowBounds, ResultHandler, BoundSql) List~E~ +doUpdate(MappedStatement, Object) int +doFlushStatements(boolean) List~BatchResult~ +doQueryCursor(MappedStatement, Object, RowBounds, BoundSql) Cursor~E~ } class ReuseExecutor { -statementMap Map~String,Statement~ +doQuery(MappedStatement, Object, RowBounds, ResultHandler, BoundSql) List~E~ +doUpdate(MappedStatement, Object) int +doFlushStatements(boolean) List~BatchResult~ +doQueryCursor(MappedStatement, Object, RowBounds, BoundSql) Cursor~E~ +close(boolean) void } class BatchExecutor { -statementList List~Statement~ -batchResultList List~BatchResult~ -currentSql String -currentStatement MappedStatement +doQuery(MappedStatement, Object, RowBounds, ResultHandler, BoundSql) List~E~ +doUpdate(MappedStatement, Object) int +doFlushStatements(boolean) List~BatchResult~ +doQueryCursor(MappedStatement, Object, RowBounds, BoundSql) Cursor~E~ +close(boolean) void } class CachingExecutor { -delegate Executor -transactionalCacheManager TransactionalCacheManager +query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql) List~E~ +update(MappedStatement, Object) int +commit(boolean) void +rollback(boolean) void +flushStatements() List~BatchResult~ +getTransaction() Transaction +close(boolean) void +isClosed() boolean -ensureNoOutParams(MappedStatement, BoundSql) void -flushCacheIfRequired(MappedStatement) void } Executor <|.. BaseExecutor BaseExecutor <|-- SimpleExecutor BaseExecutor <|-- ReuseExecutor BaseExecutor <|-- BatchExecutor Executor <|.. CachingExecutor CachingExecutor --> Executor : delegates to
2.2 执行器职责分工
执行器类型 | 核心职责 | 适用场景 | 性能特点 |
---|---|---|---|
BaseExecutor | 模板方法实现,一级缓存管理 | 所有场景的基础 | 提供通用功能和缓存 |
SimpleExecutor | 简单执行,每次创建新Statement | 单次执行、简单查询 | 简单直接,资源及时释放 |
ReuseExecutor | 重用Statement对象 | 重复执行相同SQL | 减少Statement创建开销 |
BatchExecutor | 批量执行多个SQL | 批量插入、更新、删除 | 大幅减少数据库交互次数 |
CachingExecutor | 二级缓存管理 | 需要缓存的查询场景 | 避免重复查询,提升性能 |
2.3 执行器协作关系
sequenceDiagram participant SqlSession as SqlSession participant Executor as Executor participant SH as StatementHandler participant PH as ParameterHandler participant RSH as ResultSetHandler participant DB as Database SqlSession->>Executor: query(ms, param, rowBounds, handler) Executor->>Executor: createCacheKey(ms, param, rowBounds, boundSql) Executor->>Executor: queryFromDatabase(ms, param, rowBounds, handler, key, boundSql) Executor->>SH: newStatementHandler(wrapper, ms, param, rowBounds, handler, boundSql) SH->>SH: prepare(connection, timeout) SH->>PH: parameterize(statement) PH->>DB: set parameters SH->>DB: execute query DB-->>SH: ResultSet SH->>RSH: handleResultSets(statement) RSH-->>SH: List<Object> SH-->>Executor: results Executor-->>SqlSession: List<E>
3. BaseExecutor抽象基类深度解析
3.1 模板方法模式实现
BaseExecutor采用模板方法模式,定义了SQL执行的标准流程,子类只需实现具体的执行逻辑:
java
package org.apache.ibatis.executor;
/**
* 执行器抽象基类,采用模板方法模式
* 定义SQL执行的标准流程,子类实现具体的执行逻辑
*/
public abstract class BaseExecutor implements Executor {
// 事务管理器,用于管理数据库事务
protected Transaction transaction;
// 执行器包装器,通常是CachingExecutor
protected Executor wrapper;
// 一级缓存(本地缓存),session级别缓存
protected PerpetualCache localCache;
// 存储过程出参缓存
protected PerpetualCache localOutputParameterCache;
// MyBatis全局配置对象
protected Configuration configuration;
// 查询栈深度,用于控制嵌套查询
protected int queryStack;
// 执行器是否已关闭
private boolean closed;
// 延迟加载队列,存储需要延迟加载的对象
protected List<DeferredLoad> deferredLoads = new ArrayList<>();
/**
* 模板方法:查询操作的标准流程(重载方法1)
* @param ms SQL映射语句
* @param parameter 查询参数
* @param rowBounds 分页参数
* @param resultHandler 结果处理器
* @return 查询结果列表
*/
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 根据参数生成动态SQL
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建缓存键,用于一级缓存
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 调用重载方法继续执行
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
/**
* 模板方法:查询操作的标准流程(重载方法2)
* 这是查询的核心方法,包含完整的缓存和执行逻辑
*/
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, 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 {
// 查询栈深度+1,用于处理嵌套查询
queryStack++;
// 尝试从一级缓存中获取结果(仅当没有结果处理器时)
list = resultHandler == null ? (List<E>) localCache.getObject(cacheKey) : null;
if (list != null) {
// 缓存命中,处理存储过程的输出参数
handleLocallyCachedOutputParameters(ms, cacheKey, parameter, boundSql);
} else {
// 缓存未命中,从数据库查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
} finally {
// 查询栈深度-1
queryStack--;
}
// 如果回到最外层查询
if (queryStack == 0) {
// 执行所有延迟加载
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// 清空延迟加载队列
deferredLoads.clear();
// 如果配置为STATEMENT级别缓存,执行完成后清空缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
/**
* 抽象方法:子类实现具体的数据库查询逻辑
* 不同的执行器有不同的实现策略
*/
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
/**
* 模板方法:更新操作的标准流程
* 包括插入、更新、删除操作
*/
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
// 设置错误上下文
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
// 检查执行器是否已关闭
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清空本地缓存(更新操作会影响缓存一致性)
clearLocalCache();
// 调用子类的具体实现
return doUpdate(ms, parameter);
}
/**
* 抽象方法:子类实现具体的数据库更新逻辑
*/
protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
}
3.2 一级缓存机制
BaseExecutor实现了一级缓存(本地缓存),提升重复查询的性能:
java
/**
* 从数据库查询数据并缓存结果
* 这是一级缓存的核心实现方法
*/
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException {
List<E> list;
// 在缓存中放入占位符,防止循环引用
localCache.putObject(cacheKey, EXECUTION_PLACEHOLDER);
try {
// 调用子类的具体实现执行数据库查询
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 无论成功还是失败,都要移除占位符
localCache.removeObject(cacheKey);
}
// 将查询结果放入一级缓存
localCache.putObject(cacheKey, list);
// 如果是存储过程调用,需要缓存输出参数
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(cacheKey, parameter);
}
return list;
}
/**
* 创建缓存键
* 缓存键由多个因素组成,确保唯一性
*/
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
// 检查执行器是否已关闭
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 创建缓存键对象
CacheKey cacheKey = new CacheKey();
// 添加SQL映射语句ID到缓存键中
cacheKey.update(ms.getId());
// 添加分页参数到缓存键中
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
// 添加SQL语句到缓存键中
cacheKey.update(boundSql.getSql());
// 获取参数映射列表
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// 遍历所有参数,将参数值添加到缓存键中
for (ParameterMapping parameterMapping : parameterMappings) {
// 只处理输入参数,不处理输出参数
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
// 获取参数值的逐个判断逻辑
if (boundSql.hasAdditionalParameter(propertyName)) {
// 从额外参数中获取
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
// 参数对象为空
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
// 参数对象有对应的类型处理器(基本类型)
value = parameterObject;
} else {
// 复杂对象,通过反射获取属性值
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 将参数值添加到缓存键中
cacheKey.update(value);
}
}
// 将环境ID添加到缓存键中(区分不同环境)
if (configuration.getEnvironment() != null) {
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
3.3 延迟加载机制
BaseExecutor支持延迟加载(懒加载),通过DeferredLoad实现:
java
/**
* 延迟加载类,用于实现懒加载功能
* 当访问某个属性时,才真正执行相关查询
*/
public class DeferredLoad {
// 结果对象的元数据包装器,用于反射操作
private final MetaObject resultObject;
// 需要延迟加载的属性名
private final String property;
// 目标类型(属性的类型)
private final Class<?> targetType;
// 缓存键,用于标识该次查询
private final CacheKey key;
// SQL映射语句,包含延迟加载的SQL信息
private final MappedStatement mappedStatement;
// 执行器,用于执行延迟加载查询
private final Executor executor;
/**
* 构造延迟加载对象
*/
public DeferredLoad(MetaObject resultObject, String property, Class<?> targetType, CacheKey key, MappedStatement mappedStatement, Executor executor) {
this.resultObject = resultObject;
this.property = property;
this.targetType = targetType;
this.key = key;
this.mappedStatement = mappedStatement;
this.executor = executor;
}
/**
* 执行延迟加载
* 这个方法会在适当的时机被调用,执行实际的数据库查询
*/
public void load() throws SQLException {
// 检查属性是否已经有值,如果已经有值则不需要加载
if (resultObject.getValue(property) != null) {
return;
}
// 执行实际的数据库查询
List<Object> list = (List<Object>) executor.query(
mappedStatement,
key.getParameterObject(),
RowBounds.DEFAULT,
Executor.NO_RESULT_HANDLER,
key,
mappedStatement.getBoundSql(key.getParameterObject())
);
// 处理查询结果
if (list != null && list.size() > 0) {
if (list.size() > 1) {
// 多个结果,设置为列表
resultObject.setValue(property, list);
} else {
// 单个结果,设置为单个对象
resultObject.setValue(property, list.get(0));
}
}
}
}
4. SimpleExecutor简单执行器
4.1 核心实现
SimpleExecutor是最基础的执行器,每次执行都创建新的Statement:
java
package org.apache.ibatis.executor;
/**
* 简单执行器:MyBatis的默认执行器
* 特点:每次执行都创建新的Statement对象
* 优点:简单可靠,资源管理清晰
* 缺点:每次都要创建新Statement,有一定性能开销
*/
public class SimpleExecutor extends BaseExecutor {
/**
* 构造方法
* @param configuration MyBatis全局配置
* @param transaction 事务管理器
*/
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
/**
* 执行查询操作
* 每次查询都会创建新的Statement对象
*/
@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,用于处理 SQL 语句
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 准备 Statement(创建并设置参数)
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行查询并返回结果
return handler.query(stmt, resultHandler);
} finally {
// 无论成功还是失败,都要关闭 Statement
closeStatement(stmt);
}
}
/**
* 执行更新操作(包括 INSERT、UPDATE、DELETE)
*/
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
// 获取全局配置
Configuration configuration = ms.getConfiguration();
// 创建 StatementHandler
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 准备 Statement
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行更新并返回影响行数
return handler.update(stmt);
} finally {
// 关闭 Statement
closeStatement(stmt);
}
}
/**
* 刷新批量操作
* SimpleExecutor 不支持批量操作,返回空列表
*/
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
return Collections.emptyList();
}
/**
* 执行游标查询
* 游标查询适用于大结果集的流式处理
*/
@Override
public <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
// 获取全局配置
Configuration configuration = ms.getConfiguration();
// 创建 StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
// 准备 Statement
Statement stmt = prepareStatement(handler, ms.getStatementLog());
// 返回游标对象(注意:游标不在这里关闭,由调用者负责关闭)
return handler.queryCursor(stmt);
}
/**
* 准备 Statement对象
* 包括创建、设置超时、设置参数等操作
*/
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获取数据库连接
Connection connection = getConnection(statementLog);
// 创建 Statement 并设置超时时间
stmt = handler.prepare(connection, transaction.getTimeout());
// 设置 SQL 参数
handler.parameterize(stmt);
return stmt;
}
/**
* 关闭 Statement对象
* 安全关闭,忽略异常
*/
private void closeStatement(Statement stmt) {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
// 忽略关闭异常,避免影响主逻辑
}
}
}
}
4.2 特点分析
优势:
- 简单可靠:逻辑简单,易于理解和维护
- 资源管理:及时释放Statement资源,避免内存泄漏
- 线程安全:每次执行创建新Statement,无状态共享问题
- 适用广泛:适合大多数业务场景
劣势:
- 性能开销:每次执行都创建新Statement,有一定性能开销
- 重复工作:相同SQL的重复执行无法复用Statement
5. ReuseExecutor重用执行器
5.1 核心实现
ReuseExecutor通过重用Statement对象来提升性能:
java
package org.apache.ibatis.executor;
/**
* 重用执行器:通过重用Statement对象来提升性能
* 特点:相同SQL会重用同一个Statement对象
* 优点:减少Statement创建开销,提升性能
* 适用场景:重复执行相同SQL的场景
*/
public class ReuseExecutor extends BaseExecutor {
// Statement缓存Map,以SQL为键,Statement为值
private final Map<String, Statement> statementMap = new HashMap<>();
/**
* 构造方法
*/
public ReuseExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
/**
* 执行查询操作
* 会尝试重用已存在的Statement
*/
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 准备Statement(可能会重用已存在的)
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
}
/**
* 执行更新操作
*/
@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);
}
/**
* 刷新批量操作
* 关闭所有缓存的Statement并清空缓存
*/
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
// 关闭所有缓存的Statement
for (Statement stmt : statementMap.values()) {
closeStatement(stmt);
}
// 清空缓存
statementMap.clear();
return Collections.emptyList();
}
/**
* 执行游标查询
*/
@Override
public <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.queryCursor(stmt);
}
/**
* 准备Statement对象
* 核心方法:实现Statement的重用逻辑
*/
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
// 检查是否已经有可用的Statement
if (hasStatementFor(sql)) {
// 重用已存在的Statement
stmt = getStatement(sql);
// 重新设置事务超时时间
applyTransactionTimeout(stmt);
} else {
// 创建新的Statement
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
// 将新创建的Statement放入缓存
putStatement(sql, stmt);
}
// 设置参数
handler.parameterize(stmt);
return stmt;
}
/**
* 检查是否已经有可用的Statement
* @param sql SQL语句
* @return 是否存在可用的Statement
*/
private boolean hasStatementFor(String sql) {
try {
// 检查缓存中是否存在该SQL对应的Statement,且Statement未关闭
return statementMap.containsKey(sql) && !statementMap.get(sql).isClosed();
} catch (SQLException e) {
// 如果检查过程中出现异常,认为不可用
return false;
}
}
/**
* 从缓存中获取Statement
*/
private Statement getStatement(String s) {
return statementMap.get(s);
}
/**
* 将Statement放入缓存
*/
private void putStatement(String sql, Statement stmt) {
statementMap.put(sql, stmt);
}
/**
* 关闭执行器
* 需要清理所有缓存的Statement
*/
@Override
public void close(boolean forceRollback) {
try {
// 先刷新批量操作
doFlushStatements(forceRollback);
} finally {
// 关闭所有Statement并清空缓存
for (Statement stmt : statementMap.values()) {
closeStatement(stmt);
}
statementMap.clear();
// 调用父类的关闭方法
super.close(forceRollback);
}
}
}
5.2 适用场景
最佳场景:
- 循环查询:在循环中执行相同的SQL语句
- 重复调用:同一个Mapper方法被频繁调用
- 报表查询:生成报表时执行大量相同结构的查询
性能提升:
- 减少Statement创建开销:避免重复创建Statement对象
- 减少SQL解析开销:重用已解析的Statement
- 提升执行效率:特别是在高并发场景下
6. BatchExecutor批量执行器
6.1 核心实现
BatchExecutor通过批量执行多个SQL来提升批量操作的性能:
java
package org.apache.ibatis.executor;
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;
public BatchExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
/**
* 执行更新操作(批量模式)
* 核心方法:将多个相同SQL的更新操作打包成批量执行
*/
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
final Configuration configuration = ms.getConfiguration();
final StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
final BoundSql boundSql = handler.getBoundSql();
final String sql = boundSql.getSql();
final StatementType statementType = ms.getStatementType();
// 检查是否可以重用当前的Statement(相同SQL和语句类型)
if (sql.equals(currentSql) && statementType == currentStatement.getStatementType()) {
// 相同SQL,重用Statement
final Statement stmt = statementList.get(statementList.size() - 1);
applyTransactionTimeout(stmt);
// 设置参数
handler.parameterize(stmt);
// 将参数添加到批量结果中
BatchResult batchResult = batchResultList.get(batchResultList.size() - 1);
batchResult.addParameterObject(parameter);
return BATCH_UPDATE_RETURN_VALUE;
} else {
// 不同SQL,创建新Statement
final Statement stmt;
// 再次检查是否可以重用(双重检查机制)
if (sql.equals(currentSql) && ms.getStatementType() == currentStatement.getStatementType()) {
int last = statementList.size() - 1;
stmt = statementList.get(last);
applyTransactionTimeout(stmt);
handler.parameterize(stmt);
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameter);
} else {
// 创建全新的Statement
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
// 更新当前SQL和语句信息
currentSql = sql;
currentStatement = ms;
// 将Statement和结果对象添加到列表中
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameter));
}
// 将操作添加到批量中(但不立即执行)
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
flushStatements();
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
/**
* 刷新批量操作 - 执行所有缓存的批量操作
* 这是批量执行器的核心方法,实际执行所有缓存的SQL操作
*/
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
try {
List<BatchResult> results = new ArrayList<>();
// 如果是回滚操作,直接返回空列表
if (isRollback) {
return Collections.emptyList();
}
// 遍历所有缓存的Statement
for (int i = 0, n = statementList.size(); i < n; i++) {
Statement stmt = statementList.get(i);
// 重新设置事务超时时间
applyTransactionTimeout(stmt);
try {
// 执行批量操作,返回影响行数数组
int updateCount = stmt.executeBatch();
// 获取对应的批量结果对象
BatchResult batchResult = batchResultList.get(i);
// 设置影响行数
batchResult.setUpdateCounts(new int[]{updateCount});
// 添加到结果列表中
results.add(batchResult);
} catch (BatchUpdateException e) {
// 批量执行异常处理
StringBuilder message = new StringBuilder();
message.append(batchResultList.get(i).getSql()).append(" (failed)\n");
if (e.getMessage() != null && e.getMessage().length() > 0) {
message.append(" Cause: ").append(e.getMessage());
}
// 抛出自定义批量执行异常,包含已成功的结果
throw new BatchExecutorException(message.toString(), e, results, batchResultList.get(i));
}
}
return results;
} finally {
// 无论成功还是失败,都要清理资源
// 关闭所有Statement
for (Statement stmt : statementList) {
closeStatement(stmt);
}
// 清空所有缓存列表
statementList.clear();
batchResultList.clear();
// 重置当前状态
currentSql = null;
currentStatement = null;
}
}
}
6.2 批量操作优势
性能提升:
- 减少网络往返:多个SQL一次性发送到数据库
- 减少事务开销:批量操作在同一个事务中完成
- 提升吞吐量:特别适合大量数据的批量处理
适用场景:
- 批量插入:大量数据的批量插入操作
- 批量更新:相同条件的批量更新操作
- 批量删除:大量数据的批量删除操作
- 数据迁移:数据导入导出场景
7. CachingExecutor缓存装饰器
7.1 装饰器模式实现
CachingExecutor采用装饰器模式,为其他执行器添加二级缓存功能:
java
package org.apache.ibatis.executor;
/**
* 缓存执行器 - 采用装饰器模式实现二级缓存
* 特点:为其他执行器添加二级缓存功能
* 优点:大幅提升查询性能,减少数据库访问
* 适用场景:需要缓存的查询场景
*/
public class CachingExecutor implements Executor {
// 被装饰的执行器(可以是任意类型的执行器)
private final Executor delegate;
// 事务缓存管理器,管理二级缓存的事务性
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
/**
* 构造方法 - 装饰器模式的典型实现
* @param delegate 被装饰的执行器
*/
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
// 设置装饰器引用,便于被装饰者访问装饰器
delegate.setExecutorWrapper(this);
}
/**
* 查询方法 - 二级缓存的核心实现
* 先检查二级缓存,未命中再委托给被装饰的执行器
*/
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 获取当前映射语句的缓存对象
Cache cache = ms.getCache();
// 只有配置了缓存的语句才会使用二级缓存
if (cache != null) {
// 检查是否需要刷新缓存(更新操作会刷新缓存)
flushCacheIfRequired(ms);
// 检查是否启用缓存且没有结果处理器
if (ms.isUseCache() && resultHandler == null) {
// 确保没有输出参数(存储过程的OUT参数不能缓存)
ensureNoOutParams(ms, boundSql);
// 尝试从二级缓存中获取结果
@SuppressWarnings("unchecked")
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);
}
/**
* 更新方法 - 处理更新操作对缓存的影响
*/
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
// 更新操作前先刷新相关缓存
flushCacheIfRequired(ms);
// 委托给被装饰的执行器执行更新
return delegate.update(ms, parameterObject);
}
/**
* 提交事务 - 同时提交缓存事务
*/
@Override
public void commit(boolean required) throws SQLException {
// 先提交数据库事务
delegate.commit(required);
// 再提交缓存事务(将缓存的数据真正写入缓存)
tcm.commit();
}
/**
* 回滚事务 - 同时回滚缓存事务
*/
@Override
public void rollback(boolean required) throws SQLException {
try {
// 先回滚数据库事务
delegate.rollback(required);
} finally {
// 确保缓存事务也被回滚
if (required) {
tcm.rollback();
}
}
}
/**
* 刷新语句 - 委托给被装饰的执行器
*/
@Override
public void flushStatements() throws SQLException {
delegate.flushStatements();
}
/**
* 根据需要刷新缓存
* 如果映射语句配置了flushCache=true,则清空缓存
*/
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
// 检查是否有缓存且需要刷新
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}
/**
* 确保没有输出参数
* 存储过程的OUT参数不能被缓存,因为每次调用结果都可能不同
*/
private void ensureNoOutParams(MappedStatement ms, BoundSql boundSql) {
if (ms.getStatementType() == StatementType.CALLABLE) {
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
if (parameterMapping.getMode() != ParameterMode.IN) {
throw new ExecutorException("Caching stored procedures with OUT params is not supported. Please configure useCache=false in " + ms.getId() + " statement.");
}
}
}
}
}
7.2 事务缓存管理
TransactionalCacheManager管理事务级别的缓存操作:
java
package org.apache.ibatis.executor;
public class TransactionalCacheManager {
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
public void clear(Cache cache) {
getTransactionalCache(cache).clear();
}
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
private TransactionalCache getTransactionalCache(Cache cache) {
TransactionalCache txCache = transactionalCaches.get(cache);
if (txCache == null) {
txCache = new TransactionalCache(cache);
transactionalCaches.put(cache, txCache);
}
return txCache;
}
}
8. 执行器选择策略
8.1 默认选择逻辑
MyBatis通过Configuration的newExecutor方法创建执行器:
java
public Executor newExecutor(Transaction transaction, ExecutorType execType) {
execType = execType == null ? defaultExecutorType : execType;
execType = execType == null ? ExecutorType.SIMPLE : execType;
Executor executor;
if (ExecutorType.BATCH == execType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == execType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
8.2 选择建议
场景 | 推荐执行器 | 原因 |
---|---|---|
一般业务查询 | SimpleExecutor | 简单可靠,适合大多数场景 |
重复SQL查询 | ReuseExecutor | 减少Statement创建开销 |
批量数据操作 | BatchExecutor | 大幅提升批量操作性能 |
需要缓存 | CachingExecutor + 任意基础执行器 | 提供二级缓存支持 |
高并发读多 | CachingExecutor + ReuseExecutor | 结合缓存和Statement重用 |
9. 实践案例:自定义执行器
9.1 性能监控执行器
让我们创建一个性能监控执行器,记录SQL执行时间:
java
package com.example.executor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.util.Properties;
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class PerformanceMonitorInterceptor implements Interceptor {
private long slowQueryThreshold = 1000; // 慢查询阈值,默认1秒
@Override
public Object intercept(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = invocation.proceed();
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
String methodName = invocation.getMethod().getName();
if (executionTime > slowQueryThreshold) {
System.out.println(String.format("SLOW QUERY DETECTED: %s.%s executed in %d ms",
ms.getId(), methodName, executionTime));
}
System.out.println(String.format("SQL Execution: %s.%s completed in %d ms",
ms.getId(), methodName, executionTime));
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
String threshold = properties.getProperty("slowQueryThreshold");
if (threshold != null) {
this.slowQueryThreshold = Long.parseLong(threshold);
}
}
}
9.2 配置使用
在mybatis-config.xml中配置插件:
xml
<configuration>
<plugins>
<plugin interceptor="com.example.executor.PerformanceMonitorInterceptor">
<property name="slowQueryThreshold" value="500"/>
</plugin>
</plugins>
<settings>
<setting name="defaultExecutorType" value="REUSE"/>
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
9.3 测试代码
java
package com.example;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.session.ExecutorType;
import java.io.InputStream;
/**
* Executor执行器示例测试类
* 展示不同类型执行器的使用和性能特点
*/
public class ExecutorExample {
public static void main(String[] args) throws Exception {
// 加载MyBatis配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 测试不同类型的执行器
testSimpleExecutor(factory);
testReuseExecutor(factory);
testBatchExecutor(factory);
}
/**
* 测试SimpleExecutor(简单执行器)
* 特点:每次执行都创建新的Statement
*/
private static void testSimpleExecutor(SqlSessionFactory factory) {
System.out.println("=== 测试 SimpleExecutor ===");
// 使用SIMPLE类型执行器创建SqlSession
try (SqlSession session = factory.openSession(ExecutorType.SIMPLE)) {
UserMapper mapper = session.getMapper(UserMapper.class);
// 执行多次相同查询,观察性能表现
for (int i = 0; i < 3; i++) {
System.out.println(">>> 第" + (i + 1) + "次查询(SimpleExecutor)");
User user = mapper.findById(1L);
System.out.println("查询结果: " + user);
}
System.out.println("注意:每次查询都会创建新的Statement,但会命中一级缓存");
}
}
/**
* 测试ReuseExecutor(重用执行器)
* 特点:相同SQL会重用Statement对象
*/
private static void testReuseExecutor(SqlSessionFactory factory) {
System.out.println("=== 测试 ReuseExecutor ===");
// 使用REUSE类型执行器创建SqlSession
try (SqlSession session = factory.openSession(ExecutorType.REUSE)) {
UserMapper mapper = session.getMapper(UserMapper.class);
// 执行多次相同查询,Statement会被重用
for (int i = 0; i < 3; i++) {
System.out.println(">>> 第" + (i + 1) + "次查询(ReuseExecutor)");
User user = mapper.findById(1L);
System.out.println("查询结果: " + user);
}
System.out.println("注意:相同SQL会重用Statement,性能优于 SimpleExecutor");
// 测试不同SQL的情况
System.out.println(">>> 执行不同SQL");
User user2 = mapper.findByName("John");
System.out.println("不同SQL查询结果: " + user2);
}
}
/**
* 测试BatchExecutor(批量执行器)
* 特点:将多个操作打包成批量执行,提升性能
*/
private static void testBatchExecutor(SqlSessionFactory factory) {
System.out.println("=== 测试 BatchExecutor ===");
// 使用BATCH类型执行器创建SqlSession
try (SqlSession session = factory.openSession(ExecutorType.BATCH)) {
UserMapper mapper = session.getMapper(UserMapper.class);
System.out.println(">>> 执行批量插入操作");
// 批量插入多个用户
for (int i = 1; i <= 5; i++) {
User user = new User((long) i, "User" + i, "user" + i + "@example.com");
// 注意:这里只是将操作加入批量队列,并没有立即执行
int result = mapper.insertUser(user);
System.out.println("批量插入 User" + i + ",返回值:" + result + " (注意:返回值为固定值)");
}
System.out.println(">>> 执行批量提交");
// 手动刷新批量操作,这时才会真正执行数据库操作
List<BatchResult> batchResults = session.flushStatements();
System.out.println("批量执行结果:");
for (BatchResult batchResult : batchResults) {
System.out.println(" SQL: " + batchResult.getSql());
System.out.println(" 影响行数: " + Arrays.toString(batchResult.getUpdateCounts()));
System.out.println(" 参数数量: " + batchResult.getParameterObjects().size());
}
// 提交事务
session.commit();
System.out.println("批量插入完成,共处理 5 条记录");
System.out.println("注意:BatchExecutor 适合大量数据的批量操作,能显著提升性能");
}
}
}
10. 源码调试指导
10.1 关键断点位置
BaseExecutor断点:
BaseExecutor.query()
- 查询入口,观察缓存逻辑BaseExecutor.createCacheKey()
- 缓存Key创建过程BaseExecutor.queryFromDatabase()
- 数据库查询执行
SimpleExecutor断点:
SimpleExecutor.doQuery()
- 简单查询执行SimpleExecutor.prepareStatement()
- Statement准备过程
ReuseExecutor断点:
ReuseExecutor.prepareStatement()
- Statement重用逻辑ReuseExecutor.hasStatementFor()
- Statement存在性检查
BatchExecutor断点:
BatchExecutor.doUpdate()
- 批量更新逻辑BatchExecutor.doFlushStatements()
- 批量执行提交
CachingExecutor断点:
CachingExecutor.query()
- 二级缓存查询逻辑TransactionalCacheManager.getObject()
- 事务缓存获取
10.2 调试技巧
观察执行器类型:
java
// 在SqlSession创建时观察executor类型
SqlSession session = factory.openSession(ExecutorType.REUSE);
System.out.println("Executor类型: " + session.getConfiguration().getDefaultExecutorType());
观察缓存行为:
java
// 观察一级缓存命中
User user1 = mapper.findById(1L); // 第一次查询,从数据库获取
User user2 = mapper.findById(1L); // 第二次查询,从缓存获取
观察批量操作:
java
// 观察批量执行效果
try (SqlSession session = factory.openSession(ExecutorType.BATCH)) {
// 多次update操作
List<BatchResult> results = session.flushStatements(); // 批量执行
}
11. 易错与排错清单
11.1 常见问题
问题 | 原因 | 解决方案 |
---|---|---|
Statement泄漏 | 忘记关闭Statement | 使用try-with-resources或确保在finally中关闭 |
批量操作不生效 | 忘记调用flushStatements() | 批量操作后必须调用flushStatements() |
缓存不生效 | 配置错误或缓存Key不匹配 | 检查cacheEnabled配置和CacheKey生成逻辑 |
ReuseExecutor性能问题 | 在非重复SQL场景使用 | 根据实际场景选择合适的执行器类型 |
事务缓存问题 | 事务边界与缓存不一致 | 确保事务提交/回滚与缓存操作同步 |
11.2 性能优化建议
- 合理选择执行器:根据业务场景选择合适的执行器类型
- 缓存策略优化:合理配置一级缓存和二级缓存
- 批量操作优化:大量数据操作使用BatchExecutor
- 连接池配置:根据并发量调整数据源连接池参数
- SQL优化:优化SQL语句,减少执行时间
12. 小结
通过本文的学习,我们深入了解了MyBatis Executor执行器体系:
- BaseExecutor:采用模板方法模式,提供统一的执行框架和一级缓存管理
- SimpleExecutor:基础执行器,每次创建新Statement,简单可靠
- ReuseExecutor:重用Statement对象,适合重复SQL场景
- BatchExecutor:批量执行器,大幅提升批量操作性能
- CachingExecutor:装饰器模式实现二级缓存,提升查询性能
重要提示:Executor是MyBatis执行层的核心,理解Executor体系对于掌握MyBatis的执行机制至关重要。通过源码分析和实践案例,我们能够更好地理解不同执行器的适用场景和性能特点。
在下一篇文章中,我们将深入分析StatementHandler语句处理器,了解SQL语句的预处理和执行过程。
思考题:
- 为什么MyBatis要设计多种Executor类型?它们各自的优势是什么?
- BaseExecutor的模板方法模式是如何实现的?这种设计有什么优势?
- CachingExecutor的装饰器模式是如何工作的?与一级缓存有什么区别?
- 在什么场景下应该使用BatchExecutor?使用时需要注意什么问题?