【问题标题】:Spring batch repeat step ending up in never ending loop春季批处理重复步骤以永无止境的循环结束
【发布时间】:2016-12-16 17:50:29
【问题描述】:

我有一个春季批处理作业,我想执行以下操作...

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 步的初始运行时中断。

但它确实继续给我如下消息,就好像它正在运行第 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 的链接,以免污染这篇文章。

http://pastebin.com/QhExNikm(作业配置)

http://pastebin.com/sscKKWRk(通用作业配置)

http://pastebin.com/Nn74zTpS(步骤执行监听器)

【问题讨论】:

    标签: spring spring-batch


    【解决方案1】:

    从您的问题和您的代码中,我根据您检索到的日期数量(这发生在实际工作开始之前)推断出,您将针对您拥有日期的次数执行一个步骤。

    我建议更改设计。创建一个 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();
        }  
    }
    

    编辑:添加了“jobParameter”输入(方法也略有不同)

    在类路径的某处添加以下示例 .properties 文件:

    sql.statement="select * from awesome"
    

    并将以下注释添加到您的 JobDatesCreator 类

    @PropertySource("classpath:example.properties")
    

    您也可以提供特定的 sql 语句作为命令行参数。来自 spring 文档:

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

    有关更多信息,请参阅http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html

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

    @PropertySource("classpath:example.properties")
    public class JobDatesCreator {
    
        @Value("${sql.statement}")
        private String sqlStatement;
    
        @Autowired
        private CommonExportFromStagingDbJobConfig commonJobConfig; 
    
        private List<String> dates; 
    
        @PostConstruct
        private void init(){
            // Execute your logic here for getting the data you need.
            JdbcTemplate jdbcTemplate = new JdbcTemplate(commonJobConfig.onlineStagingDb);
            // acces to your sql statement provided in a property file or as a command line argument
            System.out.println("This is the sql statement I provided in my external property: " + sqlStatement);
    
            // for now..
            dates = new ArrayList<>();
            dates.add("date 1");
            dates.add("date 2");
        }
    
        public List<String> getDates() {
            return dates;
        }
    
        public void setDates(List<String> dates) {
            this.dates = dates;
        }
    }
    

    我还注意到您有很多可以很容易重构的重复代码。现在对于每个作家,你都有这样的东西:

    @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;
    }
    

    考虑改用这样的东西:

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

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

    【讨论】:

    • 感谢您的建议。我要接受它。我希望 JobDatesCreator 使用 jobParameter 作为 jdbc 查询的输入。我尝试向 JobDates 创建器添加组件,然后添加作业范围,但这似乎不起作用。我该怎么做?
    • 在代码中查看我的编辑。如果该方法不起作用,请显示您的新配置以及您现在如何接线我不知道您要做什么以及为什么它不起作用:)
    • 嗨@Sander_M。我花了很长时间试图为我完成这项工作。我不确定这是否可能。您在 JobDatesCreator 中看到我需要访问 jobParameter 而不是属性。即 Value("{jobParameters['" + JobParamConstants.PARAM_TO_DATE + "']}") 日期 jobToDate ,我需要在我的数据库查询中使用它。据我目前所知,我认为没有办法制作 JobDatesCreator JobScope 以便它可以访问作业参数,因为这意味着 JobConfig 中的 executeMyJob 也必须具有 JobScope,我认为这是不对的。我会继续努力。
    • 我已经发布了一个新问题stackoverflow.com/questions/38949030/…,因为我认为它值得一个新线程。
    • 在你的持续努力中做得很好,它并不总是像你想要的那么容易,我知道它有时会多么令人沮丧。我的回答深受@HansjoergWingeier 代码的启发,因此在他的回答中采用了相同的方法(或者我应该说,因此与他在我的回答中的方法相同;))。恐怕我无法在 jobParameters 部分为您提供帮助。如果您要为此创建一个新问题,请尝试将其真正最小化到问题的最小限度,并使复制问题变得容易。这需要一些努力,但得到好的答案的机会要高得多。
    【解决方案2】:

    根据我们对Spring batch execute dynamically generated steps in a tasklet 的讨论,我试图回答有关如何在作业实际执行之前访问jobParameter 的问题。

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

    最简单的解决方案是将接收自服务的作业参数存储为系统属性,然后在步骤 3 中构建作业时访问此属性。但如果多个用户启动同时工作。

    还有其他方法可以在 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;
    }
    

    这样,您可以使用标准的 Value- 或 ConfigurationProperties Annotations 正常访问属性。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-09-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多