Mybati原始碼解析篇之六劍客!!!

愛撒謊的男孩發表於2020-09-10

目錄

  • 前言
  • 環境版本
  • 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中的重要元件做了初步的瞭解,為後面更深入的原始碼閱讀做了鋪墊,如果覺得作者寫的不錯,在看分享一波,謝謝支援。

相關文章