Spring 弹簧批处理重复步骤以永无止境循环结束

Spring 弹簧批处理重复步骤以永无止境循环结束,spring,spring-batch,Spring,Spring Batch,我有一个春季批处理工作,我想做以下 Step 1 - Tasklet - Create a list of dates, store the list of dates in the job execution context. Step 2 - JDBC Item Reader - Get list of dates from job execution context. Get element(0) in dates list.

我有一个春季批处理工作,我想做以下

Step 1 - 
   Tasklet - Create a list of dates, store the list of dates in the job execution context.

Step 2 - 
   JDBC Item Reader - Get list of dates from job execution context.
                      Get element(0) in dates list. Use is as input for jdbc query. 
                      Store element(0) date is job execution context 
                      Remove element(0) date from list of dates
                      Store element(0) date in job execution context                 
   Flat File Item Writer - Get element(0) date from job execution context and use for file name.

Then using a job listener repeat step 2 until no remaining dates in the list of dates.
我已经创建了作业,在第2步的第一次执行中它可以正常工作。但第二步并没有像我希望的那样重复。我知道这一点,因为当我调试代码时,它只在步骤2的初始运行时中断

然而,即使我知道它不是,它也会继续给我下面这样的消息,就好像它正在运行步骤2一样

2016-08-10 22:20:57.842  INFO 11784 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Duplicate step [readStgDbAndExportMasterListStep] detected in execution of job=[exportMasterListCsv]. If either step fails, both will be executed again on restart.
2016-08-10 22:20:57.846  INFO 11784 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [readStgDbAndExportMasterListStep]
这将在一个永无止境的循环中结束

有人能帮我弄清楚为什么我的stpe 2只运行一次,或者给我一个建议吗

提前谢谢

为了不污染这篇文章,我为我的代码添加了两个到PasteBin的链接

(作业配置)

(通用作业配置)


(Step execution listener)

从您的问题和代码中,我根据您检索的日期数量(这发生在实际作业开始之前)扣除,您将根据您有日期的次数执行步骤

我建议改变设计。创建一个java类,该类将日期作为列表,并基于该列表动态创建步骤。大概是这样的:

@EnableBatchProcessing
public class JobConfig {

    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    @Autowired
    private StepBuilderFactory stepBuilderFactory;  

    @Autowired
    private JobDatesCreator jobDatesCreator;

    @Bean
    public Job executeMyJob() {
        List<Step> steps = new ArrayList<Step>();
        for (String date : jobDatesCreator.getDates()) {
            steps.add(createStep(date));
        }

        return jobBuilderFactory.get("executeMyJob")
                .start(createParallelFlow(steps))
                .end()
                .build();       
    }

    private Step createStep(String date){
        return stepBuilderFactory.get("readStgDbAndExportMasterListStep" + date)
                .chunk(your_chunksize)
                .reader(your_reader)
                .processor(your_processor)
                .writer(your_writer)                                
                .build();       
    }   

    private Flow createParallelFlow(List<Step> steps) {
        SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
        // max multithreading = -1, no multithreading = 1, smart size = steps.size()
        taskExecutor.setConcurrencyLimit(1); 

        List<Flow> flows = steps.stream()
                .map(step -> new FlowBuilder<Flow>("flow_" + step.getName()).start(step).build())
                .collect(Collectors.toList());

        return new FlowBuilder<SimpleFlow>("parallelStepsFlow")
                .split(taskExecutor)
                .add(flows.toArray(new Flow[flows.size()]))
                .build();
    }  
}
@Bean
public FlatFileItemWriter<MasterList> division10MasterListFileWriter() {
    FlatFileItemWriter<MasterList> writer = new FlatFileItemWriter<>();
    writer.setResource(new FileSystemResource(new File(outDir, MerchHierarchyConstants.DIVISION_NO_10 )));
    writer.setHeaderCallback(masterListFlatFileHeaderCallback());
    writer.setLineAggregator(masterListFormatterLineAggregator());
    return writer;
}
并将以下注释添加到JobDatesCreator类中

@PropertySource("classpath:example.properties")
您还可以提供特定的sql语句作为命令行参数。从spring文档中:

您可以使用特定的命令行开关(例如java-jar)启动 app.jar--name=“Spring”)

欲了解更多信息,请参阅

获取日期的类(为什么要为此使用tasklet?)

@PropertySource(“classpath:example.properties”)
公共类JobDateCreator{
@值(“${sql.statement}”)
私有字符串语句;
@自动连线
私有CommonExportFromStagingDbJobConfig commonJobConfig;
私人名单日期;
@施工后
私有void init(){
//在此处执行逻辑以获取所需的数据。
JdbcTemplate JdbcTemplate=新的JdbcTemplate(commonjobcconfig.onlineStagingDb);
//访问属性文件或命令行参数中提供的sql语句
System.out.println(“这是我在外部属性中提供的sql语句:“+sqlStatement”);
//现在。。
日期=新的ArrayList();
日期。添加(“日期1”);
日期。添加(“日期2”);
}
公共列表getDates(){
返回日期;
}
公共作废设置日期(列表日期){
this.dates=日期;
}
}
我还注意到,您有很多重复的代码,可以很容易地进行重构。现在,对于每一位作家,你都有这样的东西:

@EnableBatchProcessing
public class JobConfig {

    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    @Autowired
    private StepBuilderFactory stepBuilderFactory;  

