通过上一篇的讨论 MyBatis 源码分析篇 4:Mapper 方法执行,我们已经知道 MyBatis 在获取到 Mapper 接口之后,其方法是通过在动态代理中调用 SqlSession 的方法来执行数据库操作的。那么在 SqlSession 中它具体又是怎么做的呢?这一篇我们就一起来看看 SqlSession 是如何执行数据库操作的。
我们还是以上一篇使用的测试代码为入口继续 debug:
List author = mapper.selectAllTest();
selectAllTest 方法的 sql 如下:
select id, name, sex, phone from author
跳过前面的执行,我们直接进入方法 executeForMany():
private Object executeForMany(SqlSession sqlSession, Object[] args) {
List result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
进入第 8 行代码,继续 debug,直到进入 org.apache.ibatis.session.defaults.DefaultSqlSession 类的 selectList 方法:
@Override
public List 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();
}
}
我们看到第 5 行代码:executor.query(...);。再查看一下 executor 的声明和 debug 的结果:
我们看到 executor 是 CachingExecutor 类型的,那么问题就来了:executor 是何时实例化又是如何实例化的呢?我们先带着这个问题继续往下走,直到 org.apache.ibatis.executor.CachingExecutor 类的 query() 方法:
@Override
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
//太长省略啦...
}
return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
我们直接看最后一行代码,delegate.
咦?怎么又出来一个 SimpleExecutor?delegate 又是何时实例化以及如何实例化的呢?还记不记得上面我们遗留的问题(DefaultSqlSession 中的 executor)。事实上不知道大家还记不记得在 MyBatis 源码分析篇 2:SqlSession 一文中,我们就已经见过 Executor了(SqlSession 将数据库执行的具体操作委托给了 Executor 来实现)。那么现在我们就来认识一下 Executor 及其实现类吧。
Executor 是一个接口,声明了操作数据库的方法,其实现类有:
它们之间的关系是:BaseExecutor 和 CachingExecutor 直接实现了 Executor 接口;BatchExecutor、ClosedExecutor(内部类)、ReuseExecutor 和 SimpleExecutor 继承了 BaseExecutor。
MyBatis 将 Executor 类型的 executor 作为 SqlSession 持有的成员变量,来执行底层的数据库操作。该成员变量的实例化是在获取 session 时实现的:调用 SqlSessionFactory 中任意一个 openSession(...) 方法。
点进去该接口方法的实现类 org.apache.ibatis.session.defaults.DefaultSqlSessionFactory ,我们最终可以看到 :
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
注意第 7 行代码:final Executor executor = configuration.newExecutor(tx, execType);,我们发现在这个地方对 executor 做了赋值操作,继续点进去代码,进入 org.apache.ibatis.session.Configuration 类的 newExecutor 方法:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
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);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
这段代码则是对 executor 的具体实例化的操作,从这段代码可以看出,executor 具体使用哪个实现类来实例化取决于 executorType 的值。如果方法传入的 executorType 为空,那么 executorType 将赋值为 Configuration 持有的 defaultExecutorType,如果 defaultExecutorType 也为空,就默认赋值为 ExecutorType.SIMPLE(枚举值)。紧接着下面的几个 if 条件就根据 executorType 和 cacheEnabled 来决定如何实例化。
现在我们就能抽取出这段代码的关键决定因素:executorType 和 cacheEnabled。 假设我们执行的测试代码中调用的是无参的 openSession() 方法,那么我们就需要关注的是 defaultExecutorType。
在 Configuration 类中的成员变量 defaultExecutorType 默认值为 ExecutorType.SIMPLE,cacheEnabled 默认值为 true。
//...
protected boolean cacheEnabled = true;
//...
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
//...
根据这两个值以及上面实例化 executor 的代码,我们可以得出结论:初始时的 executor 是 CachingExecutor 类型的。
那么看到这里,第一个问题便迎刃而解了,我们现在就知道了 DefaultSqlSession 中的 executor 成员变量是在 openSession 时,通过 Configuration 中的 newExecutor() 方法实例化为 CachingExecutor 的(前提是未配置 cacheEnabled )。
针对括号中的内容,需要补充的一点是,无论我们使用的是读取 XML 的方式还是通过 Class 的方式来获取 Configuration,都可以在配置中传入defaultExecutorType 和 cacheEnabled。 以 XML 方式为例,我们通过跟踪 defaultExecutorType 和 cacheEnabled 来整体看一下 Configuration 是如何读取配置的:
要想获取 SqlSession,首先要获取 SqlSessionFactory:
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
进入org.apache.ibatis.session.SqlSessionFactoryBuilder 的 build() 方法:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
//...
} finally {
//...
}
}
我们继续点击第 4 行代码的 parser.parse() 方法,进入 org.apache.ibatis.builder.xml.XMLConfigBuilder:
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
从这个方法我们可以看到很多熟悉的东西,即在 mybatis-config.xml 中我们配置过的 properties 、settings、typeAliases、plugins、objectFactory、environments、mappers 等元素。以 settings 为例,点击进入 settingsElement(settings); 方法,我们会看到 settings 允许配置的所有子元素 setting 的内容:
private void settingsElement(Properties props) throws Exception {
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
@SuppressWarnings("unchecked")
Class extends TypeHandler> typeHandler = (Class extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
configuration.setDefaultEnumTypeHandler(typeHandler);
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
@SuppressWarnings("unchecked")
Class extends Log> logImpl = (Class extends Log>)resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
其中,就包含 cacheEnabled(第 4 行,默认值为 true)和 defaultExecutorType(第 11 行,默认值为 "SIMPLE")。也就是说,如果我们想使用别的 Executor 实现类,只需要传入对应的 cacheEnabled 和 defaultExecutorType 就可以。
好了,到这里我们就剩下一个问题了:CachingExecutor 中的 Executor 类型的 delegate 是如何实例化成 SimpleExecutor 的?
喂,等等,这句话怎么读着这么绕呢?怎么 Executor 实现类里又持有一个 Executor 变量呢?事实上,如果你熟悉设计模式的话,我想你一定不难看出,在这里,MyBatis 使用了装饰器模式:CachingExecutor 为装饰器类,便于扩展。
另外,同样地,BaseExecutor 也使用了装饰器模式:
我们再次回到 Configuration 的 newExecutor(...) 方法:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
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);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
这时候要关注的是实例化 CachingExecutor 的倒数第 5 行代码:executor = new CachingExecutor(executor);。通过第一个问题的讲解我们已经得出结论:默认情况下,在 if (cacheEnabled) 执行之前,executor 的为 SimpleExecutor 类型。那么由此而知,此时传入到 CachingExecutor 装饰器类中实际的类型为 SimpleExecutor,即 CachingExecutor 中的 delegate 此时为 SimpleExecutor 类型。好了,第二个问题便说清楚了。
现在我们就接着之前位置:delegate.
@Override
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//...
List list;
try {
queryStack++;
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--;
}
//...
return list;
}
继续跟进几步,发现它又跳入了子类 SimpleExecutor 的 doQuery() 方法:
@Override
public List 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);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
跟进第 8 行代码,进入 org.apache.ibatis.executor.statement.RoutingStatementHandler,我们会发现 RoutingStatementHandler 也是用了装饰器模式(真是无处不在的装饰器啊( ̄▽ ̄)/)。
@Override
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.query(statement, resultHandler);
}
继续跟进,最后,我们会进入 org.apache.ibatis.executor.statement.PreparedStatementHandler 的 query 方法,这时,看到一段非常熟悉的代码:
@Override
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler. handleResultSets(ps);
}
可爱的 JDBC 代码有木有!!好啦,到这里我们终于吧啦完了 Mybatis 是如何执行数据库操作的。。
最后,总结一下(敲黑板),这篇文章我们以两个问题为出发点,讲解了 Executor 及其实现类,进一步引出了对 Configuration 何时且如何读取 XML 中的元素的讨论,并看到了无处不在的装饰器模式,我们在实际编码中也要善于合理地使用这些优秀的设计模式哦。
希望大家没有被我绕晕,个人经验,阅读源码呢,需要综合正向和反向思维,善于猜想和验证,勤于思考,找到合适的切入点,一点一点深入,看不懂了就多重复几次。一开始跟丢了呢,就各种笨办法上,例如
Ctrl+Shift+F(intellij idea)。比如我一开始不知道哪里实例化的 Executor 就是全局查找 new SimpleExecutor,然后反向推,一步步就找到了入口。还有一点要注意的是,看源码要带有目的性,即主次分明,比如我就是要看它是如何实现查询的,那么其他关于缓存啊,参数和结果集映射等代码就都先略过不看,就只一步步跟入实现查询的代码。每一块儿都可以这么来看,等到这样看完所有的关键点,就可以对整体有了一个很好的理解。
小可爱们,散会!!(〃'▽'〃)
附:
当前版本:mybatis-3.5.0
官网文档:MyBatis
项目实践:MyBatis Learn
手写源码:MyBatis 简易实现