MyBatis 源碼分析(八):執(zhí)行器

執(zhí)行器 ExecutorMyBatis 的核心接口之一抒巢,接口層提供的相關(guān)數(shù)據(jù)庫操作键闺,都是基于 Executor 的子類實現(xiàn)的慢宗。

Executor體系

創(chuàng)建執(zhí)行器

在創(chuàng)建 sql 會話時再来,MyBatis 會調(diào)用 Configuration#newExecutor 方法創(chuàng)建執(zhí)行器。枚舉類 ExecutorType 定義了三種執(zhí)行器類型闻鉴,即 SIMPLE茵乱、REUSEBatch,這些執(zhí)行器的主要區(qū)別在于:

  • SIMPLE 在每次執(zhí)行完成后都會關(guān)閉 statement 對象孟岛;
  • REUSE 會在本地維護(hù)一個容器瓶竭,當(dāng)前 statement 創(chuàng)建完成后放入容器中,當(dāng)下次執(zhí)行相同的 sql 時會復(fù)用 statement 對象渠羞,執(zhí)行完畢后也不會關(guān)閉斤贰;
  • BATCH 會將修改操作記錄在本地,等待程序觸發(fā)或有下一次查詢時才批量執(zhí)行修改操作次询。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  // 默認(rèn)類型為 simple
  executorType = executorType == null ? defaultExecutorType : executorType;
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  Executor executor;
  if (ExecutorType.BATCH == executorType) {
    executor = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {
    executor = new ReuseExecutor(this, transaction);
  } else {
    executor = new SimpleExecutor(this, transaction);
  }
  if (cacheEnabled) {
    // 如果全局緩存打開荧恍,使用 CachingExecutor 代理執(zhí)行器
    executor = new CachingExecutor(executor);
  }
  // 應(yīng)用插件
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

執(zhí)行器創(chuàng)建后,如果全局緩存配置是有效的,則會將執(zhí)行器裝飾為 CachingExecutor送巡。

基礎(chǔ)執(zhí)行器

SimpleExecutor摹菠、ReuseExecutorBatchExecutor 均繼承自 BaseExecutor骗爆。BaseExecutor 實現(xiàn)了 Executor 的全部方法次氨,對緩存、事務(wù)摘投、連接處理等提供了一些模板方法煮寡,但是針對具體的數(shù)據(jù)庫操作留下了四個抽象方法交由子類實現(xiàn)。

  /**
   * 更新
   */
  protected abstract int doUpdate(MappedStatement ms, Object parameter)
      throws SQLException;

  /**
   * 刷新 statement
   */
  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;

  /**
   * 查詢獲取游標(biāo)對象
   */
  protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
      throws SQLException;

基礎(chǔ)執(zhí)行器的查詢邏輯如下:

  @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()) {
      // 非嵌套查詢且設(shè)置強制刷新時清除緩存
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        // 緩存不為空谷朝,組裝存儲過程出參
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 無本地緩存洲押,執(zhí)行數(shù)據(jù)庫查詢
        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;
  }

  /**
   * 查詢本地緩存,組裝存儲過程結(jié)果集
   */
  private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
    if (ms.getStatementType() == StatementType.CALLABLE) {
      // 存儲過程類型圆凰,查詢緩存
      final Object cachedParameter = localOutputParameterCache.getObject(key);
      if (cachedParameter != null && parameter != null) {
        final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
        final MetaObject metaParameter = configuration.newMetaObject(parameter);
        for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
          if (parameterMapping.getMode() != ParameterMode.IN) {
            // 參數(shù)類型為 OUT 或 INOUT 的杈帐,組裝結(jié)果集
            final String parameterName = parameterMapping.getProperty();
            final Object cachedValue = metaCachedParameter.getValue(parameterName);
            metaParameter.setValue(parameterName, cachedValue);
          }
        }
      }
    }
  }

  /**
   * 查詢數(shù)據(jù)庫獲取結(jié)果集
   */
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    // 放一個 placeHolder 標(biāo)志
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      // 執(zhí)行查詢
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    // 查詢結(jié)果集放入本地緩存
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      // 如果是存儲過程查詢,將存儲過程結(jié)果集放入本地緩存
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

執(zhí)行查詢時 MyBatis 首先會根據(jù) CacheKey 查詢本地緩存专钉,CacheKey 由本次查詢的參數(shù)生成挑童,本地緩存由 PerpetualCache 實現(xiàn),這就是 MyBatis 的一級緩存跃须。一級緩存維護(hù)對象 localCache 是基礎(chǔ)執(zhí)行器的本地變量站叼,因此只有相同 sql 會話的查詢才能共享一級緩存。當(dāng)一級緩存中沒有對應(yīng)的數(shù)據(jù)菇民,基礎(chǔ)執(zhí)行器最終會調(diào)用 doQuery 方法交由子類去獲取數(shù)據(jù)尽楔。

