Mybatis 背後的強者Executor

Mybatis 背後的強者Executor

當咱們讀取完配置文件,將咱們的Mybatis配置成咱們想要的要的樣子以後,咱們就要使用他對數據庫進行一系列操做(增刪改查)。而SqlSession這個看似無所不能的操做達人,實際上是找了代練的。SqlSession將一切數據庫具體操做委託給背後的強者,今天要就讓咱們揭開Executor這個強者的面紗。java

Executor 類結構圖

能夠看出強者的家族都是一脈相承的,讓咱們逐一認識一下這一家人。sql

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

  int update(MappedStatement ms, Object parameter) throws SQLException;

  <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;

  List<BatchResult> flushStatements() throws SQLException;

  void commit(boolean required) throws SQLException;

  void rollback(boolean required) throws SQLException;

  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();

  void close(boolean forceRollback);

  boolean isClosed();

  void setExecutorWrapper(Executor executor);
}

Executor接口就好似一個武林祕籍,涵蓋了包括查詢,更新,事務操做,緩存構建的一系列描述,根據方法名咱們可略知一二。得此祕籍者兩人卻分別走了兩條不一樣的修行之路。數據庫

桃李天下的 BaseExecutor

BaseExecutor 做爲一個合格的老師,爲學生鋪好了練習的場地,處理了諸如數據庫鏈接,數據庫關閉,事務回滾,事務提交等一系列繁雜的事情。學生只需專心於如下4技能的修煉便可.關鍵 時刻,學生出拳就能了事。緩存

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;

接下來讓咱們分別看看,他的四大弟子都有什麼本事吧。網絡

SimpleExecutor : 應對簡單處理,每執行一次update或select,就開啓一個Statement對象,用完馬上關閉Statement對象。(能夠是Statement或PrepareStatement對象),俗稱打完就跑,善始善終。mybatis

BatchExecutor:善於批量處理執行update(沒有select,JDBC批處理不支持select),將全部sql都添加到批處理中(addBatch()),等待統一執行(executeBatch()),它緩存了多個Statement對象,每一個Statement對象都是addBatch()完畢後,等待逐一執行executeBatch()批處理的;app

ReuseExecutor :一旦出拳,不叫停不會停。執行update或select,以sql做爲key查找Statement對象,存在就使用,不存在就建立,用完後,不關閉Statement對象,而是放置於Map<String, Statement>內,供下一次使用。ide

ClosedExecutor :最不學無術的一個 ,是ResultLoaderMap的一個內部類,只返回一些標誌位。函數

聽完他們的本事,是否是很想知道他們具體怎麼成爲高徒的呢,秀肌肉到的是時候到了。ui

一、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.query(stmt, resultHandler);
    } 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());
    Cursor<E> cursor = handler.queryCursor(stmt);
    stmt.closeOnCompletion();
    return cursor;
  }

  @Override
public List<BatchResult> doFlushStatements(boolean isRollback) {
    return Collections.emptyList();
  }

SimpleExecutor 較爲簡單,不管是query 仍是 update statement隨建隨關。基本步驟就是獲取配置,建立相應的StatementHandler,實例化Statement ,使用StatementHandler執行數據庫操做。

二、ReuseExecutor

private final Map<String, Statement> statementMap = new HashMap<>();
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
  Configuration configuration = ms.getConfiguration();
  StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
  Statement stmt = prepareStatement(handler, ms.getStatementLog());
  return handler.update(stmt);
}

@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Configuration configuration = ms.getConfiguration();
  StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  Statement stmt = prepareStatement(handler, ms.getStatementLog());
  return handler.query(stmt, resultHandler);
}

@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.queryCursor(stmt);
}

ReuseExecutor 和 SimpleExecutor 的操做步驟基本類似,最大的區別在於前者維護者一個statementMap 用於記錄並無銷燬的statement,當相同的sql語句被執行時,會使用同一個已經建立好的statement,若是sql第一次執行,那麼就建立一個新的statement,並放入statementMap中。

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  BoundSql boundSql = handler.getBoundSql();
  String sql = boundSql.getSql();
  //有則直接使用
  if (hasStatementFor(sql)) {
     //sql 做爲 key,statement 做爲value
    stmt = getStatement(sql);
    applyTransactionTimeout(stmt);
  } else { // 沒有則建立新的statement
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    putStatement(sql, stmt);
  }
  handler.parameterize(stmt);
  return stmt;
}

固然ReuseExecutor 中的statement 不可能一直不關閉,這就須要咱們在恰當的時候,經過調用doFlushStatements方法手動對其進行關閉。

