MyBatis 核心配置綜述之Executor

上一篇咱們對SqlSessionSqlSessionFactory的建立過程有了一個詳細的瞭解,但上述的建立過程只是爲SQL執行和SQL映射作了基礎的鋪墊而已,就和咱們Spring源碼爲Bean容器的加載進行許多初始化的工做相同,那麼作好前期的準備工做接下來該作什麼了?該作數據庫鏈接驅動管理和SQL解析工做了!那麼本篇本章就來討論一下數據庫驅動鏈接管理和SQL解析的管理組件之 Executor執行器。java

MyBatis四大組件之 Executor執行器

每個SqlSession都會擁有一個Executor對象,這個對象負責增刪改查的具體操做,咱們能夠簡單的將它理解爲JDBC中Statement的封裝版。程序員

Executor的繼承結構

如圖所示,位於繼承體系最頂層的是Executor執行器,它有兩個實現類,分別是BaseExecutorCachingExecutorsql

BaseExecutor 是一個抽象類,這種經過抽象的實現接口的方式是適配器設計模式之接口適配的體現,是Executor的默認實現,實現了大部分Executor接口定義的功能,下降了接口實現的難度。BaseExecutor的子類有三個,分別是SimpleExecutorReuseExecutorBatchExecutor數據庫

SimpleExecutor: 簡單執行器,是MyBatis中默認使用的執行器,每執行一次update或select,就開啓一個Statement對象,用完就直接關閉Statement對象(能夠是Statement或者是PreparedStatment對象)設計模式

ReuseExecutor: 可重用執行器,這裏的重用指的是重複使用Statement,它會在內部使用一個Map把建立的Statement都緩存起來,每次執行SQL命令的時候,都會去判斷是否存在基於該SQL的Statement對象,若是存在Statement對象而且對應的connection尚未關閉的狀況下就繼續使用以前的Statement對象,並將其緩存起來。由於每個SqlSession都有一個新的Executor對象,因此咱們緩存在ReuseExecutor上的Statement做用域是同一個SqlSession。緩存

BatchExecutor: 批處理執行器,用於將多個SQL一次性輸出到數據庫session

CachingExecutor: 緩存執行器,先從緩存中查詢結果,若是存在,就返回;若是不存在,再委託給Executor delegate 去數據庫中取,delegate能夠是上面任何一個執行器mybatis

Executor建立過程以及源碼分析

上面咱們分析完SqlSessionFactory的建立過程的準備工做後,咱們下面就開始分析會話的建立以及Executor的執行過程。app

在建立完SqlSessionFactory以後,調用其openSession方法:

SqlSession sqlSession = factory.openSession();

SqlSessionFactory的默認實現是DefaultSqlSessionFactory,因此咱們須要關心的就是DefaultSqlSessionFactory中的openSession()方法

openSession調用的是openSessionFromDataSource方法,傳遞執行器的類型,方法傳播級別,是否自動提交,而後在openSessionFromDataSource方法中會建立一個執行器

public SqlSession openSession() {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 獲得configuration 中的environment
      final Environment environment = configuration.getEnvironment();
      // 獲得configuration 中的事務工廠
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 獲取執行器
      final Executor executor = configuration.newExecutor(tx, execType);
      // 返回默認的SqlSession
      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();
    }
  }

調用newExecutor方法,根據傳入的ExecutorType類型來判斷是哪一種執行器,而後執行相應的邏輯

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // defaultExecutorType默認是簡單執行器, 若是不傳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);
    }
    // 若是容許緩存,則使用緩存執行器
    // 默認是true,若是不容許緩存的話,須要手動設置
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    // 插件開發。
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

ExecutorType的選擇:

ExecutorType來決定Configuration對象建立何種類型的執行器,它的賦值能夠經過兩個地方進行賦值:

  • 能夠經過 標籤來設置當前工程中全部的SqlSession對象使用默認的Executor
<settings>
<!--取值範圍 SIMPLE, REUSE, BATCH -->
  <setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
  • 另一種直接經過Java對方法賦值的方式
session = factory.openSession(ExecutorType.BATCH);

ExecutorType是一個枚舉,它只有三個值SIMPLE, REUSE, BATCH

建立完成Executor以後,會把Executor執行器放入一個DefaultSqlSession對象中來對四個屬性進行賦值,他們分別是 configurationexecutordirtyautoCommit

Executor接口的主要方法

Executor接口的方法仍是比較多的,這裏咱們就幾個主要的方法和調用流程來作一個簡單的描述

大體流程

Executor中的大部分方法的調用鏈實際上是差很少的,下面都是深刻源碼分析執行過程,若是你沒有時間或者暫時不想深刻研究的話,給你下面的執行流程圖做爲參考。

query()方法

query方法有兩種形式,一種是直接查詢;一種是從緩存中查詢,下面來看一下源碼

<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

當有一個查詢請求訪問的時候,首先會通過Executor的實現類CachingExecutor,先從緩存中查詢SQL是不是第一次執行,若是是第一次執行的話,那麼就直接執行SQL語句,並建立緩存,若是第二次訪問相同的SQL語句的話,那麼就會直接從緩存中提取

CachingExecutor.j

// 第一次查詢,並建立緩存
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

MapperStatement維護了一條<select|update|delete|insert>節點的封裝,包括資源(resource),配置(configuration),SqlSource(sql源文件)等。使用Configuration的getMappedStatement方法來獲取MappedStatement對象

BoundSql這個類包括SQL的基本信息,基本的SQL語句,參數映射,參數類型等

上述的query方法會調用到CachingExecutor類中的query查詢緩存的方法

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) {
    // 若是須要的話刷新緩存
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, boundSql);
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); 
      }
      return list;
    }
  }
  // 委託模式,交給SimpleExecutor等實現類去實現方法。
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

由delegate執行query方法,delegate便是BaseExecutor,而後由具體的執行器去真正執行query方法

注意:源碼中通常以do** 開頭的方法都是真正加載執行的方法

// 通過一系列的調用,會調用到下面的方法(與主流程無關,故省略)
// 以SimpleExecutor簡單執行器爲例
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,解析SQL語句
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
    // 由handler來對SQL語句執行解析工做
    return handler.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

由上面的源碼能夠看出,Executor執行器所起的做用至關因而管理StatementHandler 的整個生命週期的工做,包括建立、初始化、解析、關閉。

ReuseExecutor完成的doQuery 工做:幾乎和SimpleExecutor完成的工做同樣,其內部不過是使用一個Map來存儲每次執行的查詢語句,爲後面的SQL重用做準備。

BatchExecutor完成的doQuery 工做:和SimpleExecutor完成的工做同樣。

update() 方法

在分析完上面的查詢方法後,咱們再來聊一下update()方法,update()方法不只僅指的是update()方法,它是一條update鏈,什麼意思呢?就是*insert、update、delete在語義上其實都是更新的意思,而查詢在語義上僅僅只是表示的查詢,那麼咱們來偷窺一下update方法的執行流程,與select的主要執行流程很類似,因此一次性貼出。

// 首先在頂級接口中定義update 方法,交由子類或者抽象子類去實現

// 也是首先去緩存中查詢是否具備已經執行過的相同的update語句
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
  flushCacheIfRequired(ms);
  return delegate.update(ms, parameterObject);
}

// 而後再交由BaseExecutor 執行update 方法
public int update(MappedStatement ms, Object parameter) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  clearLocalCache();
  return doUpdate(ms, parameter);
}

// 每每do* 開頭的都是真正執行解析的方法,因此doUpdate 應該就是真正要執行update鏈的解析方法了
// 交給具體的執行器去執行
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.update(stmt);
  } finally {
    closeStatement(stmt);
  }
}

ReuseExecutor完成的doUpdate 工做:幾乎和SimpleExecutor完成的工做同樣,其內部不過是使用一個Map來存儲每次執行的更新語句,爲後面的SQL重用做準備。

BatchExecutor完成的doUpdate 工做:和SimpleExecutor完成的工做類似,只是其內部有一個List列表來一次行的存儲多個Statement,用於將多個sql語句一次性輸送到數據庫執行.

queryCursor()方法

咱們查閱其源碼的時候,在執行器的執行過程當中並無發現其與query方法有任何不一樣之處,可是在doQueryCursor 方法中咱們能夠看到它返回了一個cursor對象,網上搜索cursor的相關資料並查閱其基本結構,得出來的結論是:用於逐條讀取SQL語句,應對數據量

// 查詢能夠返回Cursor<T>類型的數據,相似於JDBC裏的ResultSet類,
// 當查詢百萬級的數據的時候,使用遊標能夠節省內存的消耗,不須要一次性取出全部數據,能夠進行逐條處理或逐條取出部分批量處理。
public interface Cursor<T> extends Closeable, Iterable<T> {

    boolean isOpen();

    boolean isConsumed();
  
    int getCurrentIndex();
}

flushStatements() 方法

flushStatement()的主要執行流程和query,update 的執行流程差很少,咱們這裏就再也不詳細貼代碼了,簡單說一下flushStatement()的主要做用,flushStatement()主要用來釋放statement,或者用於ReuseExecutor和BatchExecutor來刷新緩存

createCacheKey() 方法

createCacheKey()方法主要由BaseExecutor來執行並建立緩存,MyBatis中的緩存分爲一級緩存和二級緩存,關於緩存的討論咱們將在Mybatis系列的緩存章節

Executor 中的其餘方法

Executor 中還有其餘方法,提交commit,回滾rollback,判斷是否時候緩存isCached,關閉close,獲取事務getTransaction一級清除本地緩存clearLocalCache等

Executor 的現實抽象

在上面的分析過程當中咱們瞭解到,Executor執行器是MyBatis中很重要的一個組件,Executor至關因而外包的boss,它定義了甲方(SQL)須要乾的活(Executor的主要方法),這個外包公司是個小公司,沒多少人,每一個人都須要幹不少工做,boss接到開發任務的話,通常都找項目經理(CachingExecutor),項目經理幾乎不懂技術,它主要和技術leader(BaseExecutor)打交道,技術leader主要負責框架的搭建,具體的工做都會交給下面的程序員來作,程序員的技術也有優劣,高級程序員(BatchExecutor)、中級程序員(ReuseExecutor)、初級程序員(SimpleExecutor),它們乾的活也不同。通常有新的項目需求傳達到項目經理這裏,項目經理先判斷本身手裏有沒有現成的類庫或者項目直接套用(Cache),有的話就直接讓技術leader拿來直接套用就好,沒有的話就須要搭建框架,再把框架存入本地類庫中,再進行解析。

(本文完)

下文預告: MyBatis 核心配置綜述之StatementHandler

文章參考:

https://www.jianshu.com/p/19686af69b0d

http://www.mybatis.org/mybatis-3/getting-started.html

https://www.cnblogs.com/virgosnail/p/10067964.html

相關文章
相關標籤/搜索