2024-12-22  阅读(391)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/1120664989

回答

支持, Mybatis 支持两种批量操作。

foreach 标签

foreach 主要用在构建 in 条件中,它可以在 SQL 语句中遍历一个集合。foreach 标签的属性主要有 item,index,collection,open,separator,close。

  • item:表示集合中每一个元素进行迭代时的别名,随便起的变量名。
  • index:指定一个名字,用于表示在迭代过程中,每次迭代到的位置,不常用。
  • open:表示该语句以什么开始,常用“(”。
  • separator:表示在每次进行迭代之间以什么符号作为分隔符,常用“,”。
  • close:表示以什么结束,常用“)”。
  • collection:指定要遍历的集合或数组。
public interface UserMapper {
    void batchInsertUser(@Param("userList") List<User> userList);
    void batchUpdateEmail(@Param("userList") List<User> userList);
}

<insert id="batchInsertUsers">
    INSERT INTO t_user (id, name, email)
    VALUES
    <foreach collection="userList" item="user" separator=",">
        (#{user.id}, #{user.name}, #{user.email})
    </foreach>
</insert>

<update id="batchUpdateEmails">
    <foreach collection="userList" item="user" separator=";">
        UPDATE t_user
        SET email = #{user.email}
        WHERE id = #{user.id}
    </foreach>
</update>

ExectorType.BATCH

Mybatis 内置的 ExecutorType 有 3 种,默认为 SIMPLE。

public enum ExecutorType {
  SIMPLE, REUSE, BATCH
}
  • SIMPLE:简单执行器,为每个语句的执行创建一个新的预处理语句,单条提交 sql
  • REUSE:可重用执行器,执行 SQL 语句时会先检查是否有可以重用的 Statement 对象,如果有则重用,没有则创建新的。
  • BATCH:批量执行器,会将多条 SQL 语句累积到一起,等到批量执行时一起发送给数据库,减少数据库交互次数,提高性能

备注: BATCH 模式下,事务在没有提交之前,无法获取到自增的 id。

try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    for (User user : userList) {
        userMapper.insertUser(user);
    }
    sqlSession.commit();
    sqlSession.clearCache();
} catch (Exception e) {
    e.printStackTrace();
}

上述 case,每次执行 insertUser() 方法时,SQL 语句会被加入到批处理列表中,而不是立即执行。最后,通过调用 commit() 方法一次性提交所有操作。

扩展

ExecutorType.BATCH 执行原理

MyBatis 的批处理依赖于 JDBC 的批处理机制。Mybatis 通过批处理模式将多条 SQL 语句打包成一个批次,并在一次网络交互中发送到数据库。从而减少网络延迟和数据库连接开销,提高整体性能

MyBatis 在内部通过设置 ExecutorType.BATCH 类型来实现批处理。这种类型的 Executor 会缓存所有执行的 SQL 语句,直到调用 commit() 方法时才会统一发送到数据库执行。

  @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 是否和上一次执行的 SQL 相同
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      int last = statementList.size() - 1;
      stmt = statementList.get(last);
      applyTransactionTimeout(stmt);
      // 设置SQL参数
      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());
      // 参数设置
      handler.parameterize(stmt);    // fix Issues 322
      currentSql = sql;
      currentStatement = ms;
      // 缓存SQL及结果,doFlushStatements 才真正执行sql
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
    // 批量处理
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }
  
 @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();
    }
  }

ExecutorType.BATCH 核心流程

  1. SQL 复用:如果当前执行的 SQL 语句和上一次执行的 SQL 语句相同,则复用上一次的 Statement 对象,避免重复创建 Statement,提高性能。
  2. 新 SQL 语句:如果当前 SQL 语句和上一次不同,则创建新的 Statement 对象,并将其添加到批处理列表
  3. 批量处理:每个 Statement 对象对应一次交互,将所有缓存的 SQL 语句一次性发送到数据库
  4. 提交事务:一次交互来提交事务。

Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文