You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
}

方案二:将单个文件处理作为可复用步骤循环执行

如果不想嵌套作业,也可以把「单个文件入库」做成一个可复用的步骤,通过自定义逻辑循环触发这个步骤,每次传递不同的文件名参数。不过这种方式的作业执行记录会合并在一起,监控和重试的粒度不如方案一清晰。

核心思路:

  1. 读取侧车文件得到文件名列表
  2. 遍历列表,通过JobLauncher启动包含单个文件处理步骤的作业,每次传递不同的文件名参数

关键注意事项

  • 作业参数唯一性:启动子作业/步骤时必须添加唯一参数(比如timestamp),否则Spring Batch会认为是同一个作业实例,不会重复执行
  • 异常处理:根据业务需求决定子作业失败时,是终止主作业还是跳过失败文件继续处理
  • 资源清理:处理完成后可以添加步骤删除侧车文件或标记已处理文件,避免重复执行

内容的提问来源于stack exchange,提问作者Stoyan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:19:46