MyBatis原理系列(四)-手把手帶你了解MyBatis的Executor執(zhí)行器

MyBatis原理系列(一)-手把手帶你閱讀MyBatis源碼
MyBatis原理系列(二)-手把手帶你了解MyBatis的啟動流程
MyBatis原理系列(三)-手把手帶你了解SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的關(guān)系
MyBatis原理系列(四)-手把手帶你了解MyBatis的Executor執(zhí)行器
MyBatis原理系列(五)-手把手帶你了解Statement、StatementHandler、MappedStatement間的關(guān)系
MyBatis原理系列(六)-手把手帶你了解BoundSql的創(chuàng)建過程
MyBatis原理系列(七)-手把手帶你了解如何自定義插件
MyBatis原理系列(八)-手把手帶你了解一級緩存和二級緩存
MyBatis原理系列(九)-手把手帶你了解MyBatis事務(wù)管理機制

上篇文章中我們對SqlSession有了比較詳細(xì)的講解坟比,SqlSession執(zhí)行的方法其實都交由Executor進行執(zhí)行代箭。Executor執(zhí)行器作為MyBatis的重要組件之一伍宦,也是有著比較優(yōu)秀的設(shè)計和復(fù)雜的細(xì)節(jié)。在這篇文章將會圍繞Executor來進行抽絲剝繭,探討Executor的原理。

1. Executor初識

Executor 作為一個接口碘菜,包含更新,查詢限寞,事務(wù)等一系列方法忍啸。每個SqlSession對象都會有一個Executor對象,SqlSession的操作都會由Executor執(zhí)行器執(zhí)行履植。Executor接口有個抽象實現(xiàn)BaseExecutor類计雌,其中定義了一些模板方法,由子類實現(xiàn)玫霎。

Executor 接口中定義的方法如下:

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;
  // 更新
  int update(MappedStatement ms, Object parameter) throws SQLException;

  // 先查詢緩存凿滤,在查詢數(shù)據(jù)庫
  <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;

  // 返回游標(biāo)對象
  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

  // 釋放Statement
  List<BatchResult> flushStatements() throws SQLException;

  // 事務(wù)提交
  void commit(boolean required) throws SQLException;

  // 事務(wù)回滾
  void rollback(boolean required) throws SQLException;

  // 創(chuàng)建緩存的鍵值對
  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);

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

  void close(boolean forceRollback);

  boolean isClosed();

  void setExecutorWrapper(Executor executor);

}
1.1 Executor繼承關(guān)系

Executor接口有兩個實現(xiàn),一個是BaseExecutor抽象類庶近,一個是CachingExecutor實現(xiàn)類鸭巴。BaseExecutor抽象類有四個實現(xiàn)SimpleExecutor,BathExecutor, ReuseExecutor, ClosedExecutor拦盹。

Executor繼承關(guān)系

BaseExecutor抽象類 采用模版方法的設(shè)計模式,定義了一些模版方法溪椎,即抽象方法普舆。Executor接口的其它方法BaseExecutor都給出了默認(rèn)實現(xiàn),進行緩存管理和事務(wù)操作校读,從而降低了接口的實現(xiàn)的難度沼侣。

  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;

四類Executor中,默認(rèn)采用的是SimpleExectutor歉秫。每個Executor的特點如下:

  1. SimpleExecutor:默認(rèn)的執(zhí)行器蛾洛,每次執(zhí)行update或者select操作,都會創(chuàng)建一個Statement對象,執(zhí)行結(jié)束后關(guān)閉Statement對象轧膘。
  2. ReuseExecutor:可重用執(zhí)行器钞螟,重用的是Statement對象,第一次執(zhí)行一條sql谎碍,會將這條sql的Statement對象緩存在key-value結(jié)構(gòu)的map緩存中鳞滨。下一次執(zhí)行,就可以從緩存中取出Statement對象蟆淀,減少了重復(fù)編譯的次數(shù)拯啦,從而提高了性能。每個SqlSession對象都有一個Executor對象熔任,因此這個緩存是SqlSession級別的褒链,當(dāng)SqlSession銷毀時,緩存也會銷毀疑苔。
  3. BatchExecutor:批量執(zhí)行器甫匹,默認(rèn)情況是每次執(zhí)行一條sql,MyBatis都會發(fā)送一條sql夯巷。而批量執(zhí)行器的操作是赛惩,每次執(zhí)行一條sql,不會立馬發(fā)送到數(shù)據(jù)庫趁餐,而是批量一次性發(fā)送sql喷兼。
    4.ClosedExecutor: ResultLoaderMap的內(nèi)部類,用來進行處理懶加載相關(guān)后雷,懶加載相關(guān)將在其它文章中展開介紹季惯。
1.2 Executor 創(chuàng)建

Executor 是怎么創(chuàng)建出來的呢,在前幾篇文章中臀突,我們都沒有顯示的引用Executor勉抓,其實SqlSession引用著Executor對象汉形。在我們從SqlSessionFactory獲取SqlSession對象的時候澎羞,會調(diào)用到SqlSessionFactory的openSession()方法,其實調(diào)用了openSessionFromDataSource方法圈驼,在這個方法中梳码,創(chuàng)建了事物隐圾,也創(chuàng)建了Executor對象。

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

順著configuration.newExecutor(tx, execType)掰茶,我們繼續(xù)往里看暇藏。根據(jù)ExecutorType來創(chuàng)建不同類型的執(zhí)行器,默認(rèn)創(chuàng)建的是SimpleExecutor這個執(zhí)行器濒蒋。如果一級緩存開啟(默認(rèn)是開啟的)盐碱,還會用CachingExecutor來包裝SimpleExecutor執(zhí)行器,在這里用到了裝飾者設(shè)計模式。

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    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) {
      executor = new CachingExecutor(executor);
    }
   // 加載插件鏈
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

三種Executor的創(chuàng)建都是調(diào)用了BaseExecutor的構(gòu)造函數(shù)瓮顽,BaseExecutor的構(gòu)造函數(shù)如下县好,涉及到事務(wù)讀喜慶,懶加載趣倾,緩存的相關(guān)初始化聘惦。至此一個SqlSession就有了自己唯一的Executor對象了。

  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<>();
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }
1.3 Executor 執(zhí)行

我們調(diào)用mapper接口的方法時儒恋,最終都會調(diào)用到SqlSession的方法善绎,以DefaultSqlSession為例子,查詢方法如下诫尽,最終調(diào)用也就是Executor.query()方法禀酱。Executor是SqlSession的傀儡無疑了。

  @Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

2. SimpleExecutor

SimpleExecutor 是默認(rèn)的執(zhí)行器牧嫉,也是最簡單的執(zhí)行器剂跟。它實現(xiàn)了BaseExecutor定義的四個抽象方法,doUpdate酣藻,doQuery曹洽,doQueryCursor和doFlushStatements四個方法。

在這里以doUpdate為例辽剧,介紹下SimpleExecutor的操作步驟

  1. 創(chuàng)建StatementHandler
  2. 創(chuàng)建Statement
  3. 執(zhí)行sql操作
  4. 關(guān)閉Statement
@Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      // 1. 創(chuàng)建StatementHandler
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      // 2. 創(chuàng)建Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 3. 執(zhí)行sql操作
      return handler.update(stmt);
    } finally {
      // 2. 關(guān)閉Statement
      closeStatement(stmt);
    }
  }

StatementHandler 是用來管理JDBC中的Statement對象送淆,并進行和數(shù)據(jù)庫的操作。StatementHandler和Statement的相關(guān)在其它文章中展開詳述怕轿,在此不贅述偷崩。

3. ReuseExecutor

上面提到ReuseExecutor就是重用Statement對象,如果在一個SqlSession中多次執(zhí)行一條sql撞羽,如果每次都去生成Statement對象阐斜,會造成一大筆資源浪費。因此ReuseExecutor在SimpleExecutor的基礎(chǔ)上诀紊,對prepareStatement()方法進行了改進谒出,將Statement對象緩存在內(nèi)存中,并且免去了第四步:關(guān)閉Statement對象邻奠。

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

prepareStatement 做的就是四步驟:

  1. Sql是否命中緩存
  2. 命中緩存直接從緩存中獲取到Statement對象
  3. 如果緩存中沒有到推,則創(chuàng)建新的Statement對象
  4. 接第3步,以sql為key, Statement為value放到緩存中
private final Map<String, Statement> statementMap = new HashMap<>();

