前面介绍了 MyBatis 会话相关的知识,其中创建 SqlSession
实例的过程中会创建一个 Executor
实例,一个会话会持有一个执行器。这篇文章就介绍一下执行器相关的知识。
创建执行器
执行器是通过 Configuration#newExecutor
方法创建的
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//未指定执行器类型则取默认执行器类型
executorType = executorType == null ? defaultExecutorType : executorType;
//默认执行器类型未设置则取 ExecutorType.SIMPLE
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor 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);
}
//开启二级缓存则创建 CachingExecutor
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//过一遍拦截器链(插件)
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
- 根据执行器类型创建执行器,优先级:指定类型 > 配置的默认类型>
ExecutorType.SIMPLE
类型。 - 如果开启了二级缓存会创建一个
CachingExecutor
我所用的MyBatis 3.5.0
版本cacheEnabled
值默认是ture
,所以这里创建的就是CachingExecutor
,也可以通过设置<setting name="cacheEnabled" value="false"/>
来关闭二级缓存,关于缓存后面会单独介绍。 - 最后会走一遍拦截器链,其实就是配置的插件,关于插件后面会单独介绍。
执行器分类
CachingExecutor
使用装饰器模式,内部持有另外一个Executor
,用于二级缓存未命中时查询。BaseExecutor
使用模板模式,其子类的操作都以该类为入口,查询逻辑最终会调用doQuery
方法,更新操作最终都会调用doUpdate
方法,这些都交由其子类实现。SimpleExecutor
默认的简单执行器BatchExecutor
用来支撑批量操作的批量执行器ReuseExecutor
通过内部维护一个statementMap
达到重用Statement
的目的
执行器是干嘛的
我们知道 SqlSession
实例会持有一个 Executor
实例,那么执行器是不是和会话的 CURD 操作有关呢?是的,SqlSession
的查询操作都会委托给 Executor
的 query
方法,增删改操作都会通过执行器的 update
方法完成。那就拿 selectList
来看看具体是怎么实现的吧。
//org.apache.ibatis.session.defaults.DefaultSqlSession
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
首先通过 statement
查询对应的 MappedStatement
,这里的 statement
其实就是 Mapper 接口方法的全限定名,MappedStatement
是 mapper.xml 文件中与之对应的 SQL。为了保证能唯一对应,所以 Mapper 接口中方法不支持重载。
接着就是 Executor
发乎作用的地方了,这里先不考虑开启二级缓存的情况,所以来看看 BaseExecutor#query
方法逻辑。
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
......
List<E> list;
try {
queryStack++;
//和一级缓存相关,暂不用了解
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//一级缓存中不存在,查 DB
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
......
return list;
}
直接看查询 DB 的逻辑
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);
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;
}
最终实际查询的逻辑交由子类去实现 doQuery
方法来完成,就拿默认的 SimpleExecutor#doQuery
来看看吧
@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
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
看到这里就要引入 MyBatis 中另外一个比较重要的组件 StatementHandler
了,关于它后面会单独介绍。
总结
关于执行器,只需要知道 SqlSession
的 CRUD 操作都会委托给 Executor
去执行,记住几种执行器的类型。实际代码中会涉及到一级、二级缓存相关的知识,并且最终的操作还是交给了 StatementHandler
去完成的,所以等把这两块知识介绍完后,整条链路就清晰了。