MyBatis原理系列(四)-手把手带你了解MyBatis的Executor执行器

MyBatis原理系列(一)-手把手带你阅读MyBatis源码
MyBatis原理系列(二)-手把手带你了解MyBatis的启动流程
MyBatis原理系列(三)-手把手带你了解SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的关系
MyBatis原理系列(四)-手把手带你了解MyBatis的Executor执行器
MyBatis原理系列(五)-手把手带你了解Statement、StatementHandler、MappedStatement间的关系
MyBatis原理系列(六)-手把手带你了解BoundSql的创建过程
MyBatis原理系列(七)-手把手带你了解如何自定义插件
MyBatis原理系列(八)-手把手带你了解一级缓存和二级缓存
MyBatis原理系列(九)-手把手带你了解MyBatis事务管理机制

上篇文章中我们对SqlSession有了比较详细的讲解,SqlSession执行的方法其实都交由Executor进行执行。Executor执行器作为MyBatis的重要组件之一,也是有着比较优秀的设计和复杂的细节。在这篇文章将会围绕Executor来进行抽丝剥茧,探讨Executor的原理。

1. Executor初识

Executor 作为一个接口,包含更新,查询,事务等一系列方法。每个SqlSession对象都会有一个Executor对象,SqlSession的操作都会由Executor执行器执行。Executor接口有个抽象实现BaseExecutor类,其中定义了一些模板方法,由子类实现。

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

public interface Executor {

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

  // 先查询缓存,在查询数据库
   List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

  // 查询
   List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

  // 返回游标对象
   Cursor queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

  // 释放Statement
  List 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);

}
1.1 Executor继承关系

Executor接口有两个实现,一个是BaseExecutor抽象类,一个是CachingExecutor实现类。BaseExecutor抽象类有四个实现SimpleExecutor,BathExecutor, ReuseExecutor, ClosedExecutor。

Executor继承关系

BaseExecutor抽象类 采用模版方法的设计模式,定义了一些模版方法,即抽象方法。Executor接口的其它方法BaseExecutor都给出了默认实现,进行缓存管理和事务操作,从而降低了接口的实现的难度。

  protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

  protected abstract List doFlushStatements(boolean isRollback) throws SQLException;

  protected abstract  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

  protected abstract  Cursor doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
      throws SQLException;

四类Executor中,默认采用的是SimpleExectutor。每个Executor的特点如下:

  1. SimpleExecutor:默认的执行器,每次执行update或者select操作,都会创建一个Statement对象,执行结束后关闭Statement对象。
  2. ReuseExecutor:可重用执行器,重用的是Statement对象,第一次执行一条sql,会将这条sql的Statement对象缓存在key-value结构的map缓存中。下一次执行,就可以从缓存中取出Statement对象,减少了重复编译的次数,从而提高了性能。每个SqlSession对象都有一个Executor对象,因此这个缓存是SqlSession级别的,当SqlSession销毁时,缓存也会销毁。
  3. BatchExecutor:批量执行器,默认情况是每次执行一条sql,MyBatis都会发送一条sql。而批量执行器的操作是,每次执行一条sql,不会立马发送到数据库,而是批量一次性发送sql。
    4.ClosedExecutor: ResultLoaderMap的内部类,用来进行处理懒加载相关,懒加载相关将在其它文章中展开介绍。
1.2 Executor 创建

Executor 是怎么创建出来的呢,在前几篇文章中,我们都没有显示的引用Executor,其实SqlSession引用着Executor对象。在我们从SqlSessionFactory获取SqlSession对象的时候,会调用到SqlSessionFactory的openSession()方法,其实调用了openSessionFromDataSource方法,在这个方法中,创建了事物,也创建了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),我们继续往里看。根据ExecutorType来创建不同类型的执行器,默认创建的是SimpleExecutor这个执行器。如果一级缓存开启(默认是开启的),还会用CachingExecutor来包装SimpleExecutor执行器,在这里用到了装饰者设计模式。

  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的创建都是调用了BaseExecutor的构造函数,BaseExecutor的构造函数如下,涉及到事务读喜庆,懒加载,缓存的相关初始化。至此一个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 执行

