3

I'm new to Spring batch. I've created a decider which returns a FlowExecutionStatus as "YES" / "NO". Based on the FlowExecutionStatus, I need to call step2() or step3().

In my below code, step2() is getting called before decider. How to modify the code so that decider will be called and based on the FlowExecutionStatus returned bu the decider, either step2() or step3() should be called. Please help.

@Autowired
private NumberDecider decider;

@Bean
public Job NumberLoaderJob() throws NumberFormatException, IOException {
    return jobBuilderFactory.get("numberLoaderJob").start(step1()).listener(new MyNumberJobListener())
            .next(decider).on("YES").to(step2())
            .from(decider).on("NO").to(step3()).end().build();
}

@Bean
public Step step1() {

    return stepBuilderFactory.get("step1").tasklet(new Tasklet() {

        @Override
        public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
            System.out.println("Exiting step1() execute()");
            return RepeatStatus.FINISHED;
        }
    }).build();
}

/**
 * Step 2
 *
 * @return
 * @throws NumberFormatException
 * @throws IOException
 */
@Bean
public Step step2() throws NumberFormatException, IOException {
    return stepBuilderFactory.get("step2").listener(new MyStepListener())
            .<OrderNumber, OrderNumber>chunk(Integer.valueOf(chunkSize)).faultTolerant()
            .listener(new MyChunkListener()).reader(new MyItemReader())
            .listener(new MyItemReaderListener()).writer(customItemWriter())
            .listener(new MyWriteListener()).build();
}
Paulo Merson
  • 13,270
  • 8
  • 79
  • 72

1 Answers1

5

You would need to to set the exit status in step1 so that the decider picks it up and make the decision:

@Bean
public Step step1() {

   return stepBuilderFactory.get("step1").tasklet(new Tasklet() {

       @Override
       public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
          System.out.println("Exiting step1() execute()");
          chunkContext.getStepContext().getStepExecution().setExitStatus(new ExitStatus("YES")); // or NO
          return RepeatStatus.FINISHED;
       }

    }).build();
}

EDIT: I thought that the decider should make the decision based on the exit status of step1, hence the previous sample. So adding a sample to show how to use a decider (after clarification):

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableBatchProcessing
public class MyJob {

    @Autowired
    private JobBuilderFactory jobs;

    @Autowired
    private StepBuilderFactory steps;

    @Bean
    public Step step1() {
        return steps.get("step1")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println("hello");
                    return RepeatStatus.FINISHED;
                })
                .build();
    }

    @Bean
    public JobExecutionDecider decider() {
        return (jobExecution, stepExecution) -> new FlowExecutionStatus("YES"); // or NO
    }

    @Bean
    public Step step2() {
        return steps.get("step2")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println("world");
                    return RepeatStatus.FINISHED;
                })
                .build();
    }

    @Bean
    public Step step3() {
        return steps.get("step3")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println("!!");
                    return RepeatStatus.FINISHED;
                })
                .build();
    }

    @Bean
    public Job job() {
        return jobs.get("job")
                .start(step1())
                .next(decider())
                    .on("YES").to(step2())
                    .from(decider()).on("NO").to(step3())
                    .end()
                .build();
    }

    public static void main(String[] args) throws Exception {
        ApplicationContext context = new AnnotationConfigApplicationContext(MyJob.class);
        JobLauncher jobLauncher = context.getBean(JobLauncher.class);
        Job job = context.getBean(Job.class);
        jobLauncher.run(job, new JobParameters());
    }

}

In this example, step1 is executed first, then the decider. If the decider returns YES, step2 is executed and if returns NO, step3 is executed.

Hope this helps.

Mahmoud Ben Hassine
  • 28,519
  • 3
  • 32
  • 50
  • Thanks for the quick response Mahmoud. I still see the same issue. Step 2 is executing first then step 1. @Bean public Job numberLoaderJob() throws NumberFormatException, IOException { return jobBuilderFactory.get("numberLoaderJob").start(step1()).listener(new NumberJobListener()) .on("YES").to(step2()).on("NO").to(step3()).end().build(); } – javatechguy_11232 Sep 04 '18 at 17:31
  • Could you please guide me here - https://stackoverflow.com/questions/65194027/duplicate-step-step2-detected-in-execution-of-job-job-if-either-step-fails ? – PAA Dec 08 '20 at 10:46