Mybatis源码分析六之Executor

一、Executor

前文分析了SqlSession,总的说来就是实现了数据库操作的增删改查方法,但是具体的调用都是通过Executor来实现的,所以分析Executor对了解Mybatis是怎么运行极其重要。
Executor是SqlSession的构造参数之一,他的实例化过程是调用Configuration的newExecutor方法返回的Executor executor = configuration.newExecutor(tx, execType),tx是事务工厂(已经介绍过),execType是执行器类型(SIMPLE、REUSE、BATCH):

  • SIMPLE:默认值,会为每个语句创建新的预处理语句
  • REUSE:会重用预处理语句
  • BATCH:会批量执行所有的更新语句

具体到源码做进一步分析。
newExecutor(tx, execType):

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

所以,默认会返回CachingExecutor对象。

二、CachingExecutor

顾名思义就是带有缓存器的执行器,也就是查询会缓存起来,增加、删除、修改时会清除缓存

  public CachingExecutor(Executor delegate) {
    //这里有个委派执行器
    this.delegate = delegate;
    //委派执行器又会包装当前的缓存执行器
    delegate.setExecutorWrapper(this);
  }

我们先分析其query查询方法:

  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //获取具体的sql绑定对象
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //得到缓存的key值
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    //继续调用查询
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
  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);
  }
  • CacheKey :缓存的主键,分析createCacheKey可知,键值是由查询名称、offset、limit、sql语句、参数值、以及环境id生成。
  • TransactionalCacheManager:具体缓存值存放是通过这个来实现的,调用其getObject和putObject方法,底层的存储是利用HashMap来实现,如果执行增删改方法是就会调用其clear方法清除缓存。

说到缓存,就存在一级缓存和二级缓存,一级缓存是SqlSession级别的缓存,只有在同一个session实例下才有效,不同的session不共享缓存,二级缓存是SqlSessionFactory级别,可以不同session共享缓存(mybatis缓存实现将单独分析)。

二、BaseExecutor

经过上述分析可知,具体的操作又是通过委托对象delegate来实现,也就是具体的SIMPLE、REUSE、BATCH等执行器,具体的执行语句是通过他们的父类BaseExecutor实现,如下代码段是其构造方法。

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

继续以查询为例分析:

  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) {
      //如果缓存命中,处理存储过程中的参数输出
        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) {
        // 如果配置的statementType是STATEMENT那么也要清除缓存
        clearLocalCache();
      }
    }
    return list;
  }

queryFromDatabase:从数据库中查询

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

doQuery:

  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();
      //获取执行的handler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //获取statement对象
      stmt = prepareStatement(handler, ms.getStatementLog());
      //交给handler执行
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

认真分析,可以发现最后都是交给StatementHandler去执行操作,这个后文再进行分析,本文就不继续展开。

三、SIMPLE、REUSE、BATCH执行器的区别

接下来,我们就从源码来分析这三个执行器的区别,SimpleExecutor中就简单的各种do**方法,ReuseExecutor中多了一个Map<String, Statement> statementMap集合属性,REUSE与SIMPLE的区别在于复用预处理语句,他们之间的不同点在于prepareStatement方法的实现。
ReuseExecutor中的prepareStatement方法。

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    if (hasStatementFor(sql)) {
      stmt = getStatement(sql);
      applyTransactionTimeout(stmt);
    } else {
      Connection connection = getConnection(statementLog);
      stmt = handler.prepare(connection, transaction.getTimeout());
      putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
  }

可以看出不同点在于REUSE会把sql语句和statement对象用HashMap保存起来,下次执行同样的sql语句的时候就会直接重用之前的Statement对象。

我们再看看BatchExecutor对象,这个对象有五个属性值,这个对象会批量的执行更新语句。

  public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;
  private final List<Statement> statementList = new ArrayList<>();
  private final List<BatchResult> batchResultList = new ArrayList<>();
  private String currentSql;
  private MappedStatement currentStatement;

SIMPLE和RFEUSE的do**方法,就是一个标准的模板方法设计先获取Configuration、再获取StatementHandler对象,然后实例化Statement对象,最后通过handler执行语句。

BatchExecutor中的doUpdate方法:

  public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
    final Configuration configuration = ms.getConfiguration();
    //也是获取statement对象
    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;
    //先判断当前执行SQL语句和上一条语句是否一样
    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
      currentSql = sql;
      currentStatement = ms;
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
    //这里就是不断的添加需要执行的sql语句
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

我们知道调用addBatch方法后还需要调用executeBatch方法才行,但是这里并没有,所以这个操作就需要在commit方法中去实现,在父类BaseExcutor的commit方法会调用一个doFlushStatements方法,BATCH重写了这个方法,这个方法中就调用stmt.executeBatch(),所以这三类执行器的区别也不难理解。

四、总结

本文简单介绍了Executor,以及SIMPLE、REUSE、BATCH三类执行器的区别,SqlSession拿到具体的执行语句对象,然后交给Executor来执行,执行器又处理好缓存以及是否重用以及批量更新等操作。然后又把具体的执行工作交给了StatemHandler来执行,接下来我们先分析一下Mybatis的缓存机制,然后再继续往后分析。

以上,有任何不对的地方,请指正,敬请谅解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜鸟+1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值