Mybatis源码解析-Executor执行器

215 阅读12分钟

上一篇中介绍了SqlSession如何去获得Mapper代理对象并且执行Sql语句的过程。并且提到SqlSession中的增删改查方法也都是通过Executor来实现的,这一篇也是讲解一下Executor的具体实现

这是Executor的继承结构

image.png

首先看顶层接口Executor

/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.executor;

import java.sql.SQLException;
import java.util.List;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;

/**
 * @author Clinton Begin
 */
public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;
  /**
   * 负责处理新增,修改和删除操作
   * @param ms seeMappedStatement 对象 作用:对操作数据库存储封装,包括 sql 语句、输入输出参数
   * @see MappedStatement
   * @param parameter 请求参数。修改新增操作时的vo信息
   * */
  int update(MappedStatement ms, Object parameter) throws SQLException;
  /**
   * @param ms seeMappedStatement 对象 作用:对操作数据库存储封装,包括 sql 语句、输入输出参数
   * @param parameter 请求参数 查询时条件信息
   * @param rowBounds 逻辑分页参数
   * @param resultHandler 处理返回结果
   * @param cacheKey 缓存key
   * @param boundSql sql信息
   * */
  <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

BaseExecutor作为Executor的抽象实现类。并不是直接去进行操作的。而是交给其下面三个子类执行器SimpleExecutor,ReuseExecutor,BatchExecutor来进行操作。BaseExecutor实现Executor里面的所有方法。把部分公共的实现给做好。在吧调用不同执行器的代码提取成抽象方法 给子类进行实现。这里运用到的设计模式是模版模式。

update

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

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

这里由三个执行器来实现doUpdate方法

query

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 这里其实就是拿sql语句,BoundSql是对动态SQL解析生成的SQL语句和参数映射信息的封装
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 创建CacheKey用于缓存【这里面有很多因素,用来决定key是否相同】
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 具体执行查询的地方
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

创建缓存

@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    // mapper的唯一id丢进去 命名空间+方法名
    cacheKey.update(ms.getId());
    // offset丢进去【查询结果的偏移量】
    cacheKey.update(rowBounds.getOffset());
    // limit丢进去【查询的条数】
    cacheKey.update(rowBounds.getLimit());
    // 完整的SQL语句丢进去
    cacheKey.update(boundSql.getSql());
    // 拿到入参映射
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    // 拿到类型处理器注册中心 顾名思义处理类型
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    // 遍历入参映射
    for (ParameterMapping parameterMapping : parameterMappings) {
        if (parameterMapping.getMode() != ParameterMode.OUT) {
            Object value;
            // 入参属性名 有的有指定@Param的嘛
            String propertyName = parameterMapping.getProperty();
            if (boundSql.hasAdditionalParameter(propertyName)) {
                value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
                // 入参为空 值也为空
                value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                // 从类型处理器注册中心 拿到与入参实际类型匹配的类型处理器
                value = parameterObject;
            } else {
                // 拿到一个metaObject对象
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                // 拿出里面的propertyName的值
                value = metaObject.getValue(propertyName);
            }
            // 再把入参值 丢进缓存
            cacheKey.update(value);
        }
    }
    if (configuration.getEnvironment() != null) {
        // issue #176
        // 判断有没有环境属性,有的话把环境id也丢进缓存里
        cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
}

最后调用的查询

@SuppressWarnings("unchecked")
@Override
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) {
            // issue #482
            clearLocalCache();
        }
    }
    return list;
}

不走缓存时的调用方法doQuery

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

Executo所定义的抽象方法都由不同的子类执行器来实现

  // 定义的四个抽象方法,在去掉 do 前缀的相应方法中被调用
  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的子类实现

SimpleExecutor

简单执行器。根据要求执行对应的sql。拼接好sql后直接交给StatementHandler  去执行。每次查询完成之后都会释放Statement

/**
 * Copyright 2009-2019 the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.ibatis.executor;

import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.List;

/**
 * @author Clinton Begin
 */
public class SimpleExecutor extends BaseExecutor {

    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 configuration = ms.getConfiguration();
            // 拿到一个StatementHandler对象,具体是RoutingStatementHandler类型,里面还注册了插件
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            // 解析成一个prepareStatement对象,并设置参数等
            stmt = prepareStatement(handler, ms.getStatementLog());
            // 用StatementHandler来处理查询,默认是PreparedStatementHandler
            return handler.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());
        Cursor<E> cursor = handler.queryCursor(stmt);
        stmt.closeOnCompletion();
        return cursor;
    }

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

    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        // 获取JDBC连接对象
        Connection connection = getConnection(statementLog);
        // 创建Statement对象
        stmt = handler.prepare(connection, transaction.getTimeout());
        // 继续调用StatementHandler的方法设置参数
        handler.parameterize(stmt);
        return stmt;
    }

}

测试

/**
 * 简单执行器
 * 每次都会创建一个新的处理器(PrepareStatement)
 * */