我们调用mapper接口的方法时,最终都会调用到SqlSession的方法,以DefaultSqlSession为例子,查询方法如下,最终调用也就是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 是默认的执行器,也是最简单的执行器。它实现了BaseExecutor定义的四个抽象方法,doUpdate,doQuery,doQueryCursor和doFlushStatements四个方法。

在这里以doUpdate为例,介绍下SimpleExecutor的操作步骤

  1. 创建StatementHandler
  2. 创建Statement
  3. 执行sql操作
  4. 关闭Statement
@Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      // 1. 创建StatementHandler
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      // 2. 创建Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 3. 执行sql操作
      return handler.update(stmt);
    } finally {
      // 2. 关闭Statement
      closeStatement(stmt);
    }
  }

StatementHandler 是用来管理JDBC中的Statement对象,并进行和数据库的操作。StatementHandler和Statement的相关在其它文章中展开详述,在此不赘述。

3. ReuseExecutor

上面提到ReuseExecutor就是重用Statement对象,如果在一个SqlSession中多次执行一条sql,如果每次都去生成Statement对象,会造成一大笔资源浪费。因此ReuseExecutor在SimpleExecutor的基础上,对prepareStatement()方法进行了改进,将Statement对象缓存在内存中,并且免去了第四步:关闭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. 如果缓存中没有,则创建新的Statement对象
  4. 接第3步,以sql为key, Statement为value放到缓存中
private final Map 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. 创建新的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销毁了,对应的缓存也会将销毁。

4. BatchExecutor

BatchExecutor 相对于复杂一些,批量的意思是批量的发送sql到数据库,而不是一个个的发送。以doUpdate()为例,先将多个Statement对象存储到List中,然后再将执行结果放到一个List中。实际原理使用的是JDBC的批处理操作,在执行doUpdate方法时,sql不会立马执行,而是等到commit或者rollback,执行JDBC的executeBatch方法。

  1. doUpdate()返回的值是固定的,不是影响的行数
  2. 如果连续提交相同的sql,则只会执行一次
  3. 提交sql不会立马执行,而是等到commit时候统一执行
  4. 底层使用的是JDBC的批处理操作,addBatch()和executeBatch()操作。
// 批量更新处理的固定返回值,不是影响的行数
  public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;

  // Statement集合
  private final List statementList = new ArrayList<>();
  // 批量结果集合
  private final List 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. 设置currentSql和currentStatement为当前sql
      currentSql = sql;
      currentStatement = ms;
      // 3. 将Statement加到list集合中。
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
    // 4. 调用JDBC的addBatch()方法,添加到批处理中
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

在doFlushStatements中,会遍历statementList集合中的Statement,一条条执行,并将结果加载到结果集合中。

@Override
  public List doFlushStatements(boolean isRollback) throws SQLException {
    try {
      List 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. 执行sql
          batchResult.setUpdateCounts(stmt.executeBatch());
          MappedStatement ms = batchResult.getMappedStatement();
          List 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. 关闭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. 将结果加载到结果集合中
        results.add(batchResult);
      }
      return results;
    } finally {
      for (Statement stmt : statementList) {
        closeStatement(stmt);
      }
      currentSql = null;
      statementList.clear();
      batchResultList.clear();
    }
  }
 
 

5. CachingExecutor

在前面介绍创建Executor对象的时候,会判断是否开启了一级缓存,如果开启了,则用CachingExecutor来包装以上三种类型中的一种执行器,使用装饰者设计模式来增强执行器的缓存功能。

    // 是否开启了一级缓存,默认开启
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }

CachingExecutor 的构造函数如下

 // 1. 委托执行器,也就是被包装的三种执行器的中的一种
  private final Executor delegate;
  // 2. 缓存管理类,用来管理TransactionalCache
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();

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

在执行更新操作的时候,先清空缓存,再去执行实际执行器的update方法

@Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    // 清空缓存
    flushCacheIfRequired(ms);
    // 调用实际执行器的update方法
    return delegate.update(ms, parameterObject);
  }

在执行查询的时候,先从缓存中获取,如果缓存中没有再去调用实际执行器的query方法查询数据库,并放到缓存中返回。

  1. 获取缓存key
  2. 查询缓存,如果缓存命中直接返回
  3. 如果缓存中没有,或者缓存不存在,则查询数据库,并放到缓存中