而執(zhí)行 update 等其它操作時,則會首先清除本地的一級緩存再交由子類執(zhí)行具體的操作:

  @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();
    // 調(diào)用子類執(zhí)行器邏輯
    return doUpdate(ms, parameter);
  }

簡單執(zhí)行器

簡單執(zhí)行器是 MyBatis 的默認(rèn)執(zhí)行器第练。其封裝了對 JDBC 的操作阔馋,對于查詢方法 doQuery 的實現(xiàn)如下,其主要包括創(chuàng)建 statement 處理器娇掏、創(chuàng)建 statement呕寝、執(zhí)行查詢、關(guān)閉 statement婴梧。

  @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();
      // 創(chuàng)建 statement 處理器
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 創(chuàng)建 statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 執(zhí)行查詢
      return handler.query(stmt, resultHandler);
    } finally {
      // 關(guān)閉 statement
      closeStatement(stmt);
    }
  }

創(chuàng)建 statement 處理器

全局配置類 Configuration 提供了方法 newStatementHandler 用于創(chuàng)建 statement 處理器:

  /**
   * 創(chuàng)建 statement 處理器
   */
  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // StatementHandler 包裝對象下梢,根據(jù) statement 類型創(chuàng)建代理處理器,并將實際操作委托給代理處理器處理
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 應(yīng)用插件
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

實際每次創(chuàng)建的 statement 處理器對象都是由 RoutingStatementHandler 創(chuàng)建的塞蹭,RoutingStatementHandler 根據(jù)當(dāng)前 MappedStatement 的類型創(chuàng)建具體的 statement 類型處理器孽江。StatementType 定義了 3statement 類型枚舉,分別對應(yīng) JDBC 的普通語句浮还、預(yù)編譯語句和存儲過程語句竟坛。

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 根據(jù) statement 類型選擇對應(yīng)的 statementHandler
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
  }

創(chuàng)建 statement

簡單執(zhí)行器中的 Statement 對象是根據(jù)上述步驟中生成的 statement 處理器獲取的。

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 獲取代理連接對象
    Connection connection = getConnection(statementLog);
    // 創(chuàng)建 statement 對象
    stmt = handler.prepare(connection, transaction.getTimeout());
    // 設(shè)置 statement 參數(shù)
    handler.parameterize(stmt);
    return stmt;
  }

    @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      // 從連接中創(chuàng)建 statement 對象
      statement = instantiateStatement(connection);
      // 設(shè)置超時時間
      setStatementTimeout(statement, transactionTimeout);
      // 設(shè)置分批獲取數(shù)據(jù)數(shù)量
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

執(zhí)行查詢

創(chuàng)建 statement 對象完成即可通過 JDBCAPI 執(zhí)行數(shù)據(jù)庫查詢钧舌,并從 statement 對象中獲取查詢結(jié)果担汤,根據(jù)配置進(jìn)行轉(zhuǎn)換。

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 執(zhí)行查詢
    ps.execute();
    // 處理結(jié)果集
    return resultSetHandler.handleResultSets(ps);
  }

  /**
   * 處理結(jié)果集
   */
  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    // 多結(jié)果集
    final List<Object> multipleResults = new ArrayList<>();
    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    // statement 對應(yīng)的所有 ResultMap 對象
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    // 驗證結(jié)果集不為空時洼冻,ResultMap 數(shù)量不能為 0
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      // 逐個獲取 ResultMap
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 轉(zhuǎn)換結(jié)果集崭歧,放到 multipleResults 容器中
      handleResultSet(rsw, resultMap, multipleResults, null);
      // 獲取下一個待處理的結(jié)果集
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    // statement 配置的多結(jié)果集類型
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

關(guān)閉連接

查詢完成后 statement 對象會被關(guān)閉。

  /**
   * 關(guān)閉 statement
   *
   * @param statement
   */
  protected void closeStatement(Statement statement) {
    if (statement != null) {
      try {
        statement.close();
      } catch (SQLException e) {
        // ignore
      }
    }
  }

簡單執(zhí)行器中的其它數(shù)據(jù)庫執(zhí)行方法與 doQuery 方法實現(xiàn)類似撞牢。

復(fù)用執(zhí)行器

