快捷搜索:  汽车  科技

clearcase不能导入项目(万字讲解Executor处理过程)

clearcase不能导入项目(万字讲解Executor处理过程)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 对象 StatementHandle

前面的时候,我整理了一套mybatis的缓存的文章,通过源码,详细的讲解了关于MyBatis一二级缓存的相关知识点,需要的朋友可以看这里

我就不信2W字把源码拆的这么碎,你还不明白mybatis缓存

在讲解缓存的过程中,我提到一个问题

一二级缓存都是在Executor中进行的

clearcase不能导入项目(万字讲解Executor处理过程)(1)

但是当时因为篇幅的原因,我只展示BasicExecutor的相关源码操作,其余的几个Executor没有进行完整的介绍,这里和上篇进行衔接,大家可以先去看上一篇,然后再来看这本书,希望对大家能够有所帮助

好啦,话不多说,我们来看一下

SimpleExecutor

org.apache.ibatis.executor.SimpleExecutor:继承 BaseExecutor 抽象类,简单的 Executor 实现类(默认使用

  • 每次对数据库的操作,都会创建对应的Statement对象
  • 执行完成后,关闭该Statement对象

代码如下:

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 对象 StatementHandler handler = configuration.newStatementHandler(this ms parameter RowBounds.DEFAULT null null); // 初始化 Statement 对象 stmt = prepareStatement(handler ms.getStatementLog()); // 通过 StatementHandler 执行写操作 return handler.update(stmt); } finally { // 关闭 Statement 对象 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 对象 StatementHandler handler = configuration.newStatementHandler(wrapper ms parameter rowBounds resultHandler boundSql); // 初始化 Statement 对象 stmt = prepareStatement(handler ms.getStatementLog()); // 通过 StatementHandler 执行读操作 return handler.query(stmt resultHandler); } finally { // 关闭 Statement 对象 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; // 获得 Connection 对象,如果开启了 Debug 模式,则返回的是一个代理对象 Connection connection = getConnection(statementLog); // 创建 Statement 或 PrepareStatement 对象 stmt = handler.prepare(connection transaction.getTimeout()); // 往 Statement 中设置 SQL 语句上的参数,例如 PrepareStatement 的 ? 占位符 handler.parameterize(stmt); return stmt; } }

我们看到这些方法的实现,其中的步骤差不多都是一样的

  1. 获取Configuration全局配置对象
  2. 通过上面全局配置对象的newStatementHandler方法,创建RoutingStatementHandler对象,采用了装饰器模式,根据配置的StatementType创建对应的对象,默认为PreparedStatementHandler对象,进入BaseStatementHandler的构造方法你会发现有几个重要的步骤,在后续会讲到然后使用插件链对该对象进行应用,方法如下所示:

// Configuration.java

public StatementHandler newStatementHandler(Executor executor MappedStatement mappedStatement Object parameterObject RowBounds rowBounds ResultHandler resultHandler BoundSql boundSql) {

/* * 创建 RoutingStatementHandler 路由对象

* 其中根据 StatementType 创建对应类型的 Statement 对象,默认为 PREPARED

* 执行的方法都会路由到该对象 */

StatementHandler statementHandler = new RoutingStatementHandler(executor mappedStatement parameterObject rowBounds resultHandler boundSql);

// 将 Configuration 全局配置中的所有插件应用在 StatementHandler 上面

statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);

return statementHandler; }

  1. 调用prepareStatement方法初始化Statement对象从事务中获取一个Connection数据库连接,如果开启了Debug模式,则会为该Connection创建一个动态代理对象的实例,用于打印Debug日志通过上面第2步创建的StatementHandler对象创建一个Statement对象(默认为PrepareStatement),还会进行一些准备工作,例如:如果配置了KeyGenerator(设置主键),则会设置需要返回相应自增键,在后续会讲到往Statement对象中设置SQL的参数,例如PrepareStatement的?占位符,实际上是通过DefaultParameterHandler设置占位符参数,
  2. 通过StatementHandler对Statement进行数据库的操作,如果是查询操作则会通过DefaultResultSetHandler进行参数映射(非常复杂,后续逐步分析)
ReuseExecutor

org.apache.ibatis.executor.ReuseExecutor:继承 BaseExecutor 抽象类,可重用的 Executor 实现类

  • 每次对数据库的操作,优先从当前会话的缓存中获取对应的Statement对象,如果不存在,才进行创建,创建好了会放入缓存中
  • 数据库操作执行完成后,不关闭该Statement对象
  • 其它的和SimpleExecutor是一致的

我们来看看他的prepareStatement方法就好了:

private Statement prepareStatement(StatementHandler handler Log statementLog) throws SQLException { Statement stmt; BoundSql boundSql = handler.getBoundSql(); String sql = boundSql.getSql(); /* * 根据需要执行的 SQL 语句判断 是否已有对应的 Statement 并且连接未关闭 */ if (hasStatementFor(sql)) { // 从缓存中获得 Statement 对象 stmt = getStatement(sql); // 重新设置事务超时时间 applyTransactionTimeout(stmt); } else { // 获得 Connection 对象 Connection connection = getConnection(statementLog); // 初始化 Statement 对象 stmt = handler.prepare(connection transaction.getTimeout()); // 将 Statement 添加到缓存中,key 值为 当前执行的 SQL 语句 putStatement(sql stmt); } // 往 Statement 中设置 SQL 语句上的参数,例如 PrepareStatement 的 ? 占位符 handler.parameterize(stmt); return stmt; }

在创建Statement对象前,会根据本次查询的SQL从本地的Map<String Statement> statementMap获取到对应的Statement对象

  1. 如果缓存命中,并且该对象的连接未关闭,那么重新设置当前事务的超时时间
  2. 如果缓存未命中,则执行和SimpleExecutor中的prepareStatement方法相同逻辑创建一个Statement对象并放入statementMap缓存中
BatchExecutor

org.apache.ibatis.executor.BatchExecutor:继承 BaseExecutor 抽象类,支持批量执行的 Executor 实现类

  • 我们在执行数据库的更新操作时,可以通过Statement的addBatch()方法将数据库操作添加到批处理中,等待调用Statement的executeBatch()方法进行批处理
  • BatchExecutor维护了多个Statement对象,一个对象对应一个SQL(sql和MappedStatement对象都相等),每个Statement对象对应多个数据库操作(同一个sql多种入参),就像苹果蓝里装了很多苹果,番茄蓝里装了很多番茄,最后,再统一倒进仓库

由于JDBC不支持数据库查询的批处理,所以这里就不展示它数据库查询的实现方法,和SimpleExecutor一致,我们来看看其他的方法

构造方法

public class BatchExecutor extends BaseExecutor { public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE 1002; /** * Statement 数组 */ private final List<Statement> statementList = new ArrayList<>(); /** * BatchResult 数组 * * 每一个 BatchResult 元素,对应 {@link #statementList} 集合中的一个 Statement 元素 */ private final List<BatchResult> batchResultList = new ArrayList<>(); /** * 上一次添加至批处理的 Statement 对象对应的SQL */ private String currentSql; /** * 上一次添加至批处理的 Statement 对象对应的 MappedStatement 对象 */ private MappedStatement currentStatement; public BatchExecutor(Configuration configuration Transaction transaction) { super(configuration transaction); } }

  • statementList属性:维护多个Statement对象
  • batchResultList属性:维护多个BatchResult对象,每个对象对应上面的一个Statement对象,每个BatchResult对象包含同一个SQL和其每一次操作的入参
  • currentSql属性:上一次添加至批处理的Statement对象对应的SQL
  • currentStatement属性:上一次添加至批处理的Statement对象对应的MappedStatement对象
BatchResult

org.apache.ibatis.executor.BatchResult:相同SQL(sql和MappedStatement对象都相等)聚合的结果,包含了同一个SQL每一次操作的入参,代码如下:

public class BatchResult { /** * MappedStatement 对象 */ private final MappedStatement mappedStatement; /** * SQL */ private final String sql; /** * 参数对象集合 * * 每一个元素,对应一次操作的参数 */ private final List<Object> parameterObjects; /** * 更新数量集合 * * 每一个元素,对应一次操作的更新数量 */ private int[] updateCounts; public BatchResult(MappedStatement mappedStatement String sql) { super(); this.mappedStatement = mappedStatement; this.sql = sql; this.parameterObjects = new ArrayList<>(); } public BatchResult(MappedStatement mappedStatement String sql Object parameterObject) { this(mappedStatement sql); addParameterObject(parameterObject); } public void addParameterObject(Object parameterObject) { this.parameterObjects.add(parameterObject); } } doUpdate方法

更新数据库的操作,添加至批处理,需要调用doFlushStatements执行批处理,代码如下:

@Override public int doUpdate(MappedStatement ms Object parameterObject) throws SQLException { final Configuration configuration = ms.getConfiguration(); // <1> 创建 StatementHandler 对象 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; // <2> 如果和上一次添加至批处理 Statement 对象对应的 currentSql 和 currentStatement 都一致,则聚合到 BatchResult 中 if (sql.equals(currentSql) && ms.equals(currentStatement)) { // <2.1> 获取上一次添加至批处理 Statement 对象 int last = statementList.size() - 1; stmt = statementList.get(last); // <2.2> 重新设置事务超时时间 applyTransactionTimeout(stmt); // <2.3> 往 Statement 中设置 SQL 语句上的参数,例如 PrepareStatement 的 ? 占位符 handler.parameterize(stmt);// fix Issues 322 // <2.4> 获取上一次添加至批处理 Statement 对应的 BatchResult 对象,将本次的入参添加到其中 BatchResult batchResult = batchResultList.get(last); batchResult.addParameterObject(parameterObject); } else { // <3> 否则,创建 Statement 和 BatchResult 对象 // <3.1> 初始化 Statement 对象 Connection connection = getConnection(ms.getStatementLog()); stmt = handler.prepare(connection transaction.getTimeout()); handler.parameterize(stmt); // fix Issues 322 // <3.2> 设置 currentSql 和 currentStatemen currentSql = sql; currentStatement = ms; // <3.3> 添加 Statement 到 statementList 中 statementList.add(stmt); // <3.4> 创建 BatchResult 对象,并添加到 batchResultList 中 batchResultList.add(new BatchResult(ms sql parameterObject)); } // <4> 添加至批处理 handler.batch(stmt); // <5> 返回 Integer.MIN_VALUE 1002 return BATCH_UPDATE_RETURN_VALUE; }

  1. 创建StatementHandler对象,和SimpleExecutor中一致,在后续会讲到
  2. 如果和上一次添加至批处理Statement对象对应的currentSql和currentStatement都一致,则聚合到BatchResult中获取上一次添加至批处理Statement对象重新设置事务超时时间往Statement中设置 SQL 语句上的参数,例如PrepareStatement的?占位符,在SimpleExecutor中已经讲到获取上一次添加至批处理Statement对应的BatchResult对象,将本次的入参添加到其中
  3. 否则,创建Statement和BatchResult对象初始化Statement对象,在SimpleExecutor中已经讲到,这里就不再重复了设置currentSql和currentStatemen属性添加Statement到statementList集合中创建BatchResult对象,并添加到batchResultList集合中
  4. 添加至批处理
  5. 返回Integer.MIN_VALUE 1002,为什么返回这个值?不清楚
doFlushStatements方法

执行批处理,也就是将之前添加至批处理的数据库更新操作进行批处理,代码如下:

@Override public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException { try { List<BatchResult> results = new ArrayList<>(); if (isRollback) { // <1> 如果 isRollback 为 true ,返回空数组 return Collections.emptyList(); } // <2> 遍历 statementList 和 batchResultList 数组,逐个提交批处理 for (int i = 0 n = statementList.size(); i < n; i ) { // <2.1> 获得 Statement 和 BatchResult 对象 Statement stmt = statementList.get(i); applyTransactionTimeout(stmt); BatchResult batchResult = batchResultList.get(i); try { // <2.2> 提交该 Statement 的批处理 batchResult.setUpdateCounts(stmt.executeBatch()); MappedStatement ms = batchResult.getMappedStatement(); List<Object> parameterObjects = batchResult.getParameterObjects(); /* * <2.3> 获得 KeyGenerator 对象 * 1. 配置了 <selectKey /> 则会生成 SelectKeyGenerator 对象 * 2. 配置了 useGeneratedKeys="true" 则会生成 Jdbc3KeyGenerator 对象 * 否则为 NoKeyGenerator 对象 */ KeyGenerator keyGenerator = ms.getKeyGenerator(); if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) { Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator; // <2.3.1> 批处理入参对象集合,设置自增键 jdbc3KeyGenerator.processBatch(ms stmt parameterObjects); } else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { // issue #141 for (Object parameter : parameterObjects) { // <2.3.1> 一次处理每个入参对象,设置自增键 keyGenerator.processAfter(this ms stmt parameter); } } // Close statement to close cursor #1109 // <2.4> 关闭 Statement 对象 closeStatement(stmt); } catch (BatchUpdateException e) { // 如果发生异常,则抛出 BatchExecutorException 异常 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); } // <2.5> 添加到结果集 results.add(batchResult); } return results; } finally { // <3.1> 关闭 Statement 们 for (Statement stmt : statementList) { closeStatement(stmt); } // <3.2> 置空 currentSql、statementList、batchResultList 属性 currentSql = null; statementList.clear(); batchResultList.clear(); } }

在调用doUpdate方法将数据库更新操作添加至批处理后,我们需要调用doFlushStatements方法执行批处理,逻辑如下:

  1. 如果isRollback为true,表示需要回退,返回空数组
  2. 遍历statementList和batchResultList数组,逐个提交批处理获得Statement和BatchResult对象提交该Statement的批处理获得KeyGenerator对象,用于设置自增键,在后续会讲到关闭Statement对象将BatchResult对象添加到结果集
  3. 最后会关闭所有的Statement和清空当前会话中保存的数据

前后两篇文章,希望大家能够一起查看一下,在大家的学习路上能够贡献一份力量

猜您喜欢: