通過上一篇的討論 MyBatis 源碼分析篇 4:Mapper 方法執行,我們已經知道 MyBatis 在獲取到 Mapper 接口之后,其方法是通過在動態代理中調用 SqlSession 的方法來執行數據庫操作的。那么在 SqlSession 中它具體又是怎么做的呢?這一篇我們就一起來看看 SqlSession 是如何執行數據庫操作的。
我們還是以上一篇使用的測試代碼為入口繼續 debug:
List<Author> author = mapper.selectAllTest();
selectAllTest 方法的 sql 如下:
select id, name, sex, phone from author
跳過前面的執行,我們直接進入方法 executeForMany():
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>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 <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();
}
}
我們看到第 5 行代碼:executor.query(...);。再查看一下 executor 的聲明和 debug 的結果:
我們看到 executor 是 CachingExecutor 類型的,那么問題就來了:executor 是何時實例化又是如何實例化的呢?我們先帶著這個問題繼續往下走,直到 org.apache.ibatis.executor.CachingExecutor 類的 query() 方法:
@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) {
//太長省略啦...
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
我們直接看最后一行代碼,delegate.<E> query(...);,再查看一下 delegate 的聲明和 debug 的結果:
咦?怎么又出來一個 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.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 繼續跟進代碼,我們已經知道 delegate 這時是 SimpleExecutor 類型的,但是 SimpleExecutor 類中沒有該 query 方法的實現,那么 debug 就自然會進入其父類 BaseExecutor:
@Override
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 {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
//...
return list;
}
繼續跟進幾步,發現它又跳入了子類 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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
跟進第 8 行代碼,進入 org.apache.ibatis.executor.statement.RoutingStatementHandler,我們會發現 RoutingStatementHandler 也是用了裝飾器模式(真是無處不在的裝飾器啊( ̄▽ ̄)/)。
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.<E>query(statement, resultHandler);
}
繼續跟進,最后,我們會進入 org.apache.ibatis.executor.statement.PreparedStatementHandler 的 query 方法,這時,看到一段非常熟悉的代碼:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
可愛的 JDBC 代碼有木有!!好啦,到這里我們終于吧啦完了 Mybatis 是如何執行數據庫操作的。。
最后,總結一下(敲黑板),這篇文章我們以兩個問題為出發點,講解了 Executor 及其實現類,進一步引出了對 Configuration 何時且如何讀取 XML 中的元素的討論,并看到了無處不在的裝飾器模式,我們在實際編碼中也要善于合理地使用這些優秀的設計模式哦。
希望大家沒有被我繞暈,個人經驗,閱讀源碼呢,需要綜合正向和反向思維,善于猜想和驗證,勤于思考,找到合適的切入點,一點一點深入,看不懂了就多重復幾次。一開始跟丟了呢,就各種笨辦法上,例如
Ctrl+Shift+F(intellij idea)。比如我一開始不知道哪里實例化的 Executor 就是全局查找 new SimpleExecutor,然后反向推,一步步就找到了入口。還有一點要注意的是,看源碼要帶有目的性,即主次分明,比如我就是要看它是如何實現查詢的,那么其他關于緩存啊,參數和結果集映射等代碼就都先略過不看,就只一步步跟入實現查詢的代碼。每一塊兒都可以這么來看,等到這樣看完所有的關鍵點,就可以對整體有了一個很好的理解。
小可愛們,散會!!(〃'▽'〃)
附:
當前版本:mybatis-3.5.0
官網文檔:MyBatis
項目實踐:MyBatis Learn
手寫源碼:MyBatis 簡易實現