
本文详解如何在 Spring Batch 中构建支持自定义读取、处理与写入逻辑的批处理流程,重点解决因错误使用 @StepScope 导致的数据重复写入问题,并提供 @JobScope 正确配置、类型匹配及 Chunk 写入优化的完整实践方案。
本文详解如何在 spring batch 中构建支持自定义读取、处理与写入逻辑的批处理流程,重点解决因错误使用 `@stepscope` 导致的数据重复写入问题,并提供 `@jobscope` 正确配置、类型匹配及 chunk 写入优化的完整实践方案。
在 Spring Batch 中实现“单 ID 输入 → 多对象输出 → 批量持久化”的非标准数据流(如:Reader 输出 String bookingId,Processor 输出 List> 的类型设计虽可行,但需严格保证 Chunk 语义不被破坏。
✅ 正确的作用域配置(解决重复写入根源)
@StepScope 会在每次 Step 启动时新建 Bean 实例,因此 CustomItemReader.bookingIds 在每个 Chunk 处理前都被重置为 null,触发 bookingInfoRepository.findDistinctId() 重复执行 —— 这不仅造成性能浪费,更导致 read() 方法反复返回同一组 ID,最终 Writer 将相同数据多次写入。
修正方案:
- CustomItemReader 改用 @JobScope(确保在整个 Job 生命周期内仅初始化一次);
- CorrectionProcessor 和 CustomItemWriter 移除 @StepScope,使用默认 singleton 作用域(它们无状态或依赖注入的 Repository 是线程安全的);
- 在任意 @Configuration 类上添加 @EnableBatchProcessing 启用 @JobScope 支持:
@Configuration
@EnableBatchProcessing
public class BatchConfig {
// 其他配置...
}
修正后的 Reader 示例:
@Component
@JobScope // ✅ 关键:生命周期与 Job 对齐
@Slf4j
public class CustomItemReader implements ItemReader<string> {
@Autowired
private BookingInfoRepository bookingInfoRepository;
private List<string> bookingIds; // 状态在 Job 内保持
@Override
public String read() {
if (bookingIds == null) {
bookingIds = bookingInfoRepository.findDistinctId();
log.info("Loaded {} booking IDs for processing", bookingIds.size());
}
return bookingIds.isEmpty() ? null : bookingIds.remove(0);
}
}</string></string>
✅ 类型契约与 Chunk 写入安全实践
虽然 Spring Batch 允许 ItemProcessor 的输出类型 O 为集合(如 List
@Override
public void write(Chunk extends List<bookinginfo>> chunk) throws Exception {
for (List<bookinginfo> bookingInfoList : chunk) { // ✅ 正确:遍历每个 bookingId 对应的 List
bookingInfoRepository.saveAll(bookingInfoList); // ✅ 推荐:批量保存,而非单条
}
}</bookinginfo></bookinginfo>
⚠️ 注意事项:
- 不要在 Writer 中缓存或累加数据(如 this.allData.addAll(...)),否则 Chunk 重试或重启时将重复写入;
- bookingInfoRepository.saveAll() 比循环 save() 更高效,且避免 N+1 问题;
- 若需事务隔离,确保 Step 配置 transactionManager 并启用 chuckSize(如 .chunk(10)),使每个 Chunk 作为独立事务单元。
✅ 完整流程验证要点
- Reader 状态一致性:启动 Job 后,检查日志确认 findDistinctId() 仅执行 1 次;
-
Chunk 边界清晰性:通过 log.debug("Processing chunk of size: {}", chunk.getItems().size()) 验证 Writer 接收的是预期数量的 List
; - 幂等性保障:若需支持重启,Reader 应记录已处理 ID(如写入 ExecutionContext),避免 Job 中断后重复处理。
总结:Spring Batch 的灵活性允许非常规数据流,但必须严守作用域契约——状态型组件(如带内部缓存的 Reader)应绑定到 Job 生命周期;无状态组件(Processor/Writer)使用 singleton 即可。正确的作用域是避免数据重复、提升性能与保证可靠性的基石。