/**
   * 獲取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();
    // 1惕澎。 sql中是否存在緩存中
    if (hasStatementFor(sql)) {
      // 2. 命中緩存直接從緩存中獲取到
      stmt = getStatement(sql);
      applyTransactionTimeout(stmt);
    } else {
      Connection connection = getConnection(statementLog);
      // 3. 創(chuàng)建新的Statement對象
      stmt = handler.prepare(connection, transaction.getTimeout());
      // 4. 以sql為key, Statement為value放到緩存中
      putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
  }

  /**
   * 緩存map中是否存在sql的Statement對象
   * @param sql
   * @return
   */
  private boolean hasStatementFor(String sql) {
    try {
      Statement statement = statementMap.get(sql);
      return statement != null && !statement.getConnection().isClosed();
    } catch (SQLException e) {
      return false;
    }
  }

  /**
   * 從緩存中獲取
   * @param s
   * @return
   */
  private Statement getStatement(String s) {
    return statementMap.get(s);
  }

  /**
   * 以sql為key, Statement為value放到緩存中
   * @param sql
   * @param stmt
   */
  private void putStatement(String sql, Statement stmt) {
    statementMap.put(sql, stmt);
  }

由于SqlSession都引用著自己的Executor對象,因此這個緩存是SqlSession級別的颜骤,如果SqlSession銷毀了唧喉,對應(yīng)的緩存也會將銷毀。

4. BatchExecutor

BatchExecutor 相對于復(fù)雜一些,批量的意思是批量的發(fā)送sql到數(shù)據(jù)庫八孝,而不是一個個的發(fā)送董朝。以doUpdate()為例,先將多個Statement對象存儲到List中干跛,然后再將執(zhí)行結(jié)果放到一個List中子姜。實際原理使用的是JDBC的批處理操作,在執(zhí)行doUpdate方法時楼入,sql不會立馬執(zhí)行哥捕,而是等到commit或者rollback,執(zhí)行JDBC的executeBatch方法嘉熊。

  1. doUpdate()返回的值是固定的遥赚,不是影響的行數(shù)
  2. 如果連續(xù)提交相同的sql,則只會執(zhí)行一次
  3. 提交sql不會立馬執(zhí)行阐肤,而是等到commit時候統(tǒng)一執(zhí)行
  4. 底層使用的是JDBC的批處理操作凫佛,addBatch()和executeBatch()操作。
// 批量更新處理的固定返回值孕惜,不是影響的行數(shù)
  public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;

  // Statement集合
  private final List<Statement> statementList = new ArrayList<>();
  // 批量結(jié)果集合
  private final List<BatchResult> batchResultList = new ArrayList<>();
  // 上一次Sql語句
  private String currentSql;
  // 上一次的MappedStatement對象
  private MappedStatement currentStatement;

  public BatchExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  @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;
    // 1. currentSql和currentStatement初始化為null愧薛,走else分支
    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 {
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);    // fix Issues 322
      // 2. 設(shè)置currentSql和currentStatement為當(dāng)前sql
      currentSql = sql;
      currentStatement = ms;
      // 3. 將Statement加到list集合中。
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
    // 4. 調(diào)用JDBC的addBatch()方法衫画,添加到批處理中
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

在doFlushStatements中毫炉,會遍歷statementList集合中的Statement,一條條執(zhí)行碧磅,并將結(jié)果加載到結(jié)果集合中碘箍。

@Override
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    try {
      List<BatchResult> results = new ArrayList<>();
      // 1. 回滾則直接返回
      if (isRollback) {
        return Collections.emptyList();
      }
      // 2. 遍歷statementList集合
      for (int i = 0, n = statementList.size(); i < n; i++) {
        Statement stmt = statementList.get(i);
        applyTransactionTimeout(stmt);
        BatchResult batchResult = batchResultList.get(i);
        try {
          // 3. 執(zhí)行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())) { //issue #141
            for (Object parameter : parameterObjects) {
              keyGenerator.processAfter(this, ms, stmt, parameter);
            }
          }
          // Close statement to close cursor #1109
          // 4. 關(guān)閉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);
        }
        // 5. 將結(jié)果加載到結(jié)果集合中
        results.add(batchResult);
      }
      return results;
    } finally {
      for (Statement stmt : statementList) {
        closeStatement(stmt);
      }
      currentSql = null;
      statementList.clear();
      batchResultList.clear();
    }
  }

5. CachingExecutor

在前面介紹創(chuàng)建Executor對象的時候,會判斷是否開啟了一級緩存鲸郊,如果開啟了丰榴,則用CachingExecutor來包裝以上三種類型中的一種執(zhí)行器,使用裝飾者設(shè)計模式來增強執(zhí)行器的緩存功能秆撮。

    // 是否開啟了一級緩存四濒,默認(rèn)開啟
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }

CachingExecutor 的構(gòu)造函數(shù)如下

 // 1. 委托執(zhí)行器,也就是被包裝的三種執(zhí)行器的中的一種
  private final Executor delegate;
  // 2. 緩存管理類职辨,用來管理TransactionalCache
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();

  public CachingExecutor(Executor delegate) {
    // 3. 互相引用
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }

在執(zhí)行更新操作的時候盗蟆,先清空緩存,再去執(zhí)行實際執(zhí)行器的update方法

@Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    // 清空緩存
    flushCacheIfRequired(ms);
    // 調(diào)用實際執(zhí)行器的update方法
    return delegate.update(ms, parameterObject);
  }

在執(zhí)行查詢的時候舒裤,先從緩存中獲取喳资,如果緩存中沒有再去調(diào)用實際執(zhí)行器的query方法查詢數(shù)據(jù)庫,并放到緩存中返回腾供。

  1. 獲取緩存key
  2. 查詢緩存仆邓,如果緩存命中直接返回
  3. 如果緩存中沒有鲜滩,或者緩存不存在,則查詢數(shù)據(jù)庫节值,并放到緩存中

TransactionalCacheManager 和 TransactionalCache涉及到緩存模塊徙硅,也打算在其它文章中講解,在此也就一筆帶過搞疗。

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 1. 獲取緩存key
    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) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        // 2. 如果緩存為空嗓蘑,則查詢數(shù)據(jù)庫放到緩存中
        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);
  }

6. Executor 栗子

前面講解了四種Executor,實際進行操作的是SimpleExecutor匿乃,ReuseExecutor桩皿,BatchExecutor三種,如果不指定執(zhí)行器類型扳埂,默認(rèn)是SimpleExecutor业簿。如果開啟了緩存,則會使用CachingExecutor進行包裝阳懂,增加緩存邏輯梅尤。
接下來將用幾個栗子來實際操作一番,也能將三種執(zhí)行器的特點展現(xiàn)出來岩调。

6.1 SimpleExecutor

在獲取sqlSession時可以指定執(zhí)行器的類型巷燥,先看看SimpleExecutor執(zhí)行結(jié)果。

public static void main(String[] args) {
        try {
            // 1. 讀取配置
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            // 2. 創(chuàng)建SqlSessionFactory工廠
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            // 3. 獲取sqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);
            // 4. 獲取Mapper
            TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
            // 5. 執(zhí)行接口方法
            for(long i = 1; i < 3; i++){
                TTestUser userInfo = userMapper.selectByPrimaryKey(i);
                System.out.println("userInfo = " + JSONUtil.toJsonStr(userInfo));
            }
            // 6. 提交事物
            sqlSession.commit();
            // 7. 關(guān)閉資源
            sqlSession.close();
            inputStream.close();
        } catch (Exception e){
            log.error(e.getMessage(), e);
        }
    }

可以從日志打印看到我們執(zhí)行了2次sql号枕,Statement編譯了2次缰揪。

SimpleExecutor
6.2 ReuseExecutor

ReuseExecutor 重用的就是Statement對象,以key-value形式來緩存Statement對象葱淳,避免了同一個sql編譯多次钝腺,從而提高性能。

public static void main(String[] args) {
        try {
            // 1. 讀取配置
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            // 2. 創(chuàng)建SqlSessionFactory工廠
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            // 3. 獲取sqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE);
            // 4. 獲取Mapper
            TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
            // 5. 執(zhí)行接口方法
            for(long i = 1; i < 3; i++){
                TTestUser userInfo = userMapper.selectByPrimaryKey(i);
                System.out.println("userInfo = " + JSONUtil.toJsonStr(userInfo));
            }
            // 6. 提交事物
            sqlSession.commit();
            // 7. 關(guān)閉資源
            sqlSession.close();
            inputStream.close();
        } catch (Exception e){
            log.error(e.getMessage(), e);
        }
    }

可以從日志打印看到我們執(zhí)行了2次sql赞厕,Statement編譯了1次艳狐。

ReuseExecutor
6.3 BatchExecutor

