Spring Framework中JDBC批量操作的三种实现方式
作者:lang20150928
这篇文章详细介绍了如何使用 Spring 的 JdbcTemplate 进行高效的数据库批量更新,从而减少与数据库之间的网络往返次数(round-trips),提升性能。
下面我将用通俗易懂的方式,结合代码示例和实际场景,帮你系统地理解这一节的核心思想和三种主要批量处理方式。
为什么要用“批量操作”?
在没有批量处理时,如果你要插入或更新 1000 条数据:
for (Actor actor : actors) {
jdbcTemplate.update("INSERT INTO t_actor ...", actor.getName(), ...);
}
这会向数据库发送 1000 次独立的 SQL 请求 → 1000 次网络通信 → 效率极低。
而使用 批量操作(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 提取字段- 支持 POJO(JavaBean)、Map 等格式
要求:Actor 类必须有 getFirstName()、getId() 等标准 getter 方法
方式二:使用 ? 占位符 + List<Object[]>
List<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)这个操作很慢。
可以通过设置系统属性关闭:
# 在 JVM 启动参数或 spring.properties 中添加 spring.jdbc.getParameterType.ignore=true
或者显式指定类型(更推荐)。
处理超大数据集:分多个小批次提交(Multiple Batches)
前面的例子都是一次性提交全部数据。但如果数据量太大(比如 10 万条),一次性加载到内存会导致内存溢出(OOM)。
解决方案:
使用分批提交(chunking),每 100 条提交一次。
public int[][] batchUpdate(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批量操作方式的资料请关注脚本之家其它相关文章!
