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 接口的两个方法:
- getBatchSize():返回本次批量操作的总条数
- 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)其它相关文章!
 
         
       
       
       
       
       
       加载中,请稍侯......
 加载中,请稍侯......
      
精彩评论