JdbcBatchItemWriter 详解
本章定位:深入掌握 Spring Batch 最高频的数据库 Writer——JdbcBatchItemWriter,从参数绑定到性能调优。
定义与作用
JdbcBatchItemWriter 使用 JDBC Batch API(PreparedStatement.addBatch() + executeBatch())将数据批量写入数据库。它将 Chunk 中的 N 条数据通过 N 次 addBatch() 后一次性 executeBatch(),极大减少网络往返。
小崔处理杨英每日 5000 条订单写入时的体验:
"不写 JDBC 循环、不写 Connection 管理、不写 try-catch。JdbcBatchItemWriter 帮我把一切包裹在 Chunk 事务里。5000 条 = 100 次 executeBatch(),网络往返从 5000 次降到 100 次。"
核心原理
三种 ParameterSourceProvider
| Provider | 数据输入 | 适用场景 |
|---|---|---|
BeanPropertyItemSqlParameterSourceProvider | POJO | DTO/Entity 的 getter 自动映射到 :paramName |
MapItemSqlParameterSourceProvider | Map | 动态构建参数集合 |
| 自定义 | 任意 | 复杂 SQL、聚合计算 |
完整示例
场景一:标准 Entity 写入
@Bean
public JdbcBatchItemWriter<StudentEntity> standardStudentWriter(DataSource dataSource) {
return new JdbcBatchItemWriterBuilder<StudentEntity>()
.dataSource(dataSource)
.sql("INSERT INTO students (student_id, name, email, age, import_time) "
+ "VALUES (:studentId, :name, :email, :age, :importTime) "
+ "ON DUPLICATE KEY UPDATE name = VALUES(name), email = VALUES(email), age = VALUES(age)")
.itemSqlParameterSourceProvider(
new BeanPropertyItemSqlParameterSourceProvider<>())
.assertUpdates(true)
.build();
}
场景二:多数据源 Writer
@Bean
public JdbcBatchItemWriter<AuditLog> auditWriter(
@Qualifier("auditDataSource") DataSource auditDataSource) {
return new JdbcBatchItemWriterBuilder<AuditLog>()
.dataSource(auditDataSource) // 走单独的审计数据源
.sql("INSERT INTO audit_log (job_name, step_name, record_count, timestamp) "
+ "VALUES (:jobName, :stepName, :recordCount, :timestamp)")
.itemSqlParameterSourceProvider(log -> {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("jobName", log.jobName());
params.addValue("stepName", log.stepName());
params.addValue("recordCount", log.recordCount());
params.addValue("timestamp", LocalDateTime.now());
return params;
})
.build();
}
性能对比:
| 方案 | 10 万条写入 | 内存占用 |
|---|---|---|
JdbcTemplate.update() 逐条 | ~120s | 低 |
JdbcBatchItemWriter chunk=50 | ~8s (15x) | 低 |
JdbcBatchItemWriter chunk=1000 | ~2s (60x) | 中 |
易错场景与避坑
反例一:SQL 中参数名和 POJO 属性名不匹配
// ❌ SQL 中 :studentName 但 POJO 属性是 name
.sql("INSERT INTO students (name) VALUES (:studentName)")
// BeanPropertyItemSqlParameterSourceProvider 找不到 studentName → 报错
反例二:忘记设置 assertUpdates
// ❌ 默认 assertUpdates=false → SQL 执行了 0 行也不报错
.sql("UPDATE students SET status='ACTIVE' WHERE id=:id")
// 如果 id 不存在 → 0 rows affected → 静默跳过 → 数据丢失!
正确做法:对于 INSERT/UPDATE 必须 assertUpdates(true)。
面试高频考点
Q1:JdbcBatchItemWriter 和 JpaItemWriter 的性能差异?
JdbcBatchItemWriter 使用
executeBatch()一次性提交多条 SQL,1 次网络往返。JpaItemWriter 逐条entityManager.merge(),N 次网络往返。大数据量下 JDBC 版本快 5-10 倍。但 JPA 版本支持级联和实体状态管理。
Q2:如何处理 Writer 中部分成功部分失败的情况?
在 Chunk 模式下不可行——一个 Chunk 的写入在同一个事务中,要么全成功要么全回滚。如果某些行预期会失败,需要在 Processor 阶段过滤或标记,或在 Writer 中使用
faultTolerant().skip()。