前言
在Mybatis源码-SqlSession获取文章中已经知道,Mybatis
中获取SqlSession
时会创建执行器Executor
并存放在SqlSession
中,通过SqlSession
可以获取映射接口的动态代理对象,动态代理对象的生成可以参考Mybatis源码-加载映射文件与动态代理,可以用下图进行概括。
所以,映射接口的动态代理对象实际执行方法时,执行的请求最终会由MapperMethod
的execute()
方法完成。本篇文章将以MapperMethod
的execute()
方法作为起点,对Mybatis
中的一次实际执行请求进行说明,并结合源码对执行器Executor
的原理进行阐释。本篇文章不会对Mybatis
中的缓存进行说明,关于Mybatis
中的一级缓存和二级缓存相关内容,会在后续的文章中单独进行分析,为了屏蔽Mybatis
中的二级缓存的干扰,需要在Mybatis
的配置文件中添加如下配置以禁用二级缓存。
正文
本节将以一个实际的查询例子,以单步跟踪并结合源码的方法,对Mybatis
的一次实际执行请求进行说明。给定映射接口如下所示。
public interface BookMapper {
Book selectBookById(int id);
}
给定映射文件如下所示。
Mybatis
的执行代码如下所示。
public class MybatisTest {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream(resource));
// 获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取映射接口的动态代理对象
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
// 执行一次查询操作
System.out.println(bookMapper.selectBookById(1));
}
}
基于上述的映射接口,映射文件和执行代码,最终执行查询操作时,会调用到MapperMethod
的execute()
方法并进入查询的逻辑分支,这部分源码如下所示。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
......
case SELECT:
// 根据实际执行的方法的返回值的情况进入不同的逻辑分支
if (method.returnsVoid() && method.hasResultHandler()) {
// 无返回值情况
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 返回值为集合的情况
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
// 返回值为map的情况
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
// 返回值为迭代器的情况
result = executeForCursor(sqlSession, args);
} else {
// 上述情况之外的情况
// 将方法的入参转换为Sql语句的参数
Object param = method.convertArgsToSqlCommandParam(args);
// 调用DefaultSqlSession的selectOne()方法执行查询操作
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
......
}
......
return result;
}
已知映射接口中的每个方法都会对应一个MapperMethod
,MapperMethod
中的SqlCommand
会指示该方法对应的MappedStatement
信息和类型信息(SELECT,UPDATE等),MapperMethod
中的MethodSignature
会存储该方法的参数信息和返回值信息,所以在上述的MapperMethod
的execute()
方法中,首先根据SqlCommand
的指示的类型进入不同的逻辑分支,本示例中会进入SELECT的逻辑分支,然后又会根据MethodSignature
中指示的方法返回值情况进入不同的查询分支,本示例中的方法返回值既不是集合,map或迭代器,也不是空,所以会进入查询一条数据的查询分支。本示例中单步跟踪到这里时,数据如下所示。
在MapperMethod
中的execute()
方法中会调用到DefaultSqlSession
的selectOne()
方法执行查询操作,该方法实现如下所示。
@Override
public T selectOne(String statement) {
return this.selectOne(statement, null);
}
@Override
public T selectOne(String statement, Object parameter) {
// 查询操作会由selectList()完成
List list = this.selectList(statement, parameter);
if (list.size() == 1) {
// 查询结果只有一个时,返回查询结果
return list.get(0);
} else if (list.size() > 1) {
// 查询结果大于一个时,报错
throw new TooManyResultsException(
"Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
DefaultSqlSession
的selectOne()
方法中会将查询请求交由DefaultSqlSession
的selectList()
方法完成,如果selectList()
方法返回的结果集合中只有一个返回值,就将这个返回值返回,如果多于一个返回值,就报错。DefaultSqlSession
的selectList()
方法如下所示。
@Override
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 从Configuration中的mappedStatements缓存中获取MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
// 调用Executor的query()方法执行查询操作
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();
}
}
在DefaultSqlSession
的selectList()
方法中,会先根据statement参数值在Configuration
中的mappedStatements缓存中获取MappedStatement
,statement参数值其实就是MapperMethod
中的SqlCommand
的name
字段,是MappedStatement
在mappedStatements缓存中的唯一标识。获取到MappedStatement
后,就会调用Executor
的query()
方法执行查询操作,因为禁用了二级缓存,所以这里的Executor
实际上为SimpleExecutor
。本示例中单步跟踪到这里时,数据如下所示。
SimpleExecutor
的类图如下所示。
SimpleExecutor
和BaseExecutor
之间使用了模板设计模式,调用SimpleExecutor
的query()
方法时会调用到BaseExecutor
的query()
方法,如下所示。
@Override
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler) throws SQLException {
// 获取Sql语句
BoundSql boundSql = ms.getBoundSql(parameter);
// 生成CacheKey
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 调用重载的query()方法
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
继续看BaseExecutor
中的重载的query()
方法,如下所示。
@Override
public List 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 list;
try {
queryStack++;
// 先从一级缓存中根据CacheKey命中查询结果
list = resultHandler == null ? (List) 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 (BaseExecutor.DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
上述的query()
方法大部分逻辑是在为Mybatis
中的一级缓存服务,这里暂时不分析,除开缓存的逻辑,上述query()
方法做的事情可以概括为:先从缓存中获取查询结果,获取到则返回缓存中的查询结果,否则直接查询数据库。下面分析直接查询数据库的逻辑,queryFromDatabase()
方法的实现如下所示。
private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 调用doQuery()进行查询操作
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;
}
上述queryFromDatabase()
方法中,会调用BaseExecutor
定义的抽象方法doQuery()
进行查询,本示例中,doQuery()
方法由SimpleExecutor
进行了实现,如下所示。
@Override
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 创建RoutingStatementHandler
StatementHandler handler = configuration.newStatementHandler(
wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 实例化Statement
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行查询
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
上述的doQuery()
方法中,做了三件事情,第一件事情
是创建RoutingStatementHandler
,实际上RoutingStatementHandler
正如其名字所示,仅仅只是做一个路由转发的作用,在创建RoutingStatementHandler
时,会根据映射文件中CURD标签上的statementType属性决定创建什么类型的StatementHandler
并赋值给RoutingStatementHandler
中的delegate字段,后续对RoutingStatementHandler
的所有操作均会被其转发给delegate,此外在初始化SimpleStatementHandler
,PreparedStatementHandler
和CallableStatementHandler
时还会一并初始化ParameterHandler
和ResultSetHandler
。映射文件中CURD标签上的statementType属性与StatementHandler
的对应关系如下。
statementType属性 | 对应的StatementHandler |
作用 |
---|---|---|
STATEMENT | SimpleStatementHandler |
直接操作SQL ,不进行预编译 |
PREPARED | PreparedStatementHandler |
预编译SQL |
CALLABLE | CallableStatementHandler |
执行存储过程 |
RoutingStatementHandler
与SimpleStatementHandler
,PreparedStatementHandler
和CallableStatementHandler
的关系可以用下图示意。
在创建RoutingStatementHandler
之后,还会为RoutingStatementHandler
植入插件逻辑。Configuration
的newStatementHandler()
方法实现如下。
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 创建RoutingStatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(
executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 为RoutingStatementHandler植入插件逻辑
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
继续分析doQuery()
方法中的第二件事情
,即实例化Statement
,prepareStatement()
方法实现如下所示。
private Statement prepareStatement(StatementHandler handler, Log statementLog)
throws SQLException {
Statement stmt;
// 获取到Connection对象并为Connection对象生成动态代理对象
Connection connection = getConnection(statementLog);
// 通过Connection对象的动态代理对象实例化Statement
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
prepareStatement()
方法中首先会从Transaction
中将数据库连接对象Connection
对象获取出来并为其生成动态代理对象以实现日志打印功能的增强,然后会通过Connection
的动态代理对象实例化Statement
,最后会处理Statement
中的占位符,比如将PreparedStatement
中的?
替换为实际的参数值。
继续分析doQuery()
方法中的第三件事情
,即执行查询。本篇文章的示例中,映射文件的CURD标签没有对statementType属性进行设置,因此查询的操作最终会被RoutingStatementHandler
路由转发给PreparedStatementHandler
的query()
方法,如下所示。
@Override
public List query(Statement statement, ResultHandler resultHandler)
throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 调用到JDBC的逻辑了
ps.execute();
// 调用ResultSetHandler处理查询结果
return resultSetHandler.handleResultSets(ps);
}
如上所示,在PreparedStatementHandler
的query()
方法中就会调用到JDBC
的逻辑向数据库进行查询,最后还会使用已经初始化好并植入了插件逻辑的ResultSetHandler
处理查询结果并返回。
至此,对Mybatis
的一次实际执行请求的说明到此为止,本篇文章中的示例以查询为例,增删改大体类似,故不再赘述。
总结
Mybatis
中的执行器Executor
会在创建SqlSession
时一并被创建出来并被存放于SqlSession
中,如果禁用了二级缓存,则Executor
实际为SimpleExecutor
,否则为CachingExecutor
。Mybatis
中的一次实际执行,会由所执行方法对应的MapperMethod
的execute()
方法完成,在execute()
方法中,会根据执行操作的类型(增改删查)调用SqlSession
中的相应的方法,例如insert()
,update()
,delete()
和select()
等,MapperMethod
在这其中的作用就是MapperMethod
关联着本次执行方法所对应的SQL
语句以及入参和出参等信息。在SqlSession
的insert()
,update()
,delete()
和select()
等方法中,SqlSession
会将与数据库的操作交由执行器Executor
来完成,无论是在SimpleExecutor
还是CachingExecutor
中,如果抛开缓存相关的逻辑,这些Executor
均会先根据映射文件中CURD标签的statementType字段创建相应的StatementHandler
,创建StatementHandler
的过程中还会一并将处理参数和处理结果的ParameterHandler
和ResultSetHandler
创建出来,创建好StatementHandler
之后,会基于StatementHandler
实例化Statement
,最后在StatementHandler
中基于实例化好的Statement
完成和数据库的交互,基于创建好的ResultSetHandler
处理交互结果并将结果返回。