ReuseExecutor 相對于 SimpleExecutor 實現(xiàn)了對 statment 對象的復(fù)用率碾,其在本地維護(hù)了 statementMap 用于保存 sql 語句和 statement 對象的關(guān)系。當(dāng)調(diào)用 prepareStatement 方法獲取 statement 對象時首先會查找本地是否有對應(yīng)的 statement 對象屋彪,如果有則進(jìn)行復(fù)用所宰,負(fù)責(zé)重新創(chuàng)建并將 statement 對象放入本地緩存。

  /**
   * 創(chuàng)建 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)) {
      // 如果本地容器中包含當(dāng)前 sql 對應(yīng)的 statement 對象畜挥,進(jìn)行復(fù)用
      stmt = getStatement(sql);
      applyTransactionTimeout(stmt);
    } else {
      Connection connection = getConnection(statementLog);
      stmt = handler.prepare(connection, transaction.getTimeout());
      putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
  }

  private boolean hasStatementFor(String sql) {
    try {
      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);
  }

提交或回滾會導(dǎo)致執(zhí)行器調(diào)用 doFlushStatements 方法仔粥,復(fù)用執(zhí)行器會因此批量關(guān)閉本地的 statement 對象。

  /**
   * 批量關(guān)閉 statement 對象
   *
   * @param isRollback
   * @return
   */
  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) {
    for (Statement stmt : statementMap.values()) {
      closeStatement(stmt);
    }
    statementMap.clear();
    return Collections.emptyList();
  }

批量執(zhí)行器

BatchExecutor 相對于 SimpleExecutor 蟹但,其 update 操作是批量執(zhí)行的。

  @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();
    final String sql = boundSql.getSql();
    final Statement stmt;
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      // 如果當(dāng)前 sql 與上次傳入 sql 相同且為相同的 MappedStatement,復(fù)用 statement 對象
      int last = statementList.size() - 1;
      // 獲取最后一個 statement 對象
      stmt = statementList.get(last);
      // 設(shè)置超時時間
      applyTransactionTimeout(stmt);
      // 設(shè)置參數(shù)
      handler.parameterize(stmt);//fix Issues 322
      // 獲取批量執(zhí)行結(jié)果對象
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
    } else {
      // 創(chuàng)建新的 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));
    }
    // 執(zhí)行 JDBC 批量添加 sql 語句操作
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

  /**
   * 批量執(zhí)行 sql
   *
   * @param isRollback
   * @return
   * @throws SQLException
   */
  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    try {
      // 批量執(zhí)行結(jié)果
      List<BatchResult> results = new ArrayList<>();
      if (isRollback) {
        return Collections.emptyList();
      }
      for (int i = 0, n = statementList.size(); i < n; i++) {
        Statement stmt = statementList.get(i);
        applyTransactionTimeout(stmt);
        BatchResult batchResult = batchResultList.get(i);
        try {
          // 設(shè)置執(zhí)行影響行數(shù)
          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;
            // 設(shè)置數(shù)據(jù)庫生成的主鍵
            jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
          } else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { //issue #141
            for (Object parameter : parameterObjects) {
              keyGenerator.processAfter(this, ms, stmt, parameter);
            }
          }
          // Close statement to close cursor #1109
          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);
        }
        results.add(batchResult);
      }
      return results;
    } finally {
      for (Statement stmt : statementList) {
        closeStatement(stmt);
      }
      currentSql = null;
      statementList.clear();
      batchResultList.clear();
    }
  }

執(zhí)行器提交或回滾事務(wù)時會調(diào)用 doFlushStatements哄酝,從而批量執(zhí)行提交的 sql 語句并最終批量關(guān)閉 statement 對象瓢棒。

緩存執(zhí)行器與二級緩存

CachingExecutor 對基礎(chǔ)執(zhí)行器進(jìn)行了裝飾,其作用就是為查詢提供二級緩存客叉。所謂的二級緩存是由 CachingExecutor 維護(hù)的诵竭,相對默認(rèn)內(nèi)置的一級緩存而言的緩存。二者區(qū)別如下:

  • 一級緩存由基礎(chǔ)執(zhí)行器維護(hù)兼搏,且不可關(guān)閉卵慰。二級緩存的配置是開發(fā)者可干預(yù)的,在 xml 文件或注解中針對 namespace 的緩存配置就是二級緩存配置向族。
  • 一級緩存在執(zhí)行器中維護(hù)呵燕,即不同 sql 會話不能共享一級緩存。二級緩存則是根據(jù) namespace 維護(hù)件相,不同 sql 會話是可以共享二級緩存的再扭。

