【Mybatis】BatchExecutor批量处理逻辑源码解析

尊重个人劳动成果,转载请注明出处: https://blog.csdn.net/walson_z/article/details/109909852

一、概述

本文主要对BatchExecutor两个主要的方法doUpdate和doFlushStatements的源码进行解析,前者所有涉及数据变动的Mapper方法(insert、update、delete)都会最终调用到,主要作用是将连续执行且sql相同的Statement做分组保存,方便后面批量执行;后者会在提交事务的时候会被调用,主要作用是将前面分组保存的Statement进行批量执行。

二、BatchExecutor.doUpdate()

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;
    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());
      //Statement参数化。参数化后下面调用的handler.batch(stmt)将前面参数化的statement添加到批量处理中
      handler.parameterize(stmt);    // fix Issues 322
      currentSql = sql;
      currentStatement = ms;
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

重点关注7~23行:判断本次执行的sql以及由它生成的MapperedStatement是否与上一次的相同,如果不相同则创建Statement,参数化Statement,并将Statement保存在一个列表中;另外创建一个与Statement对应的BatchResult,同样放在一个列表中。如果相同,则取出列表中最后一个Statement进行参数化(后续调用addBatch()添加到批量处理);另外取出BatchResult,将本条sql的参数设置进去

三、BatchExecutor.doFlushStatements()

public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    try {
      List<BatchResult> results = new ArrayList<>();
      if (isRollback) {
        return Collections.emptyList();
      }
      //逐个取出之前分组保存的Statement进行批量执行
      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();
    }
  }

重点关注8~24行:逐个取出之前保存的列表中的每个Statement,对其调用executeBatch(),并将返回值设置到BatchResult.updateCount字段。如果是sql是insert,则将插入时自动生成的主键填充回BatchResult.parameterObjects中,某些业务可能需要用到这些自动生成的主键做后续处理,这时候可以在插入后从返回的BatchResult中获取。

四、总结

本文主要解析了BatchExecutor源码中如何实现批量处理,值得注意的是,如果使用Spring集成Mybatis,SqlSessionTemplate会判断当前是否存在事务,不存在的话,每次执行sql都会进行提交,这样的话即使ExecutorType设置为BATCH,批量操作也不会生效。最近在学习Mybatis框架,关于事务和批处理的关系,原生Mybatis、Mybatis-Spring和底层JDBC的事务的关系,还有许多问题有待梳理,后续再写文章讨论。

 

文章若有纰漏、逻辑不通的地方,还请大家批评指出!

Mybatis-plus提供了方便的批量处理功能,可以通过它来实现批量新增和批量修改操作。在使用Mybatis-plus进行批量处理时,官方提供了针对id的批量修改和批量新增方法。但是有时候我们需要根据其他字段来进行操作,这时候就需要我们自己进行修改了。 对于批量修改操作,可以使用Mybatis-plus提供的方法进行操作,也可以根据项目的实际需求进行扩展。可以将需要修改的数据封装成一个列表,然后通过遍历列表的方式进行批量修改。 对于批量新增操作也是类似的,可以使用Mybatis-plus提供的方法进行操作,也可以根据项目的实际需求进行扩展。同样可以将需要新增的数据封装成一个列表,然后通过遍历列表的方式进行批量新增。 如果项目中没有引用Mybatis-plus,但是仍然想进行批量操作,并且数据量较大时,可以参考Mybatis-plus的批量操作方式,在Mybatis环境下自己编写批量操作的代码。可以使用Mybatis的批量操作功能,通过编写自定义的工具类来实现批量修改和批量新增操作。 在代码中,可以通过SqlSessionFactory获取SqlSession,并根据需要获取对应的Mapper。然后将需要处理的数据列表和Mapper传入自定义的方法中,通过循环遍历数据列表,在每一次循环中执行对应的操作。可以设置一个批处理的阈值,当达到这个阈值时,执行批处理操作。最后,在非事务环境下强制提交事务。 总结来说,Mybatis-plus提供了方便的批量处理功能,可以通过它来实现批量新增和批量修改。如果需要根据其他字段进行操作,可以自行扩展。如果项目中没有引用Mybatis-plus,但仍想进行批量操作,可以参考Mybatis-plus的批量操作方式,在Mybatis环境下编写自定义的批量操作代码。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Mybatis-plus批量操作](https://blog.csdn.net/WayneLee0809/article/details/126424482)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值