Mybatis核心處理層源碼閱讀分析(Executor系列組件)

Executor是MyBatis的核心接口之一,其中定義了數據庫操作的基本方法。在實際應用中經常涉及的SqISession接口的功能,都是基於Executor 接口實現的。

public interface Executor {
  ResultHandler NO_RESULT_HANDLER = null;

  // 執行update、insert、delete三種類型的SQL語句
  int update(MappedStatement ms, Object parameter) throws SQLException;

  // 執行select類型的SQL語句,返回值分爲結果對象列表或遊標對象
  <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;

  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

  // 批量執行SQL語句
  List<BatchResult> flushStatements() throws SQLException;

  // 提交事務
  void commit(boolean required) throws SQLException;

  // 回滾事務
  void rollback(boolean required) throws SQLException;

  // 創建緩存中用到的CacheKey對象
  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

  // 根據CacheKey對象查找緩存
  boolean isCached(MappedStatement ms, CacheKey key);

  // 清空一級緩存
  void clearLocalCache();

  // 延遲加載一級緩存中的數據
  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

  // 獲取事務
  Transaction getTransaction();

  // 關閉事務
  void close(boolean forceRollback);

  // 是否關閉
  boolean isClosed();
}

1 BaseExecutor

BaseExecutor是一個實現了Executor接口的抽象類,它實現了Executor接口的大部分方法。BaseExecutor中主要提供了緩存管理和事務管理的基本功能,繼承BaseExecutor的子類只要實現四個基本方法來完成數據庫的相關操作即可,這四個方法分別是:doUpdate()方法、doQuery()方法、doQueryCursor()方法、doFlushStatement()方法。

public abstract class BaseExecutor implements Executor {

  private static final Log log = LogFactory.getLog(BaseExecutor.class);

  // 事務對象,用於實現事務的提交、回滾和關閉
  protected Transaction transaction;
  // 其中封裝的Executor對象
  protected Executor wrapper;

  // 延遲加載隊列
  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
  // 一級緩存,用於緩存該Executor對象查詢結果集映射得到的結果對象
  protected PerpetualCache localCache;
  // 一級緩存,用於緩存輸出類型的參數
  protected PerpetualCache localOutputParameterCache;
  protected Configuration configuration;

  // 記錄嵌套查詢的層數
  protected int queryStack;
  // 是否關閉
  private boolean closed;
}

1.1 一級緩存簡介

常見的應用系統中,數據庫是比較珍貴的資源,很容易成爲整個系統的瓶頸。在設計和維護系統時,會進行多方面的權衡,並且利用多種優化手段,減少對數據庫的直接訪問。

使用緩存是一種比較有效的優化手段,使用緩存可以減少應用系統與數據庫的網絡交互、減少數據庫訪問次數、降低數據庫的負擔、降低重複創建和銷燬對象等一系列開銷,從而提高整個系統的性能。

MyBatis提供的緩存功能,分別爲一級緩存和二級緩存。BaseExecutor主要實現了一級緩存的相關內容。一級緩存是會話級緩存,在MyBatis中每創建一個SqlSession對象,就表示開啓一次數據庫會話。在一次會話中,應用程序可能會在短時間內(一個事務內),反覆執行完全相同的查詢語句,如果不對數據進行緩存,那麼每一次查詢都會執行一次數據庫查詢操作,而多次完全相同的、時間間隔較短的查詢語句得到的結果集極有可能完全相同,這會造成數據庫資源的浪費。

爲了避免上述問題,MyBatis會在Executor對象中建立一個簡單的一級緩存,將每次查詢的結果集緩存起來。在執行查詢操作時,會先查詢一級緩存,如果存在完全一樣的查詢情況,則直接從一級緩存中取出相應的結果對象並返回給用戶,減少數據庫訪問次數,從而減小了數據庫的壓力。

一級緩存的生命週期與SqlSession相同,其實也就與SqISession中封裝的Executor 對象的生命週期相同。當調用Executor對象的close()方法時(斷開連接),該Executor 對象對應的一級緩存就會被廢棄掉。一級緩存中對象的存活時間受很多方面的影響,例如,在調用Executor的update()方法時,也會先請空一級緩存。一級緩存默認是開啓的,一般情況下,不需要用戶進行特殊配置。

1.2 一級緩存的管理

BaseExecutor的query()方法會首先創建CacheKey對象,並根據該CacheKey對象查找一級緩存,如果緩存命中則返回緩存中記錄的結果對象,如果緩存未命中則查詢數據庫得到結果集,之後將結果集映射成結果對象並保存到一級緩存中,同時返回結果對象。

public abstract class BaseExecutor implements Executor {
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 獲取BoundSql對象
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 創建CacheKey對象,該對象由多個參數組裝而成
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // query方法的重載,進行後續處理
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 可以看到CacheKey對象由MappedStatement的id、RowBounds的offset和limit
    // sql語句(包含佔位符"?")、用戶傳遞的實參組成
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // 獲取用戶傳入的實參,並添加到CacheKey對象中
    for (ParameterMapping parameterMapping : parameterMappings) {
      // 過濾掉輸出類型的參數
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        // 將實參添加到CacheKey對象中
        cacheKey.update(value);
      }
    }
    // 如果configuration的environment不爲空,則將該environment的id
    // 添加到CacheKey對象中
    if (configuration.getEnvironment() != null) {
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // 檢查當前Executor是否已關閉
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      // 非嵌套查詢,且<select>節點配置的flushCache屬性爲true時,纔會清空一級緩存
      clearLocalCache();
    }
    List<E> list;
    try {
      // 增加查詢層數
      queryStack++;
      // 根據傳入的CacheKey對象 查詢一級緩存
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        // 針對存儲過程調用的處理,在一級緩存命中時,獲取緩存中保存的輸出類型參數
        // 並設置到用戶傳入的實參parameter對象中
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 緩存未命中,則從數據庫查詢結果集,其中會調用doQuery()方法完成數據庫查詢操作,
        // 該方法爲抽象方法,由BaseExecutor的子類實現
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      // 當前查詢完成,查詢層數減少
      queryStack--;
    }
    if (queryStack == 0) {
      // 延遲加載的相關內容
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
}

從上面的代碼中可以看到,BaseExecutor的query()方法會根據flushCache屬性和localCacheScope配置 決定是否清空一級緩存。

另外,BaseExecutor的update()方法在調用doUpdate()方法之前,也會清除一級緩存。update()方法負責執行insert、update、delete三類SQL 語句,它是調用doUpdate()方法實現的。

  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    // 判斷當前的Executor是否已經關閉
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 清除一級緩存,該方法會調用localCache和localOutputParameterCache
    // 的clear()方法清除緩存
    clearLocalCache();
    // 抽象方法,交由子類實現
    return doUpdate(ms, parameter);
  }

  @Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }

1.3 事務相關操作

在BatchExecutor實現中,可以緩存多條SQL語句,等待合適時機將緩存的多條SQL 語句一併發送到數據庫執行。Executor的flushStatements()方法主要是針對批處理多條SQL語句的,它會調用doFlushStatements()這個基本方法處理Executor中緩存的多條SQL語句。在BaseExecutor的commit()及rollback()等方法中都會首先調用flushStatements()方法,然後再執行相關事務操作。

  @Override
  public void commit(boolean required) throws SQLException {
    // 檢查當前連接是否已關閉
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    // 清除一級緩存
    clearLocalCache();
    // 不執行Executor中緩存的SQL語句
    flushStatements();
    // 根據參數required決定是否提交事務
    if (required) {
      transaction.commit();
    }
  }

  @Override
  public List<BatchResult> flushStatements() throws SQLException {
    return flushStatements(false);
  }

  public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 這是一個交由子類實現的抽象方法,參數isRollBack表示
    // 是否執行Executor中緩存的SQL語句,false表示執行,true表示不執行
    return doFlushStatements(isRollBack);
  }

  @Override
  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        // 清除一級緩存
        clearLocalCache();
        // 批量執行緩存的sql語句
        flushStatements(true);
      } finally {
        // 根據required決定是否回滾事務
        if (required) {
          transaction.rollback();
        }
      }
    }
  }

  @Override
  public void close(boolean forceRollback) {
    try {
      try {
        // 根據forceRollback參數決定 是否強制回滾該事務
        rollback(forceRollback);
      } finally {
        if (transaction != null) {
          transaction.close();
        }
      }
    } catch (SQLException e) {
      // Ignore.  There's nothing that can be done at this point.
      log.warn("Unexpected exception on closing transaction.  Cause: " + e);
    } finally {
      transaction = null;
      deferredLoads = null;
      localCache = null;
      localOutputParameterCache = null;
      closed = true;
    }
  }

2 SimpleExecutor

SimpleExecutor繼承了BaseExecutor抽象類,它是最簡單的Executor接口實現。Executor組件使用了模板方法模式,一級緩存等固定不變的操作都封裝到了BaseExecutor中,在SimpleExecutor中就不必再關心一級緩存等操作,只需要專注實現4 個基本方法的實現即可。

public class SimpleExecutor extends BaseExecutor {

  public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  @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對象
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 完成Statement的創建和初始化,該方法首先會調用StatementHandler的prepare()方法
      // 創建Statement對象,然後調用StatementHandler的parameterize()方法處理佔位符
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 調用StatementHandler的query()方法,執行sql語句,並通過ResultSetHandler
      // 完成結果集的映射
      return handler.<E>query(stmt, resultHandler);
    } finally {
      // 關閉Statement對象
      closeStatement(stmt);
    }
  }

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    // 創建Statement對象
    stmt = handler.prepare(connection, transaction.getTimeout());
    // 處理佔位符
    handler.parameterize(stmt);
    return stmt;
  }

  /**
   * 與前面doQuery()方法的實現非常類似
   */
  @Override
  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);
    }
  }

  @Override
  protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.<E>queryCursor(stmt);
  }

  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    // SimpleExecutor不提供sql語句批處理,所以直接返回空集合
    return Collections.emptyList();
  }

}

3 ReuseExecutor

在傳統的JDBC編程中,複用Statement對象是常用的一種優化手段,該優化手段可以減少SQL預編譯的開銷以及創建和銷燬Statement對象的開銷,從而提高性能(Reuse,複用)。

ReuseExecutor提供了Statement複用的功能,ReuseExecutor中通過statementMap 字段緩存使用過的Statement對象,key是SQL語句,value是SQL對應的Statement 對象。

ReuseExecutor.doQuery()、doQueryCursor()、doUpdate()方法的實現與SimpleExecutor中對應方法的實現一樣,區別在於其中調用的prepareStatement()方法,SimpleExecutor每次都會通過JDBC的Connection對象創建新的Statement對象,而ReuseExecutor則會先嚐試重用StaternentMap中緩存的Statement對象。

  // 本map用於緩存使用過的Statement,以提升本框架的性能
  // key SQL語句,value 該SQL語句對應的Statement
  private final Map<String, Statement> statementMap = new HashMap<String, Statement>();

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    BoundSql boundSql = handler.getBoundSql();
    // 獲取要執行的sql語句
    String sql = boundSql.getSql();
    // 如果之前執行過該sql,則從緩存中取出對應的Statement對象
    // 不再創建新的Statement,減少系統開銷
    if (hasStatementFor(sql)) {
      stmt = getStatement(sql);
      // 修改超時時間
      applyTransactionTimeout(stmt);
    } else {
      // 獲取數據庫連接
      Connection connection = getConnection(statementLog);
      // 從連接中獲取Statement對象
      stmt = handler.prepare(connection, transaction.getTimeout());
      // 將sql語句 和 其對應的Statement對象緩存起來
      putStatement(sql, stmt);
    }
    // 處理佔位符
    handler.parameterize(stmt);
    return stmt;
  }

  /**
   * 當事務提交或回滾、連接關閉時,都需要關閉這些緩存的Statement對象。前面分析的BaseExecutor的
   * commit()、rollback()和close()方法中都會調用doFlushStatements()方法,
   * 所以在該方法中關閉Statement對象的邏輯非常合適
   */
  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    // 遍歷Statement對象集合,並依次關閉
    for (Statement stmt : statementMap.values()) {
      closeStatement(stmt);
    }
    // 清除對Statement對象的緩存
    statementMap.clear();
    // 返回一個空集合
    return Collections.emptyList();
  }

拓展內容:SQL預編譯

1、數據庫預編譯起源

(1)數據庫SQL語句編譯特性
數據庫接收到sql語句之後,需要詞法和語義解析,以優化sql語句,制定執行計劃。這需要花費一些時間。但是很多情況,我們的同一條sql語句可能會反覆執行,或者每次執行的時候只有個別的值不同(比如:query的where子句值不同,update的set子句值不同,insert的values值不同)。

(2)減少編譯的方法
如果每次都需要經過上面的詞法語義解析、語句優化、制定執行計劃等,則效率就明顯不行了。爲了解決上面的問題,於是就有了預編譯,預編譯語句就是將這類語句中的值用佔位符替代,可以視爲將sql語句模板化或者說參數化。一次編譯、多次運行,省去了解析優化等過程。

(3)緩存預編譯
預編譯語句被DB的編譯器編譯後的執行代碼被緩存下來,那麼下次調用時只要是相同的預編譯語句就不需要重複編譯,只要將參數直接傳入編譯過的語句執行代碼中(相當於一個函數)就會得到執行。並不是所以預編譯語句都一定會被緩存,數據庫本身會用一種策略(內部機制)。

(4) 預編譯的實現方法
預編譯是通過PreparedStatement和佔位符來實現的。

2.預編譯作用
(1)減少編譯次數 提升性能
預編譯之後的 sql 多數情況下可以直接執行,DBMS(數據庫管理系統)不需要再次編譯。越複雜的sql,往往編譯的複雜度就越大。

(2)防止SQL注入
使用預編譯,後面注入的參數將不會再次觸發SQL編譯。也就是說,對於後面注入的參數,系統將不會認爲它會是一個SQL命令,而默認其是一個參數,參數中的or或and等(SQL注入常用技倆)就不是SQL語法保留字了。

3.mybatis是如何實現預編譯的

mybatis默認情況下,將對所有的 sql 進行預編譯。mybatis底層使用PreparedStatement,過程是,先將帶有佔位符(即”?”)的sql模板發送至數據庫服務器,由服務器對此無參數的sql進行編譯後,將編譯結果緩存,然後直接執行帶有真實參數的sql。核心是通過 “#{ }” 實現的。在預編譯之前,#{ } 被解析爲一個預編譯語句(PreparedStatement)的佔位符 ?。

// sqlMap 中如下的 sql 語句
select * from user where name = #{name};
// 解析成爲預編譯語句
select * from user where name = ?;

4 BatchExecutor

應用系統在執行一條SQL語句時,會將SQL語句以及相關參數通過網絡發送到數據庫系統。對於頻繁操作數據庫的應用系統來說,如果執行一條SQL語句就向數據庫發送一次請求,很多時間會浪費在網絡通信上。使用批量處理的優化方式可以在客戶端緩存多條SQL語句,並在合適的時機將多條SQL語句打包發送給數據庫執行,從而減少網絡方面的開銷,提升系統的性能。

需要注意的是,在批量執行多條SQL 語句時,每次向數據庫發送的SQL語句條數
是有上限的,若超出上限,數據庫會拒絕執行這些SQL語句井拋出異常,所以批量發送SQL語句的時機很重要。

mybatis的BatchExecutor實現了批處理多條SQL 語句的功能。

public class BatchExecutor extends BaseExecutor {

  public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;
  // 緩存多個Statement對象,其中每個Statement對象中都可以緩存多條
  // 結構相同 但參數不同的sql語句
  private final List<Statement> statementList = new ArrayList<Statement>();
  // 記錄批處理的結果,BatchResult中通過updateCounts字段
  // 記錄每個Statement對象 執行批處理的結果
  private final List<BatchResult> batchResultList = new ArrayList<BatchResult>();
  // 記錄當前執行的sql語句
  private String currentSql;
  // 記錄當前執行的MappedStatement對象
  private MappedStatement currentStatement;

  /**
   * JDBC中的批處理只支持insert、update、delete等類型的SQL語句,不支持select類型的
   * SQL語句,所以doUpdate()方法是BatchExecutor中最重要的一個方法。
   * 本方法在添加一條SQL語句時,首先會將currentSql字段記錄的SQL語句以及currentStatement字段
   * 記錄的MappedStatement對象與當前添加的SQL以及MappedStatement對象進行比較,
   * 如果相同則添加到同一個Statement對象中等待執行,如果不同則創建新的Statement對象
   * 井將其緩存到statementList集合中等待執行
   */
  @Override
  public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
    // 獲取configuration配置對象
    final Configuration configuration = ms.getConfiguration();
    // 實例化一個StatementHandler,並返回
    final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
    // 獲取需要執行的sql語句
    final BoundSql boundSql = handler.getBoundSql();
    final String sql = boundSql.getSql();
    final Statement stmt;
    // 判斷要執行的sql語句結構 及 MappedStatement對象 是否與上次的相同
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      // 相同則添加到同一個Statement對象中等待執行
      // 首先獲取statementList集合中最後一個Statement對象
      int last = statementList.size() - 1;
      stmt = statementList.get(last);
      // 重新設置事務超時時間
      applyTransactionTimeout(stmt);
      // 綁定實參,處理佔位符?
      handler.parameterize(stmt);
      // 查找對應的BatchResult對象,並記錄用戶傳入的實參
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
    } else {
      // 不同則創建新的Statement對象井將其緩存到statementList集合中等待執行
      Connection connection = getConnection(ms.getStatementLog());
      // 創建新的Statement對象
      stmt = handler.prepare(connection, transaction.getTimeout());
      // 綁定實參,處理佔位符?
      handler.parameterize(stmt);
      // 記錄本次的sql語句 及 Statement對象
      currentSql = sql;
      currentStatement = ms;
      // 將新創建的Statement對象添加到statementList集合
      statementList.add(stmt);
      // 添加新的BatchResult對象
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
    // 底層通過調用java.sql.Statement的addBatch()方法添加sql語句
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

  /**
   * 上面的doUpdate()方法負責添加待執行的sql語句,
   * 而doFlushStatements()方法則將上面添加的sql語句進行批量處理
   */
  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    try {
      // 用於存儲批處理結果的集合
      List<BatchResult> results = new ArrayList<BatchResult>();
      // 如果要回滾 則返回一個空集合
      if (isRollback) {
        return Collections.emptyList();
      }
      // 批處理statementList集合中的所以Statement對象
      for (int i = 0, n = statementList.size(); i < n; i++) {
        // 獲取Statement對象 和其對應的 BatchResult對象
        Statement stmt = statementList.get(i);
        applyTransactionTimeout(stmt);
        BatchResult batchResult = batchResultList.get(i);
        try {
          // 調用Statement對象的executeBatch()方法,批量執行其中記錄的sql語句
          // 將執行返回的int[]數組set進batchResult的updateCounts字段,
          // 其中的每一個int值都代表了對應的sql語句 影響的記錄條數
          batchResult.setUpdateCounts(stmt.executeBatch());
          MappedStatement ms = batchResult.getMappedStatement();
          List<Object> parameterObjects = batchResult.getParameterObjects();
          // 獲取配置的KeyGenerator對象
          KeyGenerator keyGenerator = ms.getKeyGenerator();
          if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
            Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
            // 獲取數據庫生成的主鍵 並設置到parameterObjects中
            jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
          } else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) {
            // 對於其它類型的KeyGenerator,則調用其processAfter進行處理
            for (Object parameter : parameterObjects) {
              keyGenerator.processAfter(this, ms, stmt, parameter);
            }
          }
          closeStatement(stmt);
        } catch (BatchUpdateException e) {
          StringBuilder message = new StringBuilder();
          message.append(batchResult.getMappedStatement().getId())
                  .append(" (batch index #")
                  .append(i + 1)
                  .append(")")
                  .append(" failed.");
          if (i > 0) {
            message.append(" ")
                    .append(i)
                    .append(" prior sub executor(s) completed successfully, but will be rolled back.");
          }
          throw new BatchExecutorException(message.toString(), e, results, batchResult);
        }
        // 添加處理完的BatchResult對象到要返回的List<BatchResult>集合中
        results.add(batchResult);
      }
      return results;
    } finally {
      // 關閉所有的Statement對象
      for (Statement stmt : statementList) {
        closeStatement(stmt);
      }
      // 清空currentSql、statementList、batchResultList對象
      currentSql = null;
      statementList.clear();
      batchResultList.clear();
    }
  }
}

通過了解JDBC的批處理功能 我們可以知道,Statement中可以添加不同語句結構的SQL,但是每添加一個新結構的SQL語句都會觸發一次編譯操作。而PreparedStatement中只能添加同一語句結構的SQL語句,只會觸發一次編譯操作,但是可以通過綁定多組不同的實參實現批處理。通過上面對doUpdate()方法的分析可知,BatchExecutor會將連續添加的、相同語句結構的SQL語句添加到同一個Statement/PreparedStatement對象中,這樣可以有效地減少編譯操作的次數。

BatchExecutor中doQuery()和doQueryCursor()方法的實現與前面介紹的SimpleExecutor類似,主要區別就是BatchExecutor中的這兩個方法在最開始都會先調用flushStatements()方法,執行緩存的SQL語句,以保證 從數據庫中查詢到的數據是最新的。

CachingExecutor中爲Executor對象增加了二級緩存相關功能,而mybatis的二級緩存在實際使用中往往利大於弊,被redis等產品所替代,所以這裏不做分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章