共计 10066 个字符,预计需要花费 26 分钟才能阅读完成。
前言
在 Mybatis 源码 -SqlSession 获取文章中曾经晓得,Mybatis
中获取 SqlSession
时会创立执行器 Executor
并存放在 SqlSession
中,通过 SqlSession
能够获取映射接口的动静代理对象,动静代理对象的生成能够参考 Mybatis 源码 - 加载映射文件与动静代理,能够用下图进行概括。
所以,映射接口的动静代理对象理论执行办法时,执行的申请最终会由 MapperMethod
的execute()
办法实现。本篇文章将以 MapperMethod
的execute()
办法作为终点,对 Mybatis
中的一次理论执行申请进行阐明,并联合源码对执行器 Executor
的原理进行阐释。本篇文章不会对 Mybatis
中的缓存进行阐明,对于 Mybatis
中的 一级缓存 和二级缓存 相干内容,会在后续的文章中独自进行剖析,为了屏蔽 Mybatis
中的二级缓存的烦扰,须要在 Mybatis
的配置文件中增加如下配置以禁用二级缓存。
<settings>
<setting name="cacheEnabled" value="false"/>
</settings>
注释
本节将以一个理论的查问例子,以单步跟踪并联合源码的办法,对 Mybatis
的一次理论执行申请进行阐明。给定映射接口如下所示。
public interface BookMapper {Book selectBookById(int id);
}
给定映射文件如下所示。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mybatis.learn.dao.BookMapper">
<resultMap id="bookResultMap" type="com.mybatis.learn.entity.Book">
<result column="b_name" property="bookName"/>
<result column="b_price" property="bookPrice"/>
</resultMap>
<select id="selectBookById" resultMap="bookResultMap">
SELECT
b.id, b.b_name, b.b_price
FROM
book b
WHERE
b.id=#{id}
</select>
</mapper>
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> T selectOne(String statement) {return this.selectOne(statement, null);
}
@Override
public <T> T selectOne(String statement, Object parameter) {// 查问操作会由 selectList()实现
List<T> 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 <E> List<E> 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 <E> List<E> 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 <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++;
// 先从一级缓存中依据 CacheKey 命中查问后果
list = resultHandler == null ? (List<E>) 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 <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 {// 调用 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 <E> List<E> 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 <E> List<E> 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
解决交互后果并将后果返回。