MyBatis批量更新最简单实用方法详解(附代码案例)

2,111 阅读5分钟

批量更新是开发中常见的需求,尤其当需要一次性修改大量数据时,合理的批量更新方式能大幅提升性能,减少数据库连接和网络开销。本文用最简单的方式,结合MyBatis和MySQL,介绍几种批量更新的常见写法,优缺点,以及如何解决常见的配置问题,帮助你快速上手。

1. 批量更新的背景和必要性

在实际项目中,如果对一批数据逐条执行更新(如Java中for循环调用单条update),每次都会发起一次数据库请求,网络和磁盘IO开销大,效率低下。批量更新则是一次性将多条更新语句发送给数据库,减少请求次数,提升性能。

2. 常见的批量更新写法及示例

2.1 使用MyBatis的<foreach>拼接多条独立的UPDATE语句(推荐)

这种写法在MyBatis的XML中,利用<foreach>循环拼接多条UPDATE语句,每条语句更新一条记录,语句之间用分号;分隔,最后一次性发送给数据库执行。

示例代码:

<update id="batchUpdateUsers" parameterType="java.util.List">
  <foreach collection="list" item="user" separator=";">
    UPDATE sys_user
    SET password = #{user.password},
        update_user_id = #{user.updateUserId},
        update_user_name = #{user.updateUserName}
    WHERE id = #{user.id}
  </foreach>
</update>

Java调用示例:

List<UserEntity> users = ...; // 待更新用户列表
userMapper.batchUpdateUsers(users);

优点:

  • 代码简单直观,易于理解和维护。
  • 性能较好,因为只需一次数据库请求。
  • 适合更新字段较多、数据量适中的场景。

注意事项:

  • MySQL默认不支持一次执行多条SQL语句,需要在数据库连接URL中添加参数allowMultiQueries=true
  • 一次批量更新的条数建议控制在1000条以内,避免SQL过长导致性能问题或异常。

2.2 使用CASE WHEN语句批量更新(不推荐)

这种写法是将多条更新合并成一条UPDATE语句,利用CASE WHEN根据不同id设置不同的字段值。

示例代码:

<update id="batchUpdateUsersCaseWhen" parameterType="java.util.List">
  UPDATE sys_user
  <set>
    password = CASE id
      <foreach collection="list" item="user">
        WHEN #{user.id} THEN #{user.password}
      </foreach>
      ELSE password END,
    update_user_id = CASE id
      <foreach collection="list" item="user">
        WHEN #{user.id} THEN #{user.updateUserId}
      </foreach>
      ELSE update_user_id END,
    update_user_name = CASE id
      <foreach collection="list" item="user">
        WHEN #{user.id} THEN #{user.updateUserName}
      </foreach>
      ELSE update_user_name END
  </set>
  WHERE id IN
  <foreach collection="list" item="user" open="(" separator="," close=")">
    #{user.id}
  </foreach>
</update>

缺点:

  • SQL语句复杂,代码难维护。
  • 当更新字段多、数据量大时,SQL语句会非常长且复杂。
  • 数据库执行时需要判断大量CASE WHEN条件,性能较差。

2.3 使用INSERT ... ON DUPLICATE KEY UPDATE方式

这种方式适合插入或更新操作,先尝试插入数据,如果主键或唯一索引冲突,则执行更新。

示例代码:

<insert id="insertOrUpdateUsers" parameterType="java.util.List">
  INSERT INTO sys_user (id, username, password)
  VALUES
  <foreach collection="list" item="user" separator=",">
    (#{user.id}, #{user.username}, #{user.password})
  </foreach>
  ON DUPLICATE KEY UPDATE
    password = VALUES(password)
</insert>

要求:

  • 需要为业务字段(如username)创建唯一索引。
  • 主键id不适合自动生成或雪花算法生成的唯一值,需保证业务唯一性。

缺点:

  • 容易引发数据库死锁,尤其在高并发场景下。
  • 大型公司项目较少采用此方式。

3. 配置注意点:解决“sql injection violation, multi-statement not allow”异常

批量更新时,常见错误是:

sql injection violation, multi-statement not allow

这是因为MySQL默认不允许一次执行多条SQL语句,且数据库连接池(如Druid)中的防火墙过滤器(WallFilter)默认禁止多语句执行。

解决方案:

  1. MySQL连接串添加参数

application.ymlapplication.properties中MySQL连接URL加上:

allowMultiQueries=true

示例:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/your_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
  1. Druid连接池配置

Druid默认禁止多语句执行,需要在配置中开启:

spring:
  datasource:
    druid:
      filter:
        wall:
          config:
            multi-statement-allow: true
            none-base-statement-allow: true
  1. 使用动态数据源或中间件时

如果使用了如baomidou的动态数据源或shardingsphere分库分表中间件,需要在对应配置中也开启multi-statement-allow,否则仍会报错。

4. 性能对比和批量大小建议

  • for循环单条更新:每条数据单独发送SQL,网络和数据库压力大,性能最差。
  • foreach拼接多条语句批量更新:一次请求执行多条SQL,性能提升明显,实测100条数据更新时效率比for循环快约80倍。
  • case when批量更新:SQL复杂度高,数据库执行压力大,性能一般。
  • insert on duplicate key update:性能较好,但易产生死锁,需谨慎使用。

批量大小建议:

  • 单次批量更新建议控制在1000条以内,避免SQL过长和数据库压力过大。
  • 超过1000条时,分批执行更新。

5. 完整示例代码(MyBatis + Spring Boot)

Mapper接口:

public interface UserMapper {
    void batchUpdateUsers(@Param("list") List<UserEntity> users);
}

Mapper XML:

<update id="batchUpdateUsers" parameterType="java.util.List">
  <foreach collection="list" item="user" separator=";">
    UPDATE sys_user
    SET password = #{user.password},
        update_user_id = #{user.updateUserId},
        update_user_name = #{user.updateUserName}
    WHERE id = #{user.id}
  </foreach>
</update>

Spring Boot配置示例(application.yml):

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/your_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    druid:
      filter:
        wall:
          config:
            multi-statement-allow: true
            none-base-statement-allow: true

6. 总结

  • 推荐使用MyBatis的<foreach>循环拼接多条UPDATE语句,配合MySQL的allowMultiQueries=true和Druid的multi-statement-allow=true配置,实现高效批量更新。
  • 避免使用复杂的CASE WHEN方式,代码难维护且性能一般。
  • insert ... on duplicate key update适合特定场景,需注意唯一索引和死锁风险。
  • 控制批量更新的数据量,避免单次更新过大导致性能问题。
  • 遇到批量更新报错时,检查数据库连接参数和连接池配置,确保支持多语句执行。

通过以上方法,你可以轻松实现高效、稳定的批量更新操作,提升项目性能和代码质量。

欢迎收藏和分享,助力你在数据库操作中更加游刃有余!