Spring-Boot+Spring-Batch如何处理含待入库文件名的侧车文件
Great question! 针对你的场景,Spring Batch有两种非常实用的标准实现方式,核心思路都是先读取侧车文件获取待处理的文件名列表,再逐个触发你已经写好的单个文件入库逻辑。下面我结合代码示例详细说明:
如何在Spring Batch中结合侧车文件与单个文件处理作业
方案一:使用JobLauncherTasklet嵌套子作业
这种方式把「单个文件入库」封装成独立的子作业,主作业读取侧车文件后,通过JobLauncherTasklet逐个启动子作业处理每个文件,优势是每个文件的处理都有独立的作业执行记录,便于监控和重试。
1. 配置侧车文件读取步骤
先读取侧车文件,把所有待处理的文件名存入JobExecutionContext,让后续的Tasklet能获取到这个列表:
@Bean public Step sidecarFileReadingStep(StepBuilderFactory stepBuilderFactory, FlatFileItemReader<String> sidecarFileReader, ItemWriter<String> sidecarItemWriter) { return stepBuilderFactory.get("sidecarFileReadingStep") .<String, String>chunk(100) .reader(sidecarFileReader) .writer(sidecarItemWriter) .listener(new StepExecutionListenerSupport() { @Override public void afterStep(StepExecution stepExecution) { // 将读取到的文件名列表同步到Job级上下文 List<String> fileNames = stepExecution.getExecutionContext().get("fileNames", List.class); stepExecution.getJobExecution().getExecutionContext().put("fileNames", fileNames); } }) .build(); } // 侧车文件Reader:直接读取每行的文件名 @Bean public FlatFileItemReader<String> sidecarFileReader() { return new FlatFileItemReaderBuilder<String>() .name("sidecarFileReader") .resource(new FileSystemResource("path/to/your/sidecar.txt")) .lineMapper(new PassThroughLineMapper()) // 原样读取每行字符串 .build(); } // 侧车文件Writer:把文件名收集到Step级上下文 @Bean public ItemWriter<String> sidecarItemWriter() { return items -> { StepExecution stepExecution = StepSynchronizationManager.getContext().getStepExecution(); List<String> existingFiles = stepExecution.getExecutionContext().getOrDefault("fileNames", new ArrayList<>()); existingFiles.addAll(items); stepExecution.getExecutionContext().put("fileNames", existingFiles); }; }
2. 实现触发子作业的Tasklet
编写一个Tasklet,遍历Job上下文里的文件名,用JobLauncher启动你已经实现好的单个文件入库子作业:
@Component public class FileProcessingTasklet implements Tasklet { private final JobLauncher jobLauncher; private final Job singleFileIngestJob; // 注入你已经配置好的单个文件入库作业 public FileProcessingTasklet(JobLauncher jobLauncher, Job singleFileIngestJob) { this.jobLauncher = jobLauncher; this.singleFileIngestJob = singleFileIngestJob; } @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { JobExecution jobExecution = chunkContext.getStepContext().getJobExecution(); List<String> fileNames = jobExecution.getExecutionContext().get("fileNames", List.class); for (String fileName : fileNames) { // 传递文件名作为子作业参数,同时加timestamp保证作业实例唯一 JobParameters jobParameters = new JobParametersBuilder() .addString("inputFile", fileName) .addLong("timestamp", System.currentTimeMillis()) .toJobParameters(); // 启动子作业 JobExecution childJobExecution = jobLauncher.run(singleFileIngestJob, jobParameters); // 可选:如果子作业失败,终止主作业(根据业务需求调整) if (childJobExecution.getStatus() == BatchStatus.FAILED) { throw new JobExecutionException("处理文件 " + fileName + " 失败,终止主作业"); } } return RepeatStatus.FINISHED; } }
3. 配置主作业
把侧车文件读取步骤和文件处理Tasklet组合成主作业:
@Bean public Job mainJob(JobBuilderFactory jobBuilderFactory, Step sidecarFileReadingStep, FileProcessingTasklet fileProcessingTasklet, StepBuilderFactory stepBuilderFactory) { Step fileProcessingStep = stepBuilderFactory.get("fileProcessingStep") .tasklet(fileProcessingTasklet) .build(); return jobBuilderFactory.get("mainJob") .start(sidecarFileReadingStep) .next(fileProcessingStep) .build(); }
方案二:将单个文件处理作为可复用步骤循环执行
如果不想嵌套作业,也可以把「单个文件入库」做成一个可复用的步骤,通过自定义逻辑循环触发这个步骤,每次传递不同的文件名参数。不过这种方式的作业执行记录会合并在一起,监控和重试的粒度不如方案一清晰。
核心思路:
- 读取侧车文件得到文件名列表
- 遍历列表,通过
JobLauncher启动包含单个文件处理步骤的作业,每次传递不同的文件名参数
关键注意事项
- 作业参数唯一性:启动子作业/步骤时必须添加唯一参数(比如timestamp),否则Spring Batch会认为是同一个作业实例,不会重复执行
- 异常处理:根据业务需求决定子作业失败时,是终止主作业还是跳过失败文件继续处理
- 资源清理:处理完成后可以添加步骤删除侧车文件或标记已处理文件,避免重复执行
内容的提问来源于stack exchange,提问作者Stoyan
相关产品推荐
相关产品推荐