CachingExecutor 中的方法大多是通過直接調(diào)用其代理的執(zhí)行器來實現(xiàn)的,而查詢操作則會先查詢二級緩存夜矗。

   /**
   * 緩存事務(wù)管理器
   */
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();

    @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) {
        // 當(dāng)前 statement 配置使用二級緩存
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          // 二級緩存中沒用數(shù)據(jù)泛范,調(diào)用代理執(zhí)行器
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          // 將查詢結(jié)果放入二級緩存
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    // 無二級緩存配置,調(diào)用代理執(zhí)行器獲取結(jié)果
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {
      // 存在 namespace 對應(yīng)的緩存配置紊撕,且當(dāng)前 statement 配置了刷新緩存罢荡,執(zhí)行清空緩存操作
      // 非 select 語句配置了默認(rèn)刷新
      tcm.clear(cache);
    }
  }

如果對應(yīng)的 statement 的二級緩存配置有效,則會先通過緩存事務(wù)管理器 TransactionalCacheManager 查詢二級緩存,如果沒有命中則查詢一級緩存区赵,仍沒有命中才會執(zhí)行數(shù)據(jù)庫查詢惭缰。

緩存事務(wù)管理器

緩存執(zhí)行器對二級緩存的維護(hù)是基于緩存事務(wù)管理器 TransactionalCacheManager 的,其內(nèi)部維護(hù)了一個 Map 容器笼才,用于保存 namespace 緩存配置與事務(wù)緩存對象的映射關(guān)系漱受。

public class TransactionalCacheManager {

  /**
   * 緩存配置 - 緩存事務(wù)對象
   */
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();

  /**
   * 清除緩存
   *
   * @param cache
   */
  public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
  }

  /**
   * 獲取緩存
   */
  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }

  /**
   * 寫緩存
   */
  public void putObject(Cache cache, CacheKey key, Object value) {
    getTransactionalCache(cache).putObject(key, value);
  }

  /**
   * 緩存提交
   */
  public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.commit();
    }
  }

  /**
   * 緩存回滾
   */
  public void rollback() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.rollback();
    }
  }

  /**
   * 獲取或新建事務(wù)緩存對象
   */
  private TransactionalCache getTransactionalCache(Cache cache) {
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }

}

緩存配置映射的事務(wù)緩存對象就是前文中提到過的事務(wù)緩存裝飾器 TransactionalCachegetTransactionalCache 會從維護(hù)容器中查找對應(yīng)的事務(wù)緩存對象骡送,如果找不到就創(chuàng)建一個事務(wù)緩存對象昂羡,即通過事務(wù)緩存對象裝飾當(dāng)前緩存配置。