@Override
public List<BatchResult> doFlushStatements(boolean isRollback) {
  for (Statement stmt : statementMap.values()) {
    closeStatement(stmt);
  }
  statementMap.clear();
  return Collections.emptyList();
}

三、BatchExecutor

BatchExecutor 是幾個徒弟之中拳法最爲厲害的,他能夠進行批量操做,讓咱們看看他是如何以一敵百的。

讓咱們先來看看BatchExecutor 是處理每一條sql的。

(引用自 網絡資源 「Mybatis3.3.x技術內幕(四):五鼠鬧東京之執行器Executor設計本來」)

BatchExecutor 在執行批量更新時,根據sql語句建立Statement桶,相同的sql使用相同statement,並將全部的statement添加到statementList中。

在執行批量更新的過程當中,並不執行sql,只是拼接不一樣的statement。直至commit時,拿到statementList逐一進行sql執行。

//維護一個Statement列表 用於緩存拼接好的statement
private final List<Statement> statementList = new ArrayList<>();
//維護一個Result 列表,用於記錄處理結果
private final List<BatchResult> batchResultList = new ArrayList<>();
//當前保存的sql,也就是上一次執行的sql
private String currentSql;
////當前保存的MappedStatement,也就是上一次執行的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相同,同時MappedStatement也必須相同,取上次執行statement,並傳入這次參數
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      int last = statementList.size() - 1;
      stmt = statementList.get(last);
      applyTransactionTimeout(stmt);
      handler.parameterize(stmt);//fix Issues 322
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
    } else {
      //若是和上次執行sql不一樣,建立新的statement
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);    //fix Issues 322
      currentSql = sql;
      currentStatement = ms;
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
    //在handler內部執行addBatch()方法
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

須要注意:

sql.equals(currentSql)和statementList.get(last),充分說明了其有序邏輯:AABB,將生成2個Statement對象;AABBAA,將生成3個Statement對象,而不是2個。由於,只要sql有變化,將致使生成新的Statement對象。也就是說,如今執行的sql,只和上一次執行的sql進行對比,是否相同。若是相同使用上一個statement,若是不一樣再次建立新的statement。

那麼何時執行sql 呢?

答案是:在執行commit的時候。在執行commit、rollback等動做前,都將會執行flushStatements()方法,將Statement對象逐一關閉。

看看BatchExecutor .doFlushStatements方法。

@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
  try {
    List<BatchResult> results = new ArrayList<>();
    if (isRollback) {
      return Collections.emptyList();
    }
    //遍歷statementList,執行sql
    for (int i = 0, n = statementList.size(); i < n; i++) {
      Statement stmt = statementList.get(i);
      applyTransactionTimeout(stmt);
      BatchResult batchResult = batchResultList.get(i);
      try {
        // 執行sql
        batchResult.setUpdateCounts(stmt.executeBatch());
        MappedStatement ms = batchResult.getMappedStatement();
        List<Object> parameterObjects = batchResult.getParameterObjects();
        KeyGenerator keyGenerator = ms.getKeyGenerator();
        if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
          Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
          jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
        } else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { 
          for (Object parameter : parameterObjects) {
            keyGenerator.processAfter(this, ms, stmt, parameter);
          }
        }
        // 關閉statement
        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);
      }
      //將每次結果添加到resultList中。
      results.add(batchResult);
    }
    return results;
  } finally {
    for (Statement stmt : statementList) {
      closeStatement(stmt);
    }
    currentSql = null;
    statementList.clear();
    batchResultList.clear();
  }
}

偷奸耍滑的師叔CachingExecutor

第一次聽到CachingExecutor 的名字的時候,覺得它是全部Executor 中最厲害的一個。看名字就知道CachingExecutor在完成sql執行的任務以外,還作了緩存工做。做爲一個一樣獲得祕籍(直接實現Executor接口)的師叔,其實他就是一個偷奸耍滑的老頭子。

private final Executor delegate;

  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }

經過他的構造函數咱們能夠清楚的看出,CachingExecutor 其實仍是將全部的sql執行任務交給了不一樣Executor(SimpleExecutor、ReuseExecutor、BatchExecutor),而本身主要進行緩存處理。

關於mybatis的緩存機制,會有單獨的專題進行分析總結

@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) {
    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.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); 
      }
      return list;
    }
  }
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
}

@Override
public List<BatchResult> flushStatements() throws SQLException {
    return delegate.flushStatements();
}
相關文章
相關標籤/搜索