Spring Batch 6.x统计成功处理项并打印摘要的最佳实践是什么?
一、Spring Batch 内置的项跟踪机制
Spring Batch 原生提供StepExecution和JobExecution对象跟踪项处理状态,核心统计字段完全覆盖你的需求:
readCount: 已读取的项总数writeCount: 成功写入且事务提交的项数(自动排除回滚、跳过的项)skipCount: 被跳过的项数(含处理/写入失败且触发跳过规则的项)rollbackCount: 事务回滚次数
这些统计会自动持久化到元数据存储(如数据库),重启作业时会基于历史执行状态续算,无需额外处理基础统计逻辑。
二、统计位置选择:Writer 阶段更可靠
绝对不要在Processor中统计——Processor处理完成的项可能在Writer阶段失败导致回滚,这类项不能算作成功。只有Writer成功提交后,项才是真正处理完成的,推荐两种实现方式:
1. 直接使用内置 writeCount
如果仅需统计成功项总数,StepExecution的writeCount是最准确的数值:它仅统计成功写入且事务提交的项,回滚的项不会被计入,重试成功的项也只会被统计一次。
2. 自定义细粒度统计
如果需要按业务分类统计成功项,可实现ItemWriteListener,在afterWrite方法中累加统计值,并将结果存入ExecutionContext(保证重启时能续算):
@Component public class CustomItemWriteListener implements ItemWriteListener<BusinessItem> { @Override public void afterWrite(List<? extends BusinessItem> items) { StepExecution stepExecution = StepSynchronizationManager.getContext().getStepExecution(); ExecutionContext executionContext = stepExecution.getExecutionContext(); // 从上下文读取当前统计值,初始为0 int validItemCount = executionContext.getInt("validItemCount", 0); // 按业务规则过滤统计(示例:统计状态为"VALID"的项) long count = items.stream().filter(item -> "VALID".equals(item.getStatus())).count(); validItemCount += count; executionContext.putInt("validItemCount", validItemCount); } }
将监听器注册到Step:
@Bean public Step businessStep(ItemReader<BusinessItem> reader, ItemProcessor<BusinessItem, BusinessItem> processor, ItemWriter<BusinessItem> writer, CustomItemWriteListener listener) { return new StepBuilder("businessStep", jobRepository) .<BusinessItem, BusinessItem>chunk(20, transactionManager) .reader(reader) .processor(processor) .writer(writer) .listener(listener) .build(); }
三、重启与重试的处理
1. 重启作业
Spring Batch 会自动从上次失败的位置恢复执行,内置的writeCount会自动累加历史成功项数。自定义统计时,只要将值存入ExecutionContext(它会被持久化到元数据),重启时就能自动读取并续算,无需额外逻辑。
2. 重试处理
当配置重试规则(如通过RetryTemplate或@Retryable),只有重试成功的项才会被写入并计入writeCount;如果重试失败触发跳过规则,该项会被计入skipCount,不会被统计到成功项中。自定义统计的afterWrite方法仅在项成功写入后触发,重试过程中失败的项不会触发该方法,因此统计结果天然准确。
四、作业完成后打印摘要
实现JobExecutionListener,在afterJob方法中获取统计数据并打印到控制台:
@Component public class JobCompletionSummaryListener implements JobExecutionListener { @Override public void afterJob(JobExecution jobExecution) { if (BatchStatus.COMPLETED.equals(jobExecution.getStatus())) { System.out.println("===== 作业执行完成摘要 ====="); for (StepExecution stepExecution : jobExecution.getStepExecutions()) { System.out.printf("步骤 [%s]: 读取项数=%d, 成功处理项数=%d, 跳过项数=%d, 回滚次数=%d%n", stepExecution.getStepName(), stepExecution.getReadCount(), stepExecution.getWriteCount(), stepExecution.getSkipCount(), stepExecution.getRollbackCount()); } // 打印自定义统计(示例) StepExecution targetStep = jobExecution.getStepExecutions().stream() .filter(step -> "businessStep".equals(step.getStepName())) .findFirst().orElse(null); if (targetStep != null) { int validItemCount = targetStep.getExecutionContext().getInt("validItemCount", 0); System.out.printf("有效业务项统计: %d%n", validItemCount); } } } }
将监听器注册到Job:
@Bean public BusinessJob businessJob(JobRepository jobRepository, Step businessStep, JobCompletionSummaryListener listener) { return new JobBuilder("businessJob", jobRepository) .start(businessStep) .listener(listener) .build(); }
内容的提问来源于stack exchange,提问作者Gorock

