浅析Mybatis的Executor原理

当我们读取完配置文件,将我们的Mybatis配置成我们想要的要的样子之后,我们就要使用他对数据库进行一系列操作(增删改查)。而SqlSession这个看似无所不能的操作达人,其实是找了代练的。SqlSession将一切数据库具体操作委托给背后的Executor。

Executor 类结构图

在这里插入图片描述

我们先认识一下最大的大佬Executor;

  int update(MappedStatement ms, Object parameter) throws SQLException;

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

  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

  List<BatchResult> 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);

其中BaseExecutor类中处理了诸如数据库连接,数据库关闭,事务回滚,事务提交等一系列繁杂的事情;

// 创建事物
protected BaseExecutor(Configuration configuration, Transaction transaction) {
   this.transaction = transaction;
   this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
   this.localCache = new PerpetualCache("LocalCache");
   this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
   this.closed = false;
   this.configuration = configuration;
   this.wrapper = this;
 }

// 事物关闭
@Override
  public void close(boolean forceRollback) {
  try {
     try {
       rollback(forceRollback);
     } finally {
       if (transaction != null) {
         transaction.close();
       }
     }
   } catch (SQLException e) {
     // Ignore.  There's nothing that can be done at this point.
     log.warn("Unexpected exception on closing transaction.  Cause: " + e);
   } finally {
     transaction = null;
     deferredLoads = null;
     localCache = null;
     localOutputParameterCache = null;
     closed = true;
   }
 }

// 数据更新操作 
@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();
   return doUpdate(ms, parameter);
 }

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

这里插播一个设计模式的知识点;我们现弄清楚Executor的设计模式,方便我们理解mybatis的Executor;

Executor 整体用的是模板模式;其中的BaseExecutor就采用了模板方法模式,它实现了大部分的SQL执行逻辑,然后把以下几个方法交给子类定制化完成:

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

protected abstract ListdoFlushStatements(boolean isRollback) throws SQLException;

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

这里我们回来继续看看模板具体实现三个大类的作用;

SimpleExecutor : 应对简单处理,每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。(可以是Statement或PrepareStatement对象),俗称打完就跑,有始有终。

BatchExecutor:善于批量处理执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理的;

ReuseExecutor :一旦出拳,不叫停不会停。执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。

1、SimpleExecutor

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

 @Override
 public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
   Statement stmt = null;
   try {
     Configuration configuration = ms.getConfiguration();
     StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
     stmt = prepareStatement(handler, ms.getStatementLog());
     return handler.update(stmt);
   } finally {
     closeStatement(stmt);
   }
 }

 @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();
     StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
     stmt = prepareStatement(handler, ms.getStatementLog());
     return handler.<E>query(stmt, resultHandler);
   } finally {
     closeStatement(stmt);
   }
 }

 @Override
 protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
   Configuration configuration = ms.getConfiguration();
   StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
   Statement stmt = prepareStatement(handler, ms.getStatementLog());
   return handler.<E>queryCursor(stmt);
 }

 @Override
 public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
   return Collections.emptyList();
 }

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
   Statement stmt;
   Connection connection = getConnection(statementLog);
   stmt = handler.prepare(connection, transaction.getTimeout());
   handler.parameterize(stmt);
   return stmt;
 }

在这里插入图片描述
impleExecutor 较为简单,无论是query 还是 update statement随建随关。基本步骤就是获取配置,创建相应的StatementHandler,实例化Statement ,使用StatementHandler执行数据库操作。

2、ReuseExecutor

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

 @Override
 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
   Configuration configuration = ms.getConfiguration();
   StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
   Statement stmt = prepareStatement(handler, ms.getStatementLog());
   return handler.<E>query(stmt, resultHandler);
 }

 @Override
 protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
   Configuration configuration = ms.getConfiguration();
   StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
   Statement stmt = prepareStatement(handler, ms.getStatementLog());
   return handler.<E>queryCursor(stmt);
 }

 @Override
 public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
   for (Statement stmt : statementMap.values()) {
     closeStatement(stmt);
   }
   statementMap.clear();
   return Collections.emptyList();
 }

ReuseExecutor 和 SimpleExecutor 的操作步骤基本相似,最大的区别在于前者维护者一个statementMap 用于记录并没有销毁的statement,当相同的sql语句被执行时,会使用同一个已经创建好的statement,如果sql第一次执行,那么就创建一个新的statement,并放入statementMap中。

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  BoundSql boundSql = handler.getBoundSql();
  String sql = boundSql.getSql();
  //有则直接使用
  if (hasStatementFor(sql)) {
     //sql 作为 key,statement 作为value
    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;
}

当然ReuseExecutor 中的statement 不可能一直不关闭,这就需要我们在恰当的时候,通过调用doFlushStatements方法手动对其进行关闭。

@Override
public List<BatchResult> doFlushStatements(boolean isRollback) {
  for (Statement stmt : statementMap.values()) {
    closeStatement(stmt);
  }
  statementMap.clear();
  return Collections.emptyList();
}

3、BatchExecutor

BatchExecutor 是几个徒弟之中拳法最为厉害的,他可以进行批量操作,让我们看看他是如何以一敌百的。

让我们先来看看BatchExecutor 是处理每一条sql的。

在这里插入图片描述
BatchExecutor 在执行批量更新时,根据sql语句创建Statement桶,相同的sql使用相同statement,并将所有的statement添加到statementList中。

在执行批量更新的过程中,并不执行sql,只是拼接不同的statement。直至commit时,拿到statementList逐一进行sql执行。

//维护一个Statement列表 用于缓存拼接好的statement
private final List<Statement> statementList = new ArrayList<>();
//维护一个Result 列表,用于记录处理结果
private final List<BatchResult> batchResultList = new ArrayList<>();
//当前保存的sql,也就是上一次执行的sql
private String currentSql;
//当前保存的MappedStatement,也就是上一次执行的MappedStatement
private MappedStatement currentStatement;

@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();
    //要执行的sql
    final String sql = boundSql.getSql();
    final Statement stmt;
    //如果和上次执行sql相同,同时MappedStatement也必须相同,取上次执行statement,并传入此次参数
    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 {
      //如果和上次执行sql不同,创建新的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));
    }
    //在handler内部执行addBatch()方法
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

需要注意:

sql.equals(currentSql)和statementList.get(last),充分说明了其有序逻辑:AABB,将生成2个Statement对象;AABBAA,将生成3个Statement对象,而不是2个。因为,只要sql有变化,将导致生成新的Statement对象。也就是说,现在执行的sql,只和上一次执行的sql进行对比,是否相同。如果相同使用上一个statement,如果不同再次创建新的statement。

那么什么时候执行sql 呢?

答案是:在执行commit的时候。在执行commit、rollback等动作前,都将会执行flushStatements()方法,将Statement对象逐一关闭。

看看BatchExecutor .doFlushStatements方法。

@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
  try {
    List<BatchResult> results = new ArrayList<>();
    if (isRollback) {
      return Collections.emptyList();
    }
    //遍历statementList,执行sql
    for (int i = 0, n = statementList.size(); i < n; i++) {
      Statement stmt = statementList.get(i);
      applyTransactionTimeout(stmt);
      BatchResult batchResult = batchResultList.get(i);
      try {
        // 执行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())) { 
          for (Object parameter : parameterObjects) {
            keyGenerator.processAfter(this, ms, stmt, parameter);
          }
        }
        // 关闭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);
      }
      //将每次结果添加到resultList中。
      results.add(batchResult);
    }
    return results;
  } finally {
    for (Statement stmt : statementList) {
      closeStatement(stmt);
    }
    currentSql = null;
    statementList.clear();
    batchResultList.clear();
  }
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值