    @Autowired
    private JobDatesCreator jobDatesCreator;

    @Bean
    public Job executeMyJob() {
        List<Step> steps = new ArrayList<Step>();
        for (String date : jobDatesCreator.getDates()) {
            steps.add(createStep(date));
        }

        return jobBuilderFactory.get("executeMyJob")
                .start(createParallelFlow(steps))
                .end()
                .build();       
    }

    private Step createStep(String date){
        return stepBuilderFactory.get("readStgDbAndExportMasterListStep" + date)
                .chunk(your_chunksize)
                .reader(your_reader)
                .processor(your_processor)
                .writer(your_writer)                                
                .build();       
    }   

    private Flow createParallelFlow(List<Step> steps) {
        SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
        // max multithreading = -1, no multithreading = 1, smart size = steps.size()
        taskExecutor.setConcurrencyLimit(1); 

        List<Flow> flows = steps.stream()
                .map(step -> new FlowBuilder<Flow>("flow_" + step.getName()).start(step).build())
                .collect(Collectors.toList());

        return new FlowBuilder<SimpleFlow>("parallelStepsFlow")
                .split(taskExecutor)
                .add(flows.toArray(new Flow[flows.size()]))
                .build();
    }  
}
@Bean
public FlatFileItemWriter<MasterList> division10MasterListFileWriter() {
    FlatFileItemWriter<MasterList> writer = new FlatFileItemWriter<>();
    writer.setResource(new FileSystemResource(new File(outDir, MerchHierarchyConstants.DIVISION_NO_10 )));
    writer.setHeaderCallback(masterListFlatFileHeaderCallback());
    writer.setLineAggregator(masterListFormatterLineAggregator());
    return writer;
}
@Bean
公共FlatFileItemWriter分区10MasterListFileWriter(){
FlatFileItemWriter writer=新的FlatFileItemWriter();
writer.setResource(新文件系统资源(新文件(outDir,MerchHierarchyConstants.DIVISION_NO_10));
writer.setHeaderCallback(masterListFlatFileHeaderCallback());
writer.setLineAggregator(masterListFormatterLineAggregator());
返回作者;
}
考虑改用类似的方式:

public FlatFileItemWriter<MasterList> divisionMasterListFileWriter(String divisionNumber) {
    FlatFileItemWriter<MasterList> writer = new FlatFileItemWriter<>();
    writer.setResource(new FileSystemResource(new File(outDir, divisionNumber )));
    writer.setHeaderCallback(masterListFlatFileHeaderCallback());
    writer.setLineAggregator(masterListFormatterLineAggregator());
    return writer;
}
公共FlatFileItemWriter分区MasterListFileWriter(字符串分区号){
FlatFileItemWriter writer=新的FlatFileItemWriter();
writer.setResource(新文件系统资源(新文件(outDir,分区号));
writer.setHeaderCallback(masterListFlatFileHeaderCallback());
writer.setLineAggregator(masterListFormatterLineAggregator());
返回作者;
}

由于并非所有代码都可用于正确复制您的问题,因此此答案是解决您的问题的建议/指示

根据我们对的讨论,我试图回答在作业实际执行之前如何访问jobParameter的问题

我假设有restcall将执行批处理。一般来说,这需要采取以下步骤。 1.接收rest调用及其参数的一段代码 2.创建新的springcontext(有一些方法可以重用现有的上下文并再次启动作业,但在重用步骤、读卡器和编写器时会出现一些问题) 3.开展工作

最简单的解决方案是将从服务接收的jobparameter存储为系统属性,然后在步骤3中构建作业时访问该属性。但如果多个用户同时启动作业,这可能会导致问题

加载springcontext时,还有其他方法将参数传递到springcontext中。但这取决于您设置上下文的方式。 例如,如果在步骤2中直接使用SpringBoot,可以编写如下方法:

private int startJob(Properties jobParamsAsProps) {
  SpringApplication springApp = new SpringApplication(.. my config classes ..);
  springApp.setDefaultProperties(jobParamsAsProps);

  ConfigurableApplicationContext context = springApp.run();
  ExitCodeGenerator exitCodeGen = context.getBean(ExitCodeGenerator.class);
  int code = exitCodeGen.getExitCode();
  context.close();
  return cod;
}

通过这种方式,您可以使用标准值或ConfigurationProperties注释正常访问属性。

谢谢您的建议。我要买它。我希望JobDatesCreator使用jobParameter作为jdbc查询的输入。我尝试添加组件,然后将jobscope添加到JobDates creater,但这似乎不起作用。我该怎么做?请参见代码中的编辑。如果这种方法不起作用,请展示你的新配置以及你现在是如何连接的。我不知道你想做什么,为什么不起作用:)嗨@Sander\M。我花了很长一段时间试图让这项工作为我所用。我不确定这是否可能。您可以看到,在JobDatesCreator中,我需要访问jobParameter而不是属性。i、 e.Value(“{jobParameters['”+JobParamConstants.PARAM_TO_DATE+“']}”)日期为jobToDate,我需要在我的数据库查询中使用它。根据我目前的理解,我认为没有办法使JobDateCreator JobScope能够访问作业参数,因为这意味着JobConfig中的executeMyJob也必须具有JobScope,我认为这是不对的。我会继续努力的。我发布了一个新问题,因为我认为它值得一个新的帖子。在你持续努力的过程中做得很好,它并不总是像你想要的那么容易,我知道有时会有多沮丧。