mybatis源碼閱讀(五) ---執行器Executor

1. Executor接口設計與類結構圖

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

  // 執行update,delete,insert三種類型的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);

  boolean isCached(MappedStatement ms, CacheKey key);

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

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

  // 獲取事務對象
  Transaction getTransaction();

  // 關閉Executor對象
  void close(boolean forceRollback);

  // 檢測Executor對象是否關閉
  boolean isClosed();

  void setExecutorWrapper(Executor executor);

}

簡單執行器SimpleExecutor:每執行一次update或select,就開啓一個Statement對象,用完馬上關閉Statement對象。(能夠是Statement或PrepareStatement對象)sql

重用執行器ReuseExecutor:執行update或select,以sql做爲key查找Statement對象,存在就使用,不存在就建立,用完後,不關閉Statement對象,而是放置於Map<String, Statement>內,供下一次使用。(能夠是Statement或PrepareStatement對象)數據庫

批量執行器BatchExecutor:執行update(沒有select,JDBC批處理不支持select),將全部sql都添加到批處理中(addBatch()),等待統一執行(executeBatch()),它緩存了多個Statement對象,每一個Statement對象都是addBatch()完畢後,等待逐一執行executeBatch()批處理的;BatchExecutor至關於維護了多個桶,每一個桶裏都裝了不少屬於本身的SQL,就像蘋果藍裏裝了不少蘋果,番茄藍裏裝了不少番茄,最後,再統一倒進倉庫。(能夠是Statement或PrepareStatement對象)設計模式

緩存執行器CachingExecutor:裝飾設計模式典範,先從緩存中獲取查詢結果,存在就返回,不存在,再委託給Executor delegate去數據庫取,delegate能夠是上面任一的SimpleExecutor、ReuseExecutor、BatchExecutor。緩存

無用執行器ClosedExecutor:毫無用處,讀者可自行查看其源碼,僅做爲一種標識,和Serializable標記接口做用至關。app

做用範圍:以上這五個執行器的做用範圍,都嚴格限制在SqlSession生命週期範圍內。ide

2. 基類BaseExecutor源碼解析

它是一個實現了Executor接口的抽象類,實現了接口中的大部分方法,其中就是使用了模板模式,它主要提供了緩存和事物管理的基本功能,不一樣的實現類,只要實現4個基本方法來完成數據庫的相關操做,這4個抽象方法:doUpdate()、doQuery()、doFlushStatement()、doQueryCursor。ui

源碼片斷this

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

// 延遲加載隊列
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
// 下面兩個屬性是一級緩存用到的對象
protected PerpetualCache localCache;
protected PerpetualCache localOutputParameterCache;

protected Configuration configuration;

// 嵌套查詢層級
protected int queryStack;
private boolean closed;
protected abstract int doUpdate(MappedStatement ms, Object parameter)
    throws SQLException;

protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
    throws SQLException;

protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
    throws SQLException;

protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
    throws SQLException;

2.1 SimpleExecutor

@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
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);//關閉statement對象
  }
}

源碼很簡單,從configuration對象中去材料,交給handler去處理,處理完後,statement對象立刻關閉。spa

2.2 ReuseExecutor

執行器提供了Statement的重用功能,代碼片斷以下:設計

// 緩存使用過的Statement對象,key是SQL語句,value是SQL對應的Statement對象
private final Map<String, Statement> statementMap = new HashMap<String, Statement>();
/**
 * 準備獲取Statement對象
 * @param handler
 * @param statementLog
 * @return
 * @throws SQLException
 */
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  BoundSql boundSql = handler.getBoundSql();
  String sql = boundSql.getSql();
  if (hasStatementFor(sql)) {// 檢測是否緩存了相同模式的SQL語句所對應的Statement對象
    stmt = getStatement(sql);// 從緩存中獲取statement對象
    applyTransactionTimeout(stmt);// 修改超時時間
  } else {
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    putStatement(sql, stmt);
  }
  handler.parameterize(stmt);
  return stmt;
}

那statement對象是何時關閉的呢?當事物提交回滾或者關閉時都須要關閉這些緩存的Statement對象,在BaseExecutor.commit(),rollback(),close()方法中都會掉用doFlushStatement()方法,因此在改方法中實現關閉Statement對象是很是合適。具體以下:

