Mybatis 源碼(四)Mybatis Excuter框架

咱們在上一章介紹到,Mybatis會將全部數據庫操做轉換成iBatis編程模型,經過門面類SqlSession來操做數據庫,可是咱們深刻SqlSession源碼咱們會發現,SqlSession啥都沒幹,它將數據庫操做都委託給你了Excuter,如圖:java

Excuter框架類圖

BaseExecutor

在BaseExecutor定義了Executor的基本實現,如查詢一級緩存,事務處理等不變的部分,操做數據庫等變化部分由子類實現,使用了模板設計模式,下面咱們來看下查詢方法的源碼:git

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

/**
 * 全部的查詢操做最後都是由該方法來處理的
 */
@SuppressWarnings("unchecked")
@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) {
      // 處理存儲過程的OUT參數
      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();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482
      clearLocalCache();
    }
  }
  return list;
}

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  // 添加緩存佔位符
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    // 刪除緩存佔位符
    localCache.removeObject(key);
  }
  // 將查詢結果添加到本地緩存
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    // 若是是存儲過程則,緩存參數
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}

queryFromDatabase() 方法中,咱們能夠看到doQuery使用的是模板方法,具體邏輯是由子類來實現的,這樣作的好處是,子類只關心程序變化的部分,其餘不變的部分由父類實現。提升了代碼的複用性和代碼的擴展性。github

SimpleExecutor

普通的執行器,Mybatis的默認使用該執行器,每次新建Statement。咱們仍是來看下查詢方法的源碼:sql

@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    // 獲取Mybatis配置類
    Configuration configuration = ms.getConfiguration();
    // 根據配置類獲取StatementHandler
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    // 建立Statement
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  // 獲取Connection鏈接
  Connection connection = getConnection(statementLog);
  // 根據Connection獲取Statement
  stmt = handler.prepare(connection, transaction.getTimeout());
  // 設置參數
  handler.parameterize(stmt);
  return stmt;
}

經過 stmt = handler.prepare(connection, transaction.getTimeout());方法咱們能夠看出每次是新建Statement數據庫

ReuseExecutor

能夠重用的執行器,複用的是Statement,內部以sql語句爲key使用一個Map將Statement對象緩存起來,只要鏈接不斷開,那麼Statement就能夠重用。編程

由於每個新的SqlSession都有一個新的Executor對象,因此咱們緩存在ReuseExecutor上的Statement的做用域是同一個SqlSession,因此其實這個緩存用處其實並不大。咱們直接看下獲取Statement源碼,其餘部分和SimpleExecutor查詢方法同樣。設計模式

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  BoundSql boundSql = handler.getBoundSql();
  String sql = boundSql.getSql();
  if (hasStatementFor(sql)) {
    // 獲取複用的Statement
    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;
}

private boolean hasStatementFor(String sql) {
  try {
    // 根據sql判斷是否緩存了Statement,並判斷Connection是否關閉
    return statementMap.keySet().contains(sql) && !statementMap.get(sql).getConnection().isClosed();
  } catch (SQLException e) {
    return false;
  }
}

private Statement getStatement(String s) {
  return statementMap.get(s);
}

private void putStatement(String sql, Statement stmt) {
  statementMap.put(sql, stmt);
}

BatchExecutor

批處理執行器,經過封裝jdbc的 statement.addBatch(String sql) 以及 statement.executeBatch(); 來實現的批處理。該執行器的事務只能是手動提交模式。緩存

咱們平時執行批量的處理是通常還可使用sql拼接的方式。mybatis

執行批量更新時建議一次不要更新太多數據,若是更新數據量比較大時能夠分段執行。app

CachingExecutor

若是開啓了二級緩存那麼Mybatis會使用CachingExecutor執行器,CachingExecutor使用了裝飾器模式。

@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); // issue #578 and #116
      }
      return list;
    }
  }
  // 沒找到緩存,直接調用被裝飾則的方法
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

經過源碼咱們發現,整個查詢流程變成 了 L2 -> L1 -> DB。CachingExecutor的查詢流程,增長了二級緩存的查詢操做。

咱們在實際使用緩存過程當中通常不多使用Mybatis的二級緩存,若是想作二級緩存,建議直接在service層面使用第三方緩存框架,推薦使用爲監控而生的多級緩存框架 layering-cache,使用更方便靈活,查詢流程是 L1 -> L2 -> DB。

總結

  • BaseExecutor:使用了模板方法模式,定義了Executor的基本實現,它是一個抽象類,不能直接對外提供服務。
  • SimpleExecutor:普通的執行器,Mybatis的默認使用該執行器,每次新建Statement。
  • ReuseExecutor:能夠重用Statement的執行器,可是這個Statement緩存只在一次SqlSession中有效,咱們平時生少有在一次SqlSession中進行屢次同樣的查詢操做,因此性能提高並不大。
  • BatchExecutor:批處理執行器
  • CachingExecutor:二級緩存執行器,使用裝飾器模式,整個查詢流程變成 了 L2 -> L1 -> DB。建議直接使用第三方緩存框架,如:爲監控而生的多級緩存框架 layering-cache

Mybatis 源碼中文註釋

https://github.com/xiaolyuh/mybatis

相關文章
相關標籤/搜索