TransactionalCacheManager 和 TransactionalCache涉及到缓存模块,也打算在其它文章中讲解,在此也就一笔带过。

@Override
  public  List 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  List 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 list = (List) tcm.getObject(cache, key);
        // 2. 如果缓存为空,则查询数据库放到缓存中
        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三种,如果不指定执行器类型,默认是SimpleExecutor。如果开启了缓存,则会使用CachingExecutor进行包装,增加缓存逻辑。
接下来将用几个栗子来实际操作一番,也能将三种执行器的特点展现出来。

6.1 SimpleExecutor

在获取sqlSession时可以指定执行器的类型,先看看SimpleExecutor执行结果。

public static void main(String[] args) {
        try {
            // 1. 读取配置
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            // 2. 创建SqlSessionFactory工厂
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            // 3. 获取sqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);
            // 4. 获取Mapper
            TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
            // 5. 执行接口方法
            for(long i = 1; i < 3; i++){
                TTestUser userInfo = userMapper.selectByPrimaryKey(i);
                System.out.println("userInfo = " + JSONUtil.toJsonStr(userInfo));
            }
            // 6. 提交事物
            sqlSession.commit();
            // 7. 关闭资源
            sqlSession.close();
            inputStream.close();
        } catch (Exception e){
            log.error(e.getMessage(), e);
        }
    }

可以从日志打印看到我们执行了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. 创建SqlSessionFactory工厂
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            // 3. 获取sqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE);
            // 4. 获取Mapper
            TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
            // 5. 执行接口方法
            for(long i = 1; i < 3; i++){
                TTestUser userInfo = userMapper.selectByPrimaryKey(i);
                System.out.println("userInfo = " + JSONUtil.toJsonStr(userInfo));
            }
            // 6. 提交事物
            sqlSession.commit();
            // 7. 关闭资源
            sqlSession.close();
            inputStream.close();
        } catch (Exception e){
            log.error(e.getMessage(), e);
        }
    }

可以从日志打印看到我们执行了2次sql,Statement编译了1次。

ReuseExecutor
6.3 BatchExecutor

BatchExecutor 就是在commit时一次性提交sql,而不是发送一次,执行一次。

 try {
            // 1. 读取配置
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            // 2. 创建SqlSessionFactory工厂
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            // 3. 获取sqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
            // 4. 获取Mapper
            TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
            // 5. 执行接口方法
            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("-------开始提交事务--------- ");
            // 6. 提交事物
            sqlSession.commit();
            System.out.println("-------结束提交事务--------- ");
            // 7. 关闭资源
            sqlSession.close();
            inputStream.close();
        } catch (Exception e){
            log.error(e.getMessage(), e);
        }

这个例子不太明显,但是可以看到虽然 Thread.sleep(1000)了,但是BatchExecutor中的操作是在最后commit时,才会插入数据到数据库中去,插入时间是一致的。

打印日志
插入的2条数据
6.4 CachingExecutor

CachingExecutor不会执行具体的更新和查询操作,而是在执行更新操作的时候先清除下缓存,在执行查询操作的时候先从缓存中查找,如果命中缓存直接返回。

try {
            // 1. 读取配置
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            // 2. 创建SqlSessionFactory工厂
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            // 3. 获取sqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);
            // 4. 获取Mapper
            TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
            // 5. 执行接口方法
            for(long i = 1; i < 3; i++){
                TTestUser userInfo = userMapper.selectByPrimaryKey(2001L);
                System.out.println("userInfo = " + JSONUtil.toJsonStr(userInfo));
            }
            // 6. 提交事物
            sqlSession.commit();
            // 7. 关闭资源
            sqlSession.close();
            inputStream.close();
        } catch (Exception e){
            log.error(e.getMessage(), e);
        }

在这个栗子中,虽然我们执行了两次sql,但是参数和sql语句都是一样的,所以一次的查询结果会被缓存,第二次查询的时候直接从缓存中去取。


CachingExecutor

7. 总结

这篇文章介绍了Executor执行器的接口,继承关系,三种执行器,以及用了几个例子来介绍他们的区别,想必大家对Executor已经有了很全面的了解了吧。希望大家能够在评论区评论指正,一起进步。

你可能感兴趣的:(MyBatis原理系列(四)-手把手带你了解MyBatis的Executor执行器)