mybatis-executor解析

概述

在這裏插入圖片描述

執行器包主要包含了 Executor、ParameterHandler、ResultSetHandler、StatementHandler。

這些都是sql執行中非常重要的一環,本篇從Executor開始。

Executor:執行器,主要職責是在sql執行過程中添加緩存和事務的功能。與jdbc相關的操作會繼續委託給StatementHandler。

Executor

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

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

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

  // 查詢 resultHandler
  <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提供的方法分類

  • sql操作
  • 緩存
  • 事務

可得出Executor的基本職責。


Executor的實現類:

在這裏插入圖片描述

  • BaseExecutor:基礎執行器,實現Executor方法,添加通用緩存、事務處理,模板方法模式。
  • CachingExecutor:緩存執行器,BaseExecutor內部包含的是一級緩存,CachingExecutor是二級緩存。
  • ReuseExecutor:可重用執行器,會將Statement緩存。
  • SimpleExecutor:基礎執行器,值也包含默認數據庫執行。
  • BatchExecutor:批處理執行器,可進行批量執行sql(僅限Update)。

BaseExecutor

public abstract class BaseExecutor implements Executor {

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

  /**
   * 事務
   */
  protected Transaction transaction;
  /**
   * 包裝器
   */
  protected Executor wrapper;

  /**
   * 延遲加載
   */
  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
  /**
   * 一級緩存
   */
  protected PerpetualCache localCache;
  /**
   * 輸出參數緩存
   */
  protected PerpetualCache localOutputParameterCache;
  /**
   * 配置
   */
  protected Configuration configuration;

  /**
   * 查詢的深度
   */
  protected int queryStack;
  /**
   * 是否關閉
   */
  private boolean closed;
}

update

@Override
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);
}

query

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  /*
   * 獲取真正的sql,將入參傳入,佔位符替換
   */
  BoundSql boundSql = ms.getBoundSql(parameter);

  /*
   * 創建一級緩存key
   */
  CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);


  return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  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()) {
    clearLocalCache();
  }
  List<E> list;
  try {
    queryStack++;

    /*
     * 先嚐試從緩存拿
     */
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
      // 存儲過程時,緩存outPut類型參數
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      /*
       * 實時查庫
       */
      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  } finally {
    queryStack--;
  }
  
  // 延遲加載處理
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    // issue #601
    deferredLoads.clear();
    // 緩存是Statement級別,則清楚
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482
      clearLocalCache();
    }
  }
  // 返回實體
  return list;
}

commit

@Override
public void commit(boolean required) throws SQLException {
  if (closed) {
    throw new ExecutorException("Cannot commit, transaction is already closed");
  }
  // 清除緩存
  clearLocalCache();
  // 刷新statement
  flushStatements();
  if (required) {
    transaction.commit();
  }
}

rollback

@Override
public void rollback(boolean required) throws SQLException {
  if (!closed) {
    try {
      clearLocalCache();
      flushStatements(true);
    } finally {
      if (required) {
        transaction.rollback();
      }
    }
  }
}

SimpleExecutor

簡單執行器,最常用的執行器。

@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    // 創建 statementHandler
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    // 創建statement實例
    stmt = prepareStatement(handler, ms.getStatementLog());
    // 執行
    return handler.update(stmt);
  } finally {
    closeStatement(stmt);
  }
}

這裏步驟很簡單,創建StatementHandler和Statement實例,執行sql交給StatementHandler。

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

    /*
     * 創建一個statement處理器 用於操作jdbc statement
     */
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

    /*
     * 根據statementType創建對應的statement實例
     * simple
     * prepare
     * callable
     */
    stmt = prepareStatement(handler, ms.getStatementLog());

    /*
     * 執行sql並轉換返回值
     * 這裏handler類型是RoutingStatementHandler
     * 根據org.apache.ibatis.mapping.MappedStatement.statementType
     * 路由到真正的StatementHandler
     */
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

query和update邏輯相同。

創建Statement實例

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  // 獲取數據源連接,這裏會對Connection創建代理類,添加了日誌打印的功能
  Connection connection = getConnection(statementLog);
  //獲取一個Statement實例
  stmt = handler.prepare(connection, transaction.getTimeout());

  //往statement裏設置參數
  handler.parameterize(stmt);
  return stmt;
}

ReuseExecutor

可複用Statement實例執行器。SimpleExecutor中Statement是一次性的,用完馬上關閉。

private final Map<String, Statement> statementMap = new HashMap<>();

存儲Statement緩存,key是 動態sql。其他與SimpleExecutor完全相同。

BatchExecutor

批量執行器,可批量執行sql。僅限update(jdbc僅支持批量update)

實在用的少,略過。

CachingExecutor

緩存執行器,是mybatis二級緩存機制的實現類。

public class CachingExecutor implements Executor {

  private final Executor delegate;
  /**
   * 緩存跨SqlSession,所以需要事務控制
   */
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();
}

這裏也是典型的裝飾模式,執行器的具體實現都委託給Delegate執行。自己在之上添加緩存功能。

這裏主要看下query方法,看下緩存的處理

@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) {
        // 二級緩存爲null,委託實例查詢
        list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        // 設置值到二級緩存
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  
  // 沒有緩存容器,說明沒有二級緩存
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

從代碼中可以看出,mybatis的緩存機制順序

  1. 二級緩存
  2. 一級緩存
  3. 數據庫

二級緩存在如今分佈式環境下,也使用的非常少。

總結

executor是介於SqlSession和StatementHandler之間的,用於專門處理緩存和事務的。

不對外直接開放,也不與jdbc直接交互。在我們應用開發中,也可以細化dao層,添加緩存事務的這一層。

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