如何在Spring Batch中通过多步骤处理同一对象?
解决同一对象多步骤处理的Spring Batch方案
看起来你想要的是让从数据库读取的每个合同对象,依次走完调研、利息计算、滞纳金计算、FDI计算这些流程,而不是每个步骤都单独查询一次数据库重新处理对吧?下面给你两种适配不同场景的解决方案:
方案一:用CompositeItemProcessor串联所有处理逻辑(推荐,高效简洁)
如果你的各个处理步骤不需要独立事务,也不需要在每个步骤后持久化中间状态,这种方式最适合——只需要读取一次数据,就让每个对象依次经过所有处理逻辑。
步骤1:定义单个处理逻辑的Processor
先把每个步骤的处理逻辑拆成独立的ItemProcessor:
@Bean public ItemProcessor<Contract, Contract> researchContractProcessor() { return contract -> { // 在这里实现调研合同的业务逻辑 contract.setResearchCompleted(true); // 必须返回对象,否则会被过滤掉 return contract; }; } @Bean public ItemProcessor<Contract, Contract> calculateInterestProcessor() { return contract -> { // 实现利息计算逻辑 contract.setInterest(contract.getPrincipalAmount() * 0.06); return contract; }; } @Bean public ItemProcessor<Contract, Contract> calculateArrearsProcessor() { return contract -> { // 实现滞纳金计算逻辑 contract.setArrears(contract.getOverdueDays() * 5.0); return contract; }; } @Bean public ItemProcessor<Contract, Contract> calculateFDIProcessor() { return contract -> { // 实现FDI计算逻辑 contract.setFdiAmount(contract.getTotalAmount() * 0.02); return contract; }; }
步骤2:组合成CompositeItemProcessor
把上面的处理器按执行顺序组合成一个复合处理器:
@Bean public CompositeItemProcessor<Contract, Contract> compositeContractProcessor() { CompositeItemProcessor<Contract, Contract> compositeProcessor = new CompositeItemProcessor<>(); // 注意顺序:必须按业务依赖的先后排列 compositeProcessor.setDelegates(Arrays.asList( researchContractProcessor(), calculateInterestProcessor(), calculateArrearsProcessor(), calculateFDIProcessor() // 后续如果有其他处理逻辑,继续添加在这里 )); return compositeProcessor; }
步骤3:定义单个Step完成所有处理
用你的JdbcPagingItemReader、复合处理器和最终的Writer构建一个Step:
@Bean public Step contractProcessingStep() { return stepBuilderFactory.get("contractProcessingStep") .<Contract, Contract>chunk(10) // 按10条为一批处理,可根据性能调整 .reader(yourJdbcPagingItemReader()) // 你原来的JdbcPagingItemReader .processor(compositeContractProcessor()) .writer(contractFinalWriter()) // 最终写入正式表的Writer .build(); }
步骤4:简化Job配置
现在你的Job只需要启动这一个Step即可:
@Bean public Job jobValorizacao() { return jobBuilderFactory.get("jobValorizacao") .incrementer(new RunIdIncrementer()) .start(contractProcessingStep()) .build(); }
方案二:多Step+临时存储(适合需要独立事务/中间持久化的场景)
如果每个处理步骤需要单独的事务控制,或者必须在每个步骤后把中间状态持久化到数据库,你可以用临时存储(比如临时表)来传递数据:
核心思路
- 第一个Step读取原始数据,执行调研逻辑后写入临时表
- 后续每个Step从临时表读取数据,执行对应处理逻辑后更新临时表
- 最后一个Step把临时表的数据写入正式表
示例代码片段
// 第一个Step:调研合同并写入临时表 @Bean public Step researchContractStep() { return stepBuilderFactory.get("researchContractStep") .<Contract, Contract>chunk(10) .reader(yourJdbcPagingItemReader()) .processor(researchContractProcessor()) .writer(tempContractWriter()) // 写入临时表的Writer .build(); } // 第二个Step:从临时表读取,计算利息并更新临时表 @Bean public JdbcPagingItemReader<Contract> tempContractReader() { // 配置读取临时表的JdbcPagingItemReader JdbcPagingItemReader<Contract> reader = new JdbcPagingItemReader<>(); reader.setDataSource(dataSource); reader.setRowMapper(new BeanPropertyRowMapper<>(Contract.class)); // 设置临时表的查询语句和分页参数 // ... 省略具体配置 return reader; } @Bean public Step calculateInterestStep() { return stepBuilderFactory.get("calculateInterestStep") .<Contract, Contract>chunk(10) .reader(tempContractReader()) .processor(calculateInterestProcessor()) .writer(tempContractUpdateWriter()) // 更新临时表的Writer .build(); } // 后续步骤以此类推,最后一个Step写入正式表 @Bean public Step finalWriteStep() { return stepBuilderFactory.get("finalWriteStep") .<Contract, Contract>chunk(10) .reader(tempContractReader()) .writer(contractFinalWriter()) .build(); } // 最终的Job配置 @Bean public Job jobValorizacao() { return jobBuilderFactory.get("jobValorizacao") .incrementer(new RunIdIncrementer()) .start(researchContractStep()) .next(calculateInterestStep()) .next(calculateArrearsStep()) .next(calculateFDIStep()) .next(finalWriteStep()) .build(); }
注意事项
- 方案一注意点:如果任何一个Processor返回
null,这个Item会被过滤掉,不会进入后续处理。如果不需要过滤,确保每个Processor都返回非null的对象。 - 方案二注意点:临时表需要注意数据隔离(比如用Job的RunId作为区分标识),避免不同Job实例的数据互相干扰;处理完成后记得清理临时表数据。
- 性能考量:方案一减少了多次数据库查询的开销,适合大数据量场景;方案二因为多了多次读写临时表的操作,性能稍逊,但胜在事务和状态管理更灵活。
内容的提问来源于stack exchange,提问作者Gustavo Barbosa
相关产品推荐
相关产品推荐

