복붙노트

[SPRING] 끊임없이 끝나는 루프에서 끝나는 Spring 배치 반복 단계

SPRING

끊임없이 끝나는 루프에서 끝나는 Spring 배치 반복 단계

나는 봄 일자리를 가지고 다음과 같이하고 싶다.

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]

이것은 결코 끝나지 않는 루프에서 끝납니다.

누군가 제 2 단계가 단 한 번만 돌아가는 이유를 알아 내거나 제안 할 수 있습니까?

미리 감사드립니다.

이 게시물을 오염시키지 않도록 내 코드 용 PasteBin에 대한 두 개의 링크를 추가했습니다.

http://pastebin.com/QhExNikm (작업 구성)

http://pastebin.com/sscKKWRk (공통 작업 구성)

http://pastebin.com/Nn74zTpS (단계 실행 수신기)

해결법

  1. ==============================

    1.Spring 배치에 대한 토론을 바탕으로 태스크 릿에서 동적으로 생성 된 단계를 실행합니다. 실제로 작업이 실행되기 전에 jobParameter에 액세스하는 방법에 대한 질문에 대답하려고합니다.

    Spring 배치에 대한 토론을 바탕으로 태스크 릿에서 동적으로 생성 된 단계를 실행합니다. 실제로 작업이 실행되기 전에 jobParameter에 액세스하는 방법에 대한 질문에 대답하려고합니다.

    나는 그 배치를 실행할 restcall이 있다고 가정한다. 일반적으로 다음 단계를 수행해야합니다. 1. 매개 변수로 나머지 호출을받는 코드 조각 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 주석을 사용하여 속성을 정상적으로 액세스 할 수 있습니다.

  2. ==============================

    2.귀하의 질문과 귀하의 코드에서 귀하가 검색 한 날짜의 양 (실제 작업이 시작되기 전에 발생)을 기준으로 날짜가있는 시간에 대한 단계를 실행합니다.

    귀하의 질문과 귀하의 코드에서 귀하가 검색 한 날짜의 양 (실제 작업이 시작되기 전에 발생)을 기준으로 날짜가있는 시간에 대한 단계를 실행합니다.

    나는 디자인 변경을 제안한다. 날짜를 목록으로 가져올 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();
        }  
    }
    

    편집 : 추가 "작업 매개 변수"입력 (약간 다른 접근 방식도)

    클래스 경로의 어딘가에 다음 예제 .properties 파일을 추가하십시오.

    sql.statement="select * from awesome"
    

    JobDatesCreator 클래스에 다음 주석을 추가합니다.

    @PropertySource("classpath:example.properties")
    

    특정 sql 문을 명령 줄 인수로 제공 할 수도 있습니다. 봄 문서에서 :

    자세한 내용은 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;
    }
    

    모든 코드가 문제를 올바르게 복제 할 수있는 것은 아니므로이 대답은 문제를 해결하기위한 제안 / 표시입니다.

  3. from https://stackoverflow.com/questions/38871808/spring-batch-repeat-step-ending-up-in-never-ending-loop by cc-by-sa and MIT license