BatchExecutor 就是在commit時一次性提交sql,而不是發(fā)送一次皿桑,執(zhí)行一次毫目。

 try {
            // 1. 讀取配置
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            // 2. 創(chuàng)建SqlSessionFactory工廠
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            // 3. 獲取sqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
            // 4. 獲取Mapper
            TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
            // 5. 執(zhí)行接口方法
            for(long i = 1; i < 3; i++){
                TTestUser userInfo = new TTestUser();
                userInfo.setMemberId(2000+i);
                userInfo.setNickname(2000+i+"_nick");
                userInfo.setRealName(2000+i+"_real");
                userMapper.insertSelective(userInfo);
                // 模擬插入間隔
                Thread.sleep(1000);
            }
            System.out.println("-------開始提交事務(wù)--------- ");
            // 6. 提交事物
            sqlSession.commit();
            System.out.println("-------結(jié)束提交事務(wù)--------- ");
            // 7. 關(guān)閉資源
            sqlSession.close();
            inputStream.close();
        } catch (Exception e){
            log.error(e.getMessage(), e);
        }

這個例子不太明顯,但是可以看到雖然 Thread.sleep(1000)了诲侮,但是BatchExecutor中的操作是在最后commit時镀虐,才會插入數(shù)據(jù)到數(shù)據(jù)庫中去,插入時間是一致的沟绪。

打印日志
插入的2條數(shù)據(jù)
6.4 CachingExecutor

CachingExecutor不會執(zhí)行具體的更新和查詢操作刮便,而是在執(zhí)行更新操作的時候先清除下緩存,在執(zhí)行查詢操作的時候先從緩存中查找绽慈,如果命中緩存直接返回诺核。

try {
            // 1. 讀取配置
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            // 2. 創(chuàng)建SqlSessionFactory工廠
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            // 3. 獲取sqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);
            // 4. 獲取Mapper
            TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
            // 5. 執(zhí)行接口方法
            for(long i = 1; i < 3; i++){
                TTestUser userInfo = userMapper.selectByPrimaryKey(2001L);
                System.out.println("userInfo = " + JSONUtil.toJsonStr(userInfo));
            }
            // 6. 提交事物
            sqlSession.commit();
            // 7. 關(guān)閉資源
            sqlSession.close();
            inputStream.close();
        } catch (Exception e){
            log.error(e.getMessage(), e);
        }

在這個栗子中抄肖,雖然我們執(zhí)行了兩次sql,但是參數(shù)和sql語句都是一樣的窖杀,所以一次的查詢結(jié)果會被緩存,第二次查詢的時候直接從緩存中去取裙士。


CachingExecutor

7. 總結(jié)

這篇文章介紹了Executor執(zhí)行器的接口入客,繼承關(guān)系,三種執(zhí)行器腿椎,以及用了幾個例子來介紹他們的區(qū)別桌硫,想必大家對Executor已經(jīng)有了很全面的了解了吧。希望大家能夠在評論區(qū)評論指正啃炸,一起進步铆隘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市南用,隨后出現(xiàn)的幾起案子膀钠,更是在濱河造成了極大的恐慌,老刑警劉巖裹虫,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肿嘲,死亡現(xiàn)場離奇詭異,居然都是意外死亡筑公,警方通過查閱死者的電腦和手機雳窟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匣屡,“玉大人封救,你說我怎么就攤上這事〉纷鳎” “怎么了誉结?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長虾宇。 經(jīng)常有香客問我搓彻,道長,這世上最難降的妖魔是什么嘱朽? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任旭贬,我火速辦了婚禮,結(jié)果婚禮上搪泳,老公的妹妹穿的比我還像新娘稀轨。我一直安慰自己,他們只是感情好岸军,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布奋刽。 她就那樣靜靜地躺著瓦侮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪佣谐。 梳的紋絲不亂的頭發(fā)上肚吏,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天,我揣著相機與錄音狭魂,去河邊找鬼罚攀。 笑死,一個胖子當(dāng)著我的面吹牛雌澄,可吹牛的內(nèi)容都是我干的斋泄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼镐牺,長吁一口氣:“原來是場噩夢啊……” “哼炫掐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起睬涧,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤募胃,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后宙地,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體摔认,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年宅粥,在試婚紗的時候發(fā)現(xiàn)自己被綠了参袱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡秽梅,死狀恐怖抹蚀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情企垦,我是刑警寧澤环壤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站钞诡,受9級特大地震影響郑现,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜荧降,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一接箫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧朵诫,春花似錦辛友、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽邓梅。三九已至,卻和暖如春邑滨,著一層夾襖步出監(jiān)牢的瞬間日缨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工掖看, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留殿遂,地道東北人。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓乙各,卻偏偏與公主長得像,于是被迫代替她去往敵國和親幢竹。 傳聞我的和親對象是個殘疾皇子耳峦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355

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