Mybatis源码之路

ReuseExecutor

2020-09-10  本文已影响0人  93张先生

ReuseExecutor

在传统的 JDBC 编程中,重用 Statement 对象是常用的一种优化手段,该优化手段可以减少 SQL 预编译的开销以及创建和销毁 Statement 对象的开销,从而提高性能。ReuseExecutor 提供了 Statement 重用功能,ReuseExecutor 中通过 statementMap 字段 缓存使用过的 Statement 对象,key 是 SQL 语句,value 是 SQL 对应的 Statement 对象。

ReuseExecutor.doQuery()、 doQueryCursor()、 doUpdate() 方法的实现与 SimpleExecutor 对应方法的实现一样,区别在于其中调用的 prepareStatement() 方法, SimpleExecutor 每次都会通过 JDBC Connection 创建新的 Statement 对象,而 ReuseExecutor 会先尝试重用 StatementMap 中缓存的 Statement 对象。

public class ReuseExecutor extends BaseExecutor {

  private final Map<String, Statement> statementMap = new HashMap<>();

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

  @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.query(stmt, resultHandler);
  }

  // TODO: 2020/9/3 游标缓存,相同 SQL 多次查询,会导致前一个查询的结果集 DefaultCursor 被关闭,344 page
  @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.queryCursor(stmt);
  }

  // TODO: 2020/9/3  提交或回滚

  /**
   *
   * 当事务提交或回滚、连接关闭时,都需要关闭这些缓存的 Statement 对象。前面介绍
   * BaseExecutor.commit()、 rollback() 和 close()方法时提到,其中都会调用 doFlushStatements()方法,所以在该方法 实现关 Statement 对象的逻辑非常合适。
   * @param isRollback
   * @return
   */
  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) {
    // 边历 statementMap 合并关闭其中的 Statement 对象
    for (Statement stmt : statementMap.values()) {
      // 关闭 Statement 对象
      closeStatement(stmt);
    }
    // 清空缓存
    statementMap.clear();
    // 返回空集合
    return Collections.emptyList();
  }

  /**
   * 通过 statementMap 缓存获取 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();  // 可以执行的包含 "?" 占位符的 sql
    // 缓存中是否存在 Statement 对象
    if (hasStatementFor(sql)) {
      stmt = getStatement(sql);
      // 修改超时时间
      applyTransactionTimeout(stmt);
    } else {
      Connection connection = getConnection(statementLog);
      // 创建 Statement 对象
      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);
  }

}
上一篇 下一篇

猜你喜欢

热点阅读