@Test
public void SimpleExecutorTest() throws SQLException {
    SimpleExecutor executor = new SimpleExecutor(configuration, jdbcTransaction);
    MappedStatement queryMs = configuration.getMappedStatement("com.example.smallwhite.mybatis.dao.BabyimageDao.queryById");
    //参数 1.执行的MappedStatement 2.参数信息 3.分页信息
    executor.doQuery(queryMs, "0Q55mn3P9PcbAMa73V361", RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, queryMs.getBoundSql("0Q55mn3P9PcbAMa73V361"));
    executor.doQuery(queryMs, "0Q55mn3P9PcbAMa73V361", RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, queryMs.getBoundSql("0Q55mn3P9PcbAMa73V361"));
    MappedStatement updateMs = configuration.getMappedStatement("com.example.smallwhite.mybatis.dao.BabyimageDao.update");
    //参数 1.执行的MappedStatement 2.参数信息 3.分页信息
    executor.doUpdate(updateMs, new Babyimage("0Q55mn3P9PcbAMa73V361", "q", "q", new Timestamp(System.currentTimeMillis()),"11"));
    executor.doUpdate(updateMs, new Babyimage("0Q55mn3P9PcbAMa73V361", "q", "q", new Timestamp(System.currentTimeMillis()),"11"));
}

结果

==>  Preparing: select id, name, url, ts from babyimage where id = ? 
==> Parameters: 0Q55mn3P9PcbAMa73V361(String)
<==    Columns: id, name, url, ts
<==        Row: 0Q55mn3P9PcbAMa73V361, q, q, 2021-08-17 09:56:19.228000
<==      Total: 1
==>  Preparing: select id, name, url, ts from babyimage where id = ? 
==> Parameters: 0Q55mn3P9PcbAMa73V361(String)
<==    Columns: id, name, url, ts
<==        Row: 0Q55mn3P9PcbAMa73V361, q, q, 2021-08-17 09:56:19.228000
<==      Total: 1
==>  Preparing: update babyimage SET name = ?, url = ?, ts = ? where id = ? 
==> Parameters: q(String), q(String), 2021-08-17 20:03:21.605(Timestamp), 0Q55mn3P9PcbAMa73V361(String)
<==    Updates: 1
==>  Preparing: update babyimage SET name = ?, url = ?, ts = ? where id = ? 
==> Parameters: q(String), q(String), 2021-08-17 20:03:21.633(Timestamp), 0Q55mn3P9PcbAMa73V361(String)
<==    Updates: 1

ReuseExecutor

可重用的执行器,重用的对象是Statement,也就是说该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能。内部的实现是通过一个HashMap来维护Statement对象的。由于当前Map只在该session中有效,所以使用完成后记得调用flushStatements来清除Map。调用实现的四个抽象方法时会调用 prepareStatement() 

/**
 *    Copyright 2009-2020 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.executor;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;

/**
 * @author Clinton Begin
 */
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);
  }

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

  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) {
    //逐一关闭连接 并且清空statementMap
    for (Statement stmt : statementMap.values()) {
      closeStatement(stmt);
    }
    statementMap.clear();
    return Collections.emptyList();
  }

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //在statementMap获取 sql相同的对象 拿到Statement来操作sql 节省开销
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    if (hasStatementFor(sql)) {
      stmt = getStatement(sql);
      applyTransactionTimeout(stmt);
    } else {
      Connection connection = getConnection(statementLog);
      //不存在得话重新创建 并存储到statementMap中
      stmt = handler.prepare(connection, transaction.getTimeout());
      putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
  }

  private boolean hasStatementFor(String sql) {
    try {
      Statement statement = statementMap.get(sql);
      return statement != null && !statement.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);
  }

}

测试

/**
 * 可重用执行器
 * 相同的sql只进行一次预处理
 * */
@Test
public void ReuseExecutorTest() throws SQLException {
    ReuseExecutor executor = new ReuseExecutor(configuration, jdbcTransaction);
    MappedStatement ms = configuration.getMappedStatement("com.example.smallwhite.mybatis.dao.BabyimageDao.queryById");
    //参数 1.执行的MappedStatement 2.参数信息 3.分页信息
    List<Object> objects = executor.doQuery(ms, "0Q55mn3P9PcbAMa73V361", RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql("0Q55mn3P9PcbAMa73V361"));
    executor.doQuery(ms, "0Q55mn3P9PcbAMa73V361", RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql("0Q55mn3P9PcbAMa73V361"));
    System.out.println(objects.get(0));
}

结果

==>  Preparing: select id, name, url, ts from babyimage where id = ? 
==> Parameters: 0Q55mn3P9PcbAMa73V361(String)
<==    Columns: id, name, url, ts
<==        Row: 0Q55mn3P9PcbAMa73V361, q, q, 2021-08-17 12:03:21.633000
<==      Total: 1
==> Parameters: 0Q55mn3P9PcbAMa73V361(String)
<==    Columns: id, name, url, ts
<==        Row: 0Q55mn3P9PcbAMa73V361, q, q, 2021-08-17 12:03:21.633000
<==      Total: 1
==>  Preparing: update babyimage SET name = ?, url = ?, ts = ? where id = ? 
==> Parameters: q(String), q(String), 2021-08-17 20:05:25.677(Timestamp), 0Q55mn3P9PcbAMa73V361(String)
<==    Updates: 1
==> Parameters: q(String), q(String), 2021-08-17 20:05:25.706(Timestamp), 0Q55mn3P9PcbAMa73V361(String)
<==    Updates: 1