查詢緩存時摔踱,如果緩存未命中虐先,則將對應(yīng)的 key 放入未命中隊列,執(zhí)行數(shù)據(jù)庫查詢完畢后寫緩存時并不是立刻寫到緩存配置的本地容器中派敷,而是暫時放入待提交隊列中蛹批,當(dāng)觸發(fā)事務(wù)提交時才將提交隊列中的緩存數(shù)據(jù)寫到緩存配置中。如果發(fā)生回滾膀息,則提交隊列中的數(shù)據(jù)會被清空般眉,從而保證了數(shù)據(jù)的一致性。

  @Override
  public Object getObject(Object key) {
    // issue #116
    Object object = delegate.getObject(key);
    if (object == null) {
      // 放入未命中緩存的 key 的隊列
      entriesMissedInCache.add(key);
    }
    // issue #146
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

  @Override
  public void putObject(Object key, Object object) {
    // 緩存先寫入待提交容器
    entriesToAddOnCommit.put(key, object);
  }

  /**
   * 事務(wù)提交
   */
  public void commit() {
    if (clearOnCommit) {
      delegate.clear();
    }
    // 提交緩存
    flushPendingEntries();
    reset();
  }

  /**
   * 事務(wù)回滾
   */
  public void rollback() {
    unlockMissedEntries();
    reset();
  }

  /**
   * 事務(wù)提交潜支,提交待提交的緩存甸赃。
   */
  private void flushPendingEntries() {
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }

  /**
   * 事務(wù)回滾,清理未命中緩存冗酿。
   */
  private void unlockMissedEntries() {
    for (Object entry : entriesMissedInCache) {
      try {
        delegate.removeObject(entry);
      } catch (Exception e) {
        log.warn("Unexpected exception while notifiying a rollback to the cache adapter."
            + "Consider upgrading your cache adapter to the latest version.  Cause: " + e);
      }
    }
  }

二級緩存與一級緩存的互斥性

使用二級緩存要求無論是否配置了事務(wù)自動提交埠对,在執(zhí)行完成后, sql 會話必須手動提交事務(wù)才能觸發(fā)事務(wù)緩存管理器維護(hù)緩存到緩存配置中裁替,否則二級緩存無法生效项玛。而緩存執(zhí)行器在觸發(fā)事務(wù)提交時,不僅會調(diào)用事務(wù)緩存管理器提交弱判,還會調(diào)用代理執(zhí)行器提交事務(wù):

  @Override
  public void commit(boolean required) throws SQLException {
    // 代理執(zhí)行器提交
    delegate.commit(required);
    // 事務(wù)緩存管理器提交
    tcm.commit();
  }

代理執(zhí)行器的事務(wù)提交方法繼承自 BaseExecutor襟沮,其 commit 方法中調(diào)用了 clearLocalCache 方法清除本地一級緩存。因此二級緩存和一級緩存的使用是互斥的昌腰。

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

小結(jié)

MyBatis 提供若干執(zhí)行器封裝底層 JDBC 操作和結(jié)果集轉(zhuǎn)換开伏,并嵌入 sql 會話維度的一級緩存和 namespace 維度的二級緩存。接口層可以通過調(diào)用不同類型的執(zhí)行器來完成 sql 相關(guān)操作遭商。

  • org.apache.ibatis.executor.Executor:數(shù)據(jù)庫操作執(zhí)行器抽象接口固灵。
  • org.apache.ibatis.executor.BaseExecutor:執(zhí)行器基礎(chǔ)抽象實現(xiàn)。
  • org.apache.ibatis.executor.SimpleExecutor:簡單類型執(zhí)行器劫流。
  • org.apache.ibatis.executor.ReuseExecutorstatement 復(fù)用執(zhí)行器巫玻。
  • org.apache.ibatis.executor.BatchExecutor:批量執(zhí)行器丛忆。
  • org.apache.ibatis.executor.CachingExecutor:二級緩存執(zhí)行器。
  • org.apache.ibatis.executor.statement.StatementHandlerstatement 處理器抽象接口仍秤。
  • org.apache.ibatis.executor.statement.BaseStatementHandlerstatement 處理器基礎(chǔ)抽象實現(xiàn)熄诡。
  • org.apache.ibatis.executor.statement.RoutingStatementHandlerstatement 處理器路由對象。
  • org.apache.ibatis.executor.statement.SimpleStatementHandler:簡單 statement 處理器徒扶。
  • org.apache.ibatis.executor.statement.PreparedStatementHandler:預(yù)編譯 statement 處理器粮彤。
  • org.apache.ibatis.executor.statement.CallableStatementHandler:存儲過程 statement 處理器根穷。
  • org.apache.ibatis.cache.TransactionalCacheManager:緩存事務(wù)管理器姜骡。

注釋源碼

注釋源碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市屿良,隨后出現(xiàn)的幾起案子圈澈,更是在濱河造成了極大的恐慌,老刑警劉巖尘惧,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件康栈,死亡現(xiàn)場離奇詭異,居然都是意外死亡喷橙,警方通過查閱死者的電腦和手機啥么,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贰逾,“玉大人悬荣,你說我怎么就攤上這事「斫#” “怎么了氯迂?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長言缤。 經(jīng)常有香客問我嚼蚀,道長,這世上最難降的妖魔是什么管挟? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任轿曙,我火速辦了婚禮,結(jié)果婚禮上僻孝,老公的妹妹穿的比我還像新娘导帝。我一直安慰自己,他們只是感情好皮璧,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布舟扎。 她就那樣靜靜地躺著,像睡著了一般悴务。 火紅的嫁衣襯著肌膚如雪睹限。 梳的紋絲不亂的頭發(fā)上譬猫,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音羡疗,去河邊找鬼染服。 笑死,一個胖子當(dāng)著我的面吹牛叨恨,可吹牛的內(nèi)容都是我干的柳刮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼痒钝,長吁一口氣:“原來是場噩夢啊……” “哼秉颗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起送矩,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蚕甥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后栋荸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體菇怀,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年晌块,在試婚紗的時候發(fā)現(xiàn)自己被綠了爱沟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡匆背,死狀恐怖呼伸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情靠汁,我是刑警寧澤蜂大,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蝶怔,受9級特大地震影響奶浦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜踢星,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一澳叉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沐悦,春花似錦成洗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至副签,卻和暖如春遥椿,著一層夾襖步出監(jiān)牢的瞬間基矮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工冠场, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留家浇,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓碴裙,卻偏偏與公主長得像钢悲,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子舔株,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容