MyBatis 源碼分析篇 5:Mapper 方法執行之 Executor

通過上一篇的討論 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 的聲明

executor 類型為 CachingExecutor

我們看到 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 的結果:

delegate 的聲明
delegate 類型為 SimpleExecutor

咦?怎么又出來一個 SimpleExecutor?delegate 又是何時實例化以及如何實例化的呢?還記不記得上面我們遺留的問題(DefaultSqlSession 中的 executor)。事實上不知道大家還記不記得在 MyBatis 源碼分析篇 2:SqlSession 一文中,我們就已經見過 Executor了(SqlSession 將數據庫執行的具體操作委托給了 Executor 來實現)。那么現在我們就來認識一下 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 為裝飾器類,便于擴展。

CachingExecutor 的裝飾器模式

另外,同樣地,BaseExecutor 也使用了裝飾器模式:

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 簡易實現

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380