Mybati源碼解析篇之六劍客!!!

目錄

  • 前言
  • 環境版本
  • Mybatis的六劍客
    • SqlSession
      • 有何方法
      • 語句執行方法
      • 立即批量更新方法
      • 事務控制方法
      • 本地緩存方法
      • 獲取映射方法
      • 有何實現類?
    • Executor
      • 實現類
        • BaseExecutor
        • CachingExecutor
        • SimpleExecutor
        • BatchExecutor
        • ReuseExecutor
      • SpringBoot中如何創建?
    • StatementHandler
      • 實現類
        • SimpleStatementHandler
        • PreparedStatementHandler
        • CallableStatementHandler
        • RoutingStatementHandler
    • ParameterHandler
    • TypeHandler
    • ResultSetHandler
  • 總結

前言

  • Mybatis的專題文章寫到這裡已經是第四篇了,前三篇講了Mybatis的基本使用,相信只要認真看了的朋友,在實際開發中正常使用應該不是問題。沒有看過的朋友,作者建議去看一看,三篇文章分別是Mybatis入門之基本操作Mybatis結果映射,你射准了嗎?Mybatis動態SQL,你真的會了嗎?
  • 當然,任何一個技術都不能淺藏輒止,今天作者就帶大家深入底層源碼看一看Mybatis的基礎架構。此篇文章只是源碼的入門篇,講一些Mybatis中重要的組件,作者稱之為六劍客

環境版本

  • 本篇文章講的一切內容都是基於Mybatis3.5SpringBoot-2.3.3.RELEASE

Myabtis的六劍客

  • 其實Mybatis的底層源碼和Spring比起來還是非常容易讀懂的,作者將其中六個重要的接口抽離出來稱之為Mybatis的六劍客,分別是SqlSessionExecutorStatementHandlerParameterHandlerResultSetHandlerTypeHandler
  • 六劍客在Mybatis中分別承擔著什麼角色?下面將會逐一介紹。
  • 介紹六劍客之前,先來一張六劍客執行的流程圖,如下:

SqlSession

  • SqlSession是Myabtis中的核心API,主要用來執行命令,獲取映射,管理事務。它包含了所有執行語句、提交或回滾事務以及獲取映射器實例的方法。

有何方法

  • 其中定義了將近20個方法,其中涉及的到語句執行,事務提交回滾等方法。下面對於這些方法進行分類總結。

語句執行方法

  • 這些方法被用來執行定義在 SQL 映射 XML 文件中的 SELECT、INSERT、UPDATE 和 DELETE 語句。你可以通過名字快速了解它們的作用,每一方法都接受語句的 ID 以及參數對象,參數可以是原始類型(支持自動裝箱或包裝類)、JavaBean、POJO 或 Map。
<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<T> Cursor<T> selectCursor(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
  • 其中的最容易誤解的就是selectOneselectList,從方法名稱就很容易知道區別,一個是查詢單個,一個是查詢多個。如果你對自己的SQL無法確定返回一個還是多個結果的時候,建議使用selectList
  • insertupdatedelete方法返值是受影響的行數。
  • select還有幾個重用的方法,用於限制返回行數,在Mysql中對應的就是limit,如下:
<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds)
void select (String statement, Object parameter, ResultHandler<T> handler)
void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler)
  • 其中的RowBounds參數中保存了限制的行數,起始行數。

立即批量更新方法

  • 當你將 ExecutorType 設置為 ExecutorType.BATCH 時,可以使用這個方法清除(執行)緩存在 JDBC 驅動類中的批量更新語句。
List<BatchResult> flushStatements()

事務控制方法

  • 有四個方法用來控制事務作用域。當然,如果你已經設置了自動提交或你使用了外部事務管理器,這些方法就沒什麼作用了。然而,如果你正在使用由 Connection 實例控制的 JDBC 事務管理器,那麼這四個方法就會派上用場:
void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)
  • 默認情況下 MyBatis 不會自動提交事務,除非它偵測到調用了插入、更新或刪除方法改變了數據庫。如果你沒有使用這些方法提交修改,那麼你可以在commitrollback 方法參數中傳入 true 值,來保證事務被正常提交(注意,在自動提交模式或者使用了外部事務管理器的情況下,設置 force 值對 session 無效)。大部分情況下你無需調用 rollback(),因為 MyBatis 會在你沒有調用 commit 時替你完成回滾操作。不過,當你要在一個可能多次提交或回滾的 session 中詳細控制事務,回滾操作就派上用場了。

本地緩存方法

  • Mybatis 使用到了兩種緩存:本地緩存(local cache)和二級緩存(second level cache)。
  • 默認情況下,本地緩存數據的生命周期等同於整個 session 的周期。由於緩存會被用來解決循環引用問題和加快重複嵌套查詢的速度,所以無法將其完全禁用。但是你可以通過設置 localCacheScope=STATEMENT 來只在語句執行時使用緩存。
  • 可以調用以下方法清除本地緩存。
void clearCache()

獲取映射器

  • 在SqlSession中你也可以獲取自己的映射器,直接使用下面的方法,如下:
<T> T getMapper(Class<T> type)
  • 比如你需要獲取一個UserMapper,如下:
UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class);

有何實現類

  • 在Mybatis中有三個實現類,分別是DefaultSqlSessionSqlSessionManagerSqlSessionTemplate,其中重要的就是DefaultSqlSession,這個後面講到Mybatis執行源碼的時候會一一分析。
  • 在與SpringBoot整合時,Mybatis的啟動器配置類會默認注入一個SqlSessionTemplate,源碼如下:
@Bean
  @ConditionalOnMissingBean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) 
    //根據執行器的類型創建不同的執行器,默認CachingExecutor
    ExecutorType executorType = this.properties.getExecutorType();
    if (executorType != null) {
      return new SqlSessionTemplate(sqlSessionFactory, executorType);
    } else {
      return new SqlSessionTemplate(sqlSessionFactory);
    }
  }

Executor

  • Mybatis的執行器,是Mybatis的調度核心,負責SQL語句的生成和緩存的維護,SqlSession中的crud方法實際上都是調用執行器中的對應方法執行。
  • 繼承結構如下圖:
    繼承結構

實現類

  • 下面我們來看看都有哪些實現類,分別有什麼作用。

BaseExecutor

  • 這是一個抽象類,採用模板方法的模式,有意思的是這個老弟模仿Spring的方式,真正的執行的方法都是doxxx()
  • 其中有一個方法值得注意,查詢的時候走的一級緩存,因此這裡注意下,既然這是個模板類,那麼Mybatis執行select的時候默認都會走一級緩存。代碼如下:
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    //此處的localCache即是一級緩存,是一個Map的結構
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      //執行真正的查詢
      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;
  }

CachingExecutor

  • 這個比較有名了,二級緩存的維護類,與SpringBoot整合默認創建的就是這個傢伙。下面來看一下如何走的二級緩存,源碼如下:
@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    //查看當前Sql是否使用了二級緩存
    Cache cache = ms.getCache();
    //使用緩存了,直接從緩存中取
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        //從緩存中取數據
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          //沒取到數據,則執行SQL從數據庫查詢
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          //查到了,放入緩存中
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        //直接返回
        return list;
      }
    }
    //沒使用二級緩存,直接執行SQL從數據庫查詢
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
  • 這玩意就是走個二級緩存,其他沒什麼。

SimpleExecutor

  • 這個類像個直男,最簡單的一個執行器,就是根據對應的SQL執行,不會做一些額外的操作。

BatchExecutor

  • 通過批量操作來優化性能。通常需要注意的是批量更新操作,由於內部有緩存的實現,使用完成後記得調用flushStatements來清除緩存。

ReuseExecutor

  •  可重用的執行器,重用的對象是Statement,也就是說該執行器會緩存同一個sql的Statement,省去Statement的重新創建,優化性能。
  • 內部的實現是通過一個HashMap來維護Statement對象的。由於當前Map只在該session中有效,所以使用完成後記得調用flushStatements來清除Map。

SpringBoot中如何創建

  • 在SpringBoot到底創建的是哪個執行器呢?其實只要閱讀一下源碼可以很清楚的知道,答案就在org.apache.ibatis.session.Configuration類中,其中創建執行器的源碼如下:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    //沒有指定執行器的類型,創建默認的,即是SimpleExecutor
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //類型是BATCH,創建BatchExecutor
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
      //類型為REUSE,創建ReuseExecutor
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      //除了上面兩種,創建的都是SimpleExecutor
      executor = new SimpleExecutor(this, transaction);
    }
    //如果全局配置了二級緩存,則創建CachingExecutor,SpringBoot中這個參數默認是true,可以自己設置為false
    if (cacheEnabled) {
    //創建CachingExecutor
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
  • 顯而易見,SpringBoot中默認創建的是CachingExecutor,因為默認的cacheEnabled的值為true

StatementHandler

  • 熟悉JDBC的朋友應該都能猜到這個接口是幹嘛的,很顯然,這個是對SQL語句進行處理和參數賦值的。

實現類

  • 該接口也是有很多的實現類,如下圖:

SimpleStatementHandler

  • 這個很簡單了,就是對應我們JDBC中常用的Statement接口,用於簡單SQL的處理

PreparedStatementHandler

  • 這個對應JDBC中的PreparedStatement,預編譯SQL的接口。

CallableStatementHandler

  • 這個對應JDBC中CallableStatement,用於執行存儲過程相關的接口。

RoutingStatementHandler

  • 這個接口是以上三個接口的路由,沒有實際操作,只是負責上面三個StatementHandler的創建及調用。

ParameterHandler

  • ParameterHandler在Mybatis中負責將sql中的佔位符替換為真正的參數,它是一個接口,有且只有一個實現類DefaultParameterHandler
  • setParameters是處理參數最核心的方法。這裡不再詳細的講,後面會講到。

TypeHandler

  • 這位大神應該都聽說過,也都自定義過吧,簡單的說就是在預編譯設置參數和取出結果的時候將Java類型和JDBC的類型進行相應的轉換。當然,Mybatis內置了很多默認的類型處理器,基本夠用,除非有特殊的定製,我們才會去自定義,比如需要將Java對象以JSON字符串的形式存入數據庫,此時就可以自定義一個類型處理器。
  • 很簡單的東西,此處就不再詳細的講了,後面會單獨出一篇如何自定義類型處理器的文章。

ResultSetHandler

  • 結果處理器,負責將JDBC返回的ResultSet結果集對象轉換成List類型的集合或者Cursor
  • 具體實現類就是DefaultResultSetHandler,其實現的步驟就是將Statement執行後的結果集,按照Mapper文件中配置的ResultType或ResultMap來封裝成對應的對象,最後將封裝的對象返回。
  • 源碼及其複雜,尤其是其中對嵌套查詢的解析,這裡只做個了解,後續會專門寫一篇文章介紹。

總結

  • 至此,Mybatis源碼第一篇就已經講完了,本篇文章對Mybatis中的重要組件做了初步的了解,為後面更深入的源碼閱讀做了鋪墊,如果覺得作者寫的不錯,在看分享一波,謝謝支持。