开发者

Spring Framework中JDBC批量操作的三种实现方式

目录
  • 为什么要用“批量操作”?
  • 基本批量操作:使用 BATchPreparedStatementSetter
    • 使用方法:
    • 示例说明:
    • 关键点:
    • 特殊情况:流式输入(不确定总数)
  • 更简洁的方式:用对象列表做批量操作
    • 方式一:使用命名参数(Named Parameters) + NamedParameterJdbcTemplate
    • 方式二:使用 ? 占位符 + List<Object[]>
  • 处理超大数据集:分多个小批次提交(Multiple Batches)
    • 解决方案:
    • 返回值解释:
    • 适用场景:
  • 三种方式对比总结
    • 性能建议
      • 实际应用建议(Spring Boot 项目)
        • 总结一句话:

          这篇文章详细介绍了如何使用 Spring 的 JdbcTemplate 进行高效的数据库批量更新,从而减少与数据库之间的网络往返次数(round-trips),提升性能。

          下面我将用通俗易懂的方式,结合代码示例和实际场景,帮你系统地理解这一节的核心思想和三种主要批量处理方式。

          为什么要用“批量操作”?

          在没有批量处理时,如果你要插入或更新 1000 条数据:

          for (Actor actor : actors) {
              jdbcTemplate.update("INSERT INTO t_actor ...", actor.getName(), ...);
          }
          

          这会向数据库发送 1000 次独立的 SQL 请求 → 1000 次网络通android信 → 效率极低。

          而使用 批量操作(Batch Operations),你可以把多个 SQL 操作“打包”成一组,一次性提交给数据库执行:

          ✅ 结果:

          • 减少网络开销
          • 提高吞吐量(可能提升几十倍)
          • 更高效地利用数据库资源

          基本批量操作:使用 BatchPreparedStatementSetter

          这是最传统、控制最精细的方式。

          使用方法:

          实现 BatchPreparedStatementSetter 接口的两个方法:

          1. getBatchSize():返回本次批量操作的总条数
          2. setValues(PreparedStatement ps, int i):为第 i 条记录设置参数

          示例说明:

          public int[] batchUpdate(final List<Actor> actors) {
              return this.jdbcTemplate.batchUpdate(
                      "update t_actor set first_name = ?, last_name = ? where id = ?",
                      new BatchPreparedStatementSetter() {
                          public void setValues(PreparedStatement ps, int i) throws SQLException {
                              Actor actor = actors.get(i);
                              ps.setString(1, actor.getFirstName());
                              ps.setString(2, actor.getLastName());
                              ps.setLong(3, actor.getId().longValue());
                          }
                          public int getBatchSize() {
                              return actors.size(); // 整个列表作为一批
                          }
                      });
          }
          

          关键点:

          • 整个 actors 列表被当作一个“大批次”一次性提交。
          • 返回值是 int[],每个元素表示对应 SQL 语句影响的行数。
          • 适用于:数据量不大且能一次性加载到内存的情况。

          特殊情况:流式输入(不确定总数)

          如果数据来自文件、流或数据库游标,无法预知总数,可以使用:

          InterruptibleBatchPreparedStatementSetter

          它有一个额外方法:

          boolean isBatchExhausted();
          

          当你读完所有数据后返回 true,Spring 就停止继续添加语句。

          更简洁的方式:用对象列表做批量操作

          Spring 提供了更简洁的 API —— 不用手动实现接口,只需传入一个对象列表!

          方式一:使用命名参数(Named Parameters) + NamedParameterJdbcTemplate

          public int[] batchUpdate(List<Actor> actors) {
              return this.namedParameterJdbcTemplate.batchUpdate(
                  "update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
                  SqlParameterSourceUtils.createBatch(actors) // 自动提取 getter
              );
          }
          

          优点:

          • 使用 :firstName 这种命名参数,可读性强
          • SqlParameterSourceUtils.createBatch(actors) 自动从 Actor 对象的 getter 提取字段javascript
          • 支持 POJO(JavaBean)、Map 等格式

          要求:Actor 类必须有 getFirstName()、getId() 等标准 getter 方法

          方式二:使用 ? 占位符 + List<Object[]>

          Lisjavascriptt<Object[]> batch = new ArrayList<>();
          for (Actor actor : actors) {
              Object[] values = new Object[] {
                  actor.getFirstName(), 
                  actor.getLastName(), 
                  actor.getId()
              };
              batch.add(values);
          }
          
          jdbcTemplate.batchUpdate(
              "update t_actor set first_name = ?, last_name = ? where id = ?", 
              batch
          );
          

          优点:

          • 简单直观
          • 不依赖命名参数

          注意事项:

          • 参数顺序必须严格匹配 SQL 中的 ? 顺序
          • 如果值为 null,Spring 需要通过反射推断类型(可能影响性能)

          性能提示:

          Spring 默认调用 ParameterMetaData.getParameterType() 来判断 null 值的类型,但某些数据库驱动(如 oracle 12c)这个操js作很慢。

          可以通过设置系统属性关闭:

          # 在 JVM 启动参数或 spring.properties 中添加
          spring.jdbc.getParameterType.ignore=true
          

          或者显式指定类型(更推荐)。

          处理超大数据集:分多个小批次提交(Multiple Batches)

          前面的例子都是一次性提交全部数据。但如果数据量太大(比如 10 万条),一次性加载到内存会导致内存溢出(OOM)。

          解决方案:

          使用分批提交(chunking),每 100 条提交一次。

          public int[][] bajstchUpdate(final Collection<Actor> actors) {
              int[][] updateCounts = jdbcTemplate.batchUpdate(
                  "update t_actor set first_name = ?, last_name = ? where id = ?",
                  actors,              // 所有数据
                  100,                 // 每批最多 100 条
                  (ps, actor) -> {     // Lambda 设置参数
                      ps.setString(1, actor.getFirstName());
                      ps.setString(2, actor.getLastName());
                      ps.setLong(3, actor.getId().longValue());
                  }
              );
              return updateCounts;
          }
          

          返回值解释:

          int[][] updateCounts
          
          • 第一层数组:每个“批次”的结果
          • 第二层数组:该批次中每条 SQL 的影响行数

          例如:

          [
            [1, 1, 1, ..., 1],  // 第1批,共100条,每条影响1行
            [1, 1, 1, ..., 1],  // 第2批
            [1, 1, 1]           // 最后一批只有3条
          ]
          

          适用场景:

          • 处理大量数据(数万、数十万条)
          • 防止内存溢出
          • 支持进度监控、错误恢复

          三种方式对比总结

          方式适用场景优点缺点
          BatchPreparedStatementSetter需要精确控制每条记录参数灵活、支持中断写法较繁琐
          List<Object[]>SqlParameterSourceUtils.createBatch()数据已在内存中,结构简单写法简洁不适合超大数据集
          分批提交(multiple batches)超大数据集内存友好、可控性强稍复杂

          性能建议

          合理设置批大小(batch size)

          • 太小:仍有多次往返
          • 太大:内存压力大,事务过长
          • 推荐值:50 ~ 1000(根据测试调整)

          开启数据库的批量支持

          • mysql:确保连接 URL 包含 rewriteBatchedStatements=true
          jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
          
          • Oracle:原生支持较好
          • PostgreSQL:需要驱动支持(reWriteBatchedInserts=true

          使用连接池(如 HikariCP)

          • 批量操作期间保持连接稳定

          避免自动提交(Auto-commit)

          • 在事务中执行批量操作,防止中途失败导致部分写入

          实际应用建议(Spring Boot 项目)

          如果你使用的是 Spring Boot,推荐写法如下:

          @Service
          public class ActorService {
          
              @Autowired
              private JdbcTemplate jdbcTemplate;
          
              public void updateActorsInBatches(List<Actor> actors) {
                  jdbcTemplate.batchUpdate(
                      "UPDATE t_actor SET first_name = ?, last_name = ? WHERE id = ?",
                      actors,
                      100,
                      (ps, actor) -> {
                          ps.setString(1, actor.getFirstName());
                          ps.setString(2, actor.getLastName());
                          ps.setLong(3, actor.getId());
                      }
                  );
              }
          }
          

          搭配事务注解:

          @Transactional
          public void importHugeData() {
              List<Actor> hugeList = readFromCsvOrDatabase();
              updateActorsInBatches(hugeList); // 分批提交
          }
          

          总结一句话:

          批量操作 = 把多条 SQL “打包”发送给数据库,减少网络来回,提高性能。Spring 提供了多种方式让你既能简单使用,也能精细控制。

          下一步你可以思考:

          • 如何从 CSV 文件逐行读取并分批导入?
          • 如何在批量操作中捕获部分失败的记录?
          • 如何结合 @Async 实现并行批量处理?

          以上就是Spring Framework中JDBC批量操作的三种实现方式的详细内容,更多关于Spring JDBC批量操作方式的资料请关注编程客栈(www.devze.com)其它相关文章!

          0

          上一篇:

          下一篇:

          精彩评论

          暂无评论...
          验证码 换一张
          取 消

          最新开发

          开发排行榜