BatchExecutor

通过批量操作来优化性能。通常需要注意的是批量更新操作,不会自动提交事务。需要手动commit或者调用doFlushStatements。由于内部有缓存的实现,使用完成后记得调用flushStatements来清除缓存。

/**
 *    Copyright 2009-2020 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.executor;

import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;

/**
 * @author Jeff Butler
 */
public class BatchExecutor extends BaseExecutor {

  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;

  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;
    //判断 sql 和 configuration是否一致
    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);
      //一致的话把参数追加到parameterObjects后面进行批量操作
      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);
      //创建第一条批量执行 信息 包括sql 参数信息
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
    //将SQL命令添加到批处理执行列表中 但是并不会执行 直到调用commit或者 doFlushStatements方法后执行数据库操作
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException {
    Statement stmt = null;
    try {
      flushStatements();
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

  @Override
  protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
    flushStatements();
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    Connection connection = getConnection(ms.getStatementLog());
    Statement stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    Cursor<E> cursor = handler.queryCursor(stmt);
    stmt.closeOnCompletion();
    return cursor;
  }

  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    try {
      List<BatchResult> results = new ArrayList<>();
      if (isRollback) {
        return Collections.emptyList();
      }
      for (int i = 0, n = statementList.size(); i < n; i++) {
        Statement stmt = statementList.get(i);
        applyTransactionTimeout(stmt);
        BatchResult batchResult = batchResultList.get(i);
        try {
          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
          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);
        }
        results.add(batchResult);
      }
      return results;
    } finally {
      for (Statement stmt : statementList) {
        closeStatement(stmt);
      }
      currentSql = null;
      statementList.clear();
      batchResultList.clear();
    }
  }

}

测试

/**
 * 批处理执行器
 * 只针对新增,删除,修改操作
 * */
@Test
public void BatchExecutorTest() throws SQLException {
    BatchExecutor executor = new BatchExecutor(configuration, jdbcTransaction);
    MappedStatement ms = configuration.getMappedStatement("com.example.smallwhite.mybatis.dao.BabyimageDao.update");
    //参数 1.执行的MappedStatement 2.参数信息 3.分页信息
    int i = executor.doUpdate(ms, new Babyimage("0Q55mn3P9PcbAMa73V361", "q", "q", new Timestamp(System.currentTimeMillis()),"11"));
    executor.doUpdate(ms, new Babyimage("0Q55mn3P9PcbAMa73V361", "q", "q", new Timestamp(System.currentTimeMillis()),"11"));
    //通过doFlushStatements 或者 commit来提交事务
    //        executor.doFlushStatements(false);
    executor.commit(false);
    executor.doUpdate(ms, new Babyimage("0Q55mn3P9PcbAMa73V361", "q", "q", new Timestamp(System.currentTimeMillis()),"11"));

    System.out.println(i);
}

结果

==>  Preparing: update babyimage SET name = ?, url = ?, ts = ? where id = ? 
==> Parameters: q(String), q(String), 2021-08-17 20:34:20.352(Timestamp), 0Q55mn3P9PcbAMa73V361(String)
==> Parameters: q(String), q(String), 2021-08-17 20:34:20.411(Timestamp), 0Q55mn3P9PcbAMa73V361(String)
==>  Preparing: update babyimage SET name = ?, url = ?, ts = ? where id = ? 
==> Parameters: q(String), q(String), 2021-08-17 20:34:20.415(Timestamp), 0Q55mn3P9PcbAMa73V361(String)
-2147482646

CachingExecutor

CachingExecutor是二级缓存执行器。通过装饰器模式加载到执行器中。可以在xml中配置启用。Executor执行时会先调用CachingExecutor查找缓存中是否存在。不存在就去数据库查找

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 拿到查询语句 其实简单理解就是mapper.xml或者注解里的sql解析为占位符形式
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 创建缓存的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 {
    // 拿MappedStatement维护的二级缓存对象
    Cache cache = ms.getCache();
    if (cache != null) {
        // 判断是否需要刷新二级缓存
        // 判断:如果有缓存,且<select|update|delete|insert>标签中的flushCache属性开启了,就清空缓存
        // 因为<select>标签的flushCache默认是false,所以不会清理
         // 而<update|delete|insert>标签的flushCache默认是true,所以会清理缓存
        flushCacheIfRequired(ms);
        if (ms.isUseCache() && resultHandler == null) {
            ensureNoOutParams(ms, boundSql);
            // 从MappedStatement对象对应的二级缓存中获取数据
            @SuppressWarnings("unchecked")
            List<E> list = (List<E>) tcm.getObject(cache, key);
            if (list == null) {
                // 数据不存在则调用装饰查询
                list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                // 将数据丢入到MappedStatement维护的二级缓存对象中去
                tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
        }
    }
    // 拿不到缓存数据 就进行查询
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}