@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
  // 遍歷map集合並關閉其中的Statement對象
  for (Statement stmt : statementMap.values()) {
    closeStatement(stmt);
  }
  // 清空緩存
  statementMap.clear();
  // 返回空集合
  return Collections.emptyList();
}

2.3 BatchExecutor

BatchExecutor實現了批處理多條SQL語句的功能,須要注意的是在批處理執行SQL語句時,每次向數據庫發送的SQL語句條數是有上限,超過上限會拋出異常,因此批量發送SQL語句的時機是很重要的。

其中的核心字段含義以下:

//緩存多個Statement對象,每一個Statement對象中都緩存了多條SQL語句
 private final List<Statement> statementList = new ArrayList<Statement>();
 //記錄批處理的結果 BatchResult中經過updateCounts字段(int[])記錄每一個Statement執行批處理的結果
 private final List<BatchResult> batchResultList = new ArrayList<BatchResult>();
 // 記錄當前執行的SQL語句
 private String currentSql;
 // 記錄當前的MappedStatement對象
 private MappedStatement currentStatement;

方法實現以下:

@Override
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
  final Configuration configuration = ms.getConfiguration();
  final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
  final BoundSql boundSql = handler.getBoundSql();
  // 本次執行的sql
  final String sql = boundSql.getSql();
  final Statement stmt;
  // 若是當前執行的SQL與上一次執行的SQL相同且對應的MappedStatement對象相同
  if (sql.equals(currentSql) && ms.equals(currentStatement)) {
    int last = statementList.size() - 1;
    // 已經存在Statement,取出最後一個Statement,有序
    stmt = statementList.get(last);
    applyTransactionTimeout(stmt);
   handler.parameterize(stmt);//fix Issues 322
    BatchResult batchResult = batchResultList.get(last);
    batchResult.addParameterObject(parameterObject);
  } else {
    // 尚不存在,新建Statement
    Connection connection = getConnection(ms.getStatementLog());
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);    //fix Issues 322
    currentSql = sql;
    currentStatement = ms;
    // 放到statementList緩存
    statementList.add(stmt);
    batchResultList.add(new BatchResult(ms, sql, parameterObject));
  }
// handler.parameterize(stmt);
    // 將sql以addBatch()的方式,添加到Statement中(該步驟由StatementHandler內部完成)
  handler.batch(stmt);
  return BATCH_UPDATE_RETURN_VALUE;
}

須要注意的是sql.equals(currentSql)和statementList.get(last),充分說明了其有序邏輯:AABB,將生成2個Statement對象;AABBAA,將生成3個Statement對象,而不是2個。由於,只要sql有變化,將致使生成新的Statement對象。

緩存了這麼多Statement批處理對象,什麼時候執行它們?在doFlushStatements()方法中完成執行stmt.executeBatch(),隨即關閉這些Statement對象。

2.4 CachingExecutor

CachingExecutor是一個Executor接口的裝飾器,它爲Executor對象增長了二級緩存的相關功能。

//委託的執行器對象,能夠是SimpleExecutor、ReuseExecutor、BatchExecutor任一一個
private final Executor delegate;
//管理使用的二級緩存對像
private final TransactionalCacheManager tcm = new TransactionalCacheManager();

query方法執行的查詢操做步驟:

(1)獲取BoundSql對象,建立查詢語句對應的CacheKey對象,

(2)檢測是否開啓了二級緩存,若是沒有,則指教調用delegate對象的query()方法查詢,若是開啓了,則繼續後面的步驟

(3)檢測查詢是否包含輸出類型的參數,若是是,則報錯

(4)調用TransactionalCacheManager.getObject()方法查詢二級緩存,若是二級緩存中查找到相應的結果,則直接返回結果。

(5)若是二級緩存沒有相應的結果對象,在調用delegate對象的query()方法查詢。最後將獲得的結果放入

TransactionalCache.entriesToAddOnCommit集合中保存。

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  // (1)
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}


@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) {// (2)
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      // (3)
      ensureNoOutParams(ms, boundSql);
      // (4)
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {// (5)

        list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  // 沒有啓動二級緩存,只調用底層Executor查詢
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
相關文章
相關標籤/搜索