Batch Job Flow

Batch Step

Step์€ ์‹ค์ œ Batch ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. ์ฆ‰, ์‹ค์ œ ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ธฐ๋Šฅ์€ Step์— ๊ตฌํ˜„๋˜์–ด์žˆ๋‹ค.

์ด์ฒ˜๋Ÿผ Step ์—์„œ๋Š” Batch์—์„œ ์ฒ˜๋ฆฌํ•˜๊ณ ์ž ํ•˜๋Š” ๊ธฐ๋Šฅ๊ณผ ์„ค์ •์„ ๋ชจ๋‘ ํฌํ•จํ•˜๋Š” ์žฅ์†Œ๋ผ ์ƒ๊ฐํ•˜๋ฉด๋œ๋‹ค.

Next

.next()๋Š” ์ˆœ์ฐจ์ ์œผ๋กœ Step ๋“ค์„ ์—ฐ๊ฒฐ์‹œํ‚ฌ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

@Slf4j
@Configuration
@RequiredArgsConstructor
public class StepNextJobConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job stepNextJob(){
        return jobBuilderFactory.get("stepNextJob")
                .start(step1())
                .next(step2())
                .next(step3())
                .build();
    }

    @Bean
    public Step step1(){
        return stepBuilderFactory.get("step1")
                .tasklet((stepContribution, chunkContext) -> {
                    log.info(">>> this is step1");
                    return RepeatStatus.FINISHED;
                }).build();
    }

    @Bean
    public Step step2(){
        return stepBuilderFactory.get("step2")
                .tasklet((stepContribution, chunkContext) -> {
                    log.info(">>> this is step2");
                    return RepeatStatus.FINISHED;
                }).build();
    }

    @Bean
    public Step step3(){
        return stepBuilderFactory.get("step3")
                .tasklet((stepContribution, chunkContext) -> {
                    log.info(">>> this is step3");
                    return RepeatStatus.FINISHED;
                }).build();
    }

}

Flow

Next๋Š” ์ˆœ์ฐจ์ ์œผ๋กœ Step์˜ ์ˆœ์„œ๋ฅผ ์ œ์–ดํ•˜์ง€๋งŒ, ์•ž์˜ Step์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜๋ฉด, ๋’ค์˜ Step๋“ค์€ ์‹คํ–‰๋˜์ง€ ๋ชปํ•˜๊ฒŒ ๋œ๋‹ค. ํ•˜์ง€๋งŒ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์ •์ƒ ์ˆ˜ํ–‰์ธ ๊ฒฝ์šฐ์—” Step B๋กœ, ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋• Step C๋กœ ์ˆ˜ํ–‰ํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค.

์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ์— ๋Œ€๋น„ํ•ด ์กฐ๊ฑด๋ณ„๋กœ Step์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

    @Bean
    public Job stepNextConditionalJob(){
        return jobBuilderFactory.get("tepNextConditionalJob")
                .start(conditionalJobStep1())
                    .on("FAILED") // step1 FAILED ์ธ ๊ฒฝ์šฐ
                    .to(conditionalJobStep3()) // step3์œผ๋กœ ์ด๋™
                    .on("*") // step3์˜ ๊ฒฐ๊ณผ์™€ ์ƒ๊ด€์—†์ด
                    .end() // step3์œผ๋กœ ์ด๋™์‹œ Flow ์ข…๋ฃŒ
                .from(conditionalJobStep1()) // step1์—์„œ๋ถ€ํ„ฐ
                    .on("*") // FAILED๊ฐ€ ์•„๋‹Œ ๋ชจ๋“  ๊ฒฝ์šฐ์—
                    .to(conditionalJobStep2()) // step2๋กœ ์ด๋™
                    .next(conditionalJobStep3()) // step2๊ฐ€ ์ •์ƒ ์ข…๋ฃŒ์‹œ step3์œผ๋กœ ์ด๋™
                    .on("*") // step3์˜ ๊ฒฐ๊ณผ์™€ ์ƒ๊ด€์—†์ด
                    .end() // step3์œผ๋กœ ์ด๋™์‹œ flow ์ข…๋ฃŒ
                .end() // job ์ข…
                .build();
    }

.on()

  • ExitStatus ๋ฅผ ์ง€์ •ํ•œ๋‹ค.

  • * ์˜ ๊ฒฝ์šฐ ๋ชจ๋“  ExitStatus ๊ฐ€ ์ง€์ •๋œ๋‹ค.

.to()

  • ๋‹ค์Œ์œผ๋กœ ์ด๋™ํ•  Step์„ ์ง€์ •ํ•œ๋‹ค.

.from()

  • ์ƒํƒœ๊ฐ’์„ ๋ณด๊ณ  ์ผ์น˜ํ•˜๋Š” ์ƒํƒœ๋ผ๋ฉด to() ์— ํฌํ•จ๋œ step์„ ํ˜ธ์ถœํ•˜๋ฉฐ, ์ผ์ข…์˜ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์—ญํ• ์„ ํ•œ๋‹ค๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.

  • ์ถ”๊ฐ€๋กœ ์ด๋ฒคํŠธ๋ฅผ ์บ์น˜ํ•˜๋Š” ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•œ๋‹ค.

.end()

  • FlowBuilder๋ฅผ ๋ฐ˜ํ™˜

    • .on("*") ๋’ค์— ์žˆ๋Š” end()

    • FlowBuilder๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” end()์˜ ๊ฒฝ์šฐ ๊ณ„์†ํ•ด์„œ from()์„ ์ด์–ด๊ฐˆ ์ˆ˜ ์žˆ๋‹ค.

  • FlowBuilder ์ข…๋ฃŒ

    • build() ์•ž์— ์žˆ๋Š” end()

Flow ์ˆ˜ํ–‰ ํ•ด๋ณด๊ธฐ

		@Bean
    public Step conditionalJobStep1(){
        return stepBuilderFactory.get("step1")
                .tasklet((stepContribution, chunkContext) -> {
                    log.info(">>> this is step1");

                  	//  ExitStatus.FAILED๋กœ ์ง€์ •
                  	// ํ•ด๋‹น Status ๋กœ flow๊ฐ€ ์ง„ํ–‰๋œ๋‹ค.
                    stepContribution.setExitStatus(ExitStatus.FAILED);
                    return RepeatStatus.FINISHED;
                }).build();
    }
Job: [FlowJob: [name=stepNextConditionalJob]] launched with the following parameters: [{version=3}]
o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
.p.j.StepNextConditionalJobConfiguration : >>> this is step1
o.s.batch.core.step.AbstractStep         : Step: [step1] executed in 75ms
o.s.batch.core.job.SimpleStepHandler     : Executing step: [step3]
.p.j.StepNextConditionalJobConfiguration : >>> this is step3
o.s.batch.core.step.AbstractStep         : Step: [step3] executed in 19ms
o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=stepNextConditionalJob]] completed with the following parameters: [{version=3}] and the following status: [COMPLETED] in 252ms

ExitStatus.FAILED๋กœ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ step1 -> step3์ด ์ˆ˜ํ–‰๋˜๊ณ  ์ข…๋ฃŒ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

    @Bean
    public Step conditionalJobStep1(){
        return stepBuilderFactory.get("step1")
                .tasklet((stepContribution, chunkContext) -> {
                    log.info(">>> this is step1");

//                    stepContribution.setExitStatus(ExitStatus.FAILED);
                    return RepeatStatus.FINISHED;
                }).build();
    }
o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=stepNextConditionalJob]] launched with the following parameters: [{version=4}]
o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
.p.j.StepNextConditionalJobConfiguration : >>> this is step1
o.s.batch.core.step.AbstractStep         : Step: [step1] executed in 33ms
o.s.batch.core.job.SimpleStepHandler     : Executing step: [step2]
.p.j.StepNextConditionalJobConfiguration : >>> this is step2
o.s.batch.core.step.AbstractStep         : Step: [step2] executed in 28ms
o.s.batch.core.job.SimpleStepHandler     : Executing step: [step3]
.p.j.StepNextConditionalJobConfiguration : >>> this is step3
o.s.batch.core.step.AbstractStep         : Step: [step3] executed in 23ms
o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=stepNextConditionalJob]] completed with the following parameters: [{version=4}] and the following status: [COMPLETED] in 309ms

ExitStatus ์„ค์ •ํ•œ ๋ถ€๋ถ„์„ ์ฃผ์„ ์ฒ˜๋ฆฌํ•œ ํ›„ ์ˆ˜ํ–‰ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด step1 -> step2 -> step3 ์ˆœ์„œ๋Œ€๋กœ ์ˆ˜ํ–‰๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ Step์„ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋˜๋ฉด ๋‹ค์Œ ๋‘๊ฐ€์ง€ ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค.

  1. Step์ด ๋‹ด๋‹นํ•˜๋Š” ์—ญํ• ์ด 2๊ฐœ ์ด์ƒ์ด๋‹ค. ์‹ค์ œ ํ•ด๋‹น Step์ด ์ฒ˜๋ฆฌํ•ด์•ผํ•  ๋กœ์ง์™ธ์—๋„ ๋ถ„๊ธฐ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ExitStatus ์กฐ์ž‘์ด ํ•„์š”ํ•˜๋‹ค.

  2. ๋‹ค์–‘ํ•œ ๋ถ„๊ธฐ ๋กœ์ง ์ฒ˜๋ฆฌ์˜ ์–ด๋ ค์›€์ด ์žˆ๋‹ค. ExitStatus ๋ฅผ ์ปค์Šคํ…€ํ•˜๊ฒŒ ๊ณ ์น˜๋ ค๋ฉด Listener ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , Job Flow์— ๋“ฑ๋กํ•˜๋Š” ๋“ฑ ๋ฒˆ๊ฑฐ๋กœ์›€์ด ์กด์žฌํ•œ๋‹ค.

Decide

Spring Batch์—์„œ JobExecutionDecider๋Š” Step๋“ค์˜ Flow์†์—์„œ ๋ถ„๊ธฐ๋งŒ ๋‹ด๋‹นํ•˜๋Š” ํƒ€์ž…์ด๋‹ค.

@Slf4j
@Configuration
@RequiredArgsConstructor
public class DeciderJobConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job deciderJob(){
        return jobBuilderFactory.get("deciderJob")
                .start(startStep())
                .next(decider()) // ํ™€์ˆ˜ || ์ง์ˆ˜ ๊ตฌ๋ถ„
                .from(decider()) // decider์˜ ์ƒํƒœ๊ฐ€
                    .on("ODD") // ํ™€์ˆ˜๋ผ๋ฉด
                    .to(oddStep()) // oddStep ์‹คํ–‰
                .from(decider())// decider์˜ ์ƒํƒœ๊ฐ€
                    .on("EVEN") // ์ง์ˆ˜๋ผ๋ฉด
                    .to(evenStep()) // evenStep() ์‹คํ–‰
                .end() // builder ์ข…๋ฃŒ
                .build();
    }

    @Bean
    public Step startStep(){
        return stepBuilderFactory.get("startStep")
                .tasklet((stepContribution, chunkContext) -> {
                    log.info(">>> Start step");
                    return RepeatStatus.FINISHED;
                }).build();
    }

    @Bean
    public Step oddStep(){
        return stepBuilderFactory.get("oddStep")
                .tasklet((stepContribution, chunkContext) -> {
                    log.info(">>> ํ™€์ˆ˜");
                    return RepeatStatus.FINISHED;
                }).build();
    }

    @Bean
    public Step evenStep(){
        return stepBuilderFactory.get("evenStep")
                .tasklet((stepContribution, chunkContext) -> {
                    log.info(">>> ์ง์ˆ˜ ");
                    return RepeatStatus.FINISHED;
                }).build();
    }

    @Bean
    public JobExecutionDecider decider(){
        return new OddDecider();
    }

    public static class OddDecider implements JobExecutionDecider {

        @Override
        public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
            Random rand = new Random();

            int randomNum = rand.nextInt(50)+1;
            log.info("random number : {}", randomNum);

            if(randomNum % 2 == 0 ){
               	// Step์œผ๋กœ ์ฒ˜๋ฆฌ๊ฐ€ ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ฏ€๋กœ, FlowExecutionStatus๋กœ ์ƒํƒœ ๊ด€๋ฆฌ
                return new FlowExecutionStatus("EVEN");
            }else{
                return new FlowExecutionStatus("ODD");
            }
        }
    }
}
o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=deciderJob]] launched with the following parameters: [{version=1}]
o.s.batch.core.job.SimpleStepHandler     : Executing step: [startStep]
s.b.p.jobs.DeciderJobConfiguration       : >>> Start step
o.s.batch.core.step.AbstractStep         : Step: [startStep] executed in 45ms
s.b.p.jobs.DeciderJobConfiguration       : random number : 13
o.s.batch.core.job.SimpleStepHandler     : Executing step: [oddStep]
s.b.p.jobs.DeciderJobConfiguration       : >>> ํ™€์ˆ˜
o.s.batch.core.step.AbstractStep         : Step: [oddStep] executed in 18ms


o.s.b.a.b.JobLauncherApplicationRunner   : Running default command line with: [version=3]
o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=deciderJob]] launched with the following parameters: [{version=3}]
o.s.batch.core.job.SimpleStepHandler     : Executing step: [startStep]
s.b.p.jobs.DeciderJobConfiguration       : >>> Start step
o.s.batch.core.step.AbstractStep         : Step: [startStep] executed in 31ms
s.b.p.jobs.DeciderJobConfiguration       : random number : 50
o.s.batch.core.job.SimpleStepHandler     : Executing step: [evenStep]
s.b.p.jobs.DeciderJobConfiguration       : >>> ์ง์ˆ˜ 

๊ณ„์†ํ•ด์„œ ์ˆ˜ํ–‰ํ•˜๋ฉด, ํ™€์ˆ˜์™€ ์ง์ˆ˜๊ฐ€ ๋ฒˆ๊ฐˆ์•„๊ฐ€๋ฉด์„œ ์ˆ˜ํ–‰๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

BatchStatus vs ExitStatus

BatchStatus

public enum BatchStatus {
    COMPLETED,
    STARTING,
    STARTED,
    STOPPING,
    STOPPED,
    FAILED,
    ABANDONED,
    UNKNOWN;
  ...

Job ํ˜น์€ Step์˜ ์‹คํ–‰ ๊ฒฐ๊ณผ๋ฅผ Spring์— ๊ธฐ๋กํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” Enum์ด๋‹ค.

ExitStatus

public class ExitStatus implements Serializable, Comparable<ExitStatus> {
    public static final ExitStatus UNKNOWN = new ExitStatus("UNKNOWN");
    public static final ExitStatus EXECUTING = new ExitStatus("EXECUTING");
    public static final ExitStatus COMPLETED = new ExitStatus("COMPLETED");
    public static final ExitStatus NOOP = new ExitStatus("NOOP");
    public static final ExitStatus FAILED = new ExitStatus("FAILED");
    public static final ExitStatus STOPPED = new ExitStatus("STOPPED");
  ...
}

Step์˜ ์‹คํ–‰ ํ›„ ์ƒํƒœ๋ฅผ ๋งํ•˜๋ฉฐ, ExitStatus๋Š” Enum์ด ์•„๋‹ˆ๋‹ค.

Custom ExitStatus

๋ณธ์ธ๋งŒ์˜ custom ExitStatus๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค.

.start(step1())
    .on("FAILED")
    .end()
.from(step1())
    .on("COMPLETED WITH SKIPS")
    .to(errorPrint1())
    .end()
.from(step1())
    .on("*")
    .to(step2())
    .end()
  • step1 FAILED -> job ์‹คํŒจ

  • step1 ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜์–ด, COMPLETED WITH SKIPS ๋กœ ์ข…๋ฃŒ

  • step1 ์„ฑ๊ณต -> step2 ์„ฑ๊ณต

public class SkipCheckingListener extends StepExecutionListenerSupport {

    public ExitStatus afterStep(StepExecution stepExecution) {
        String exitCode = stepExecution.getExitStatus().getExitCode();
      
      	// step์ด ์ž˜ ์ˆ˜ํ–‰๋˜๊ณ , skip ํšŸ์ˆ˜๊ฐ€ 0๋ณด๋‹ค ํฐ ๊ฒฝ์šฐ์— COMPLETED WITH SKIPS return
        if (!exitCode.equals(ExitStatus.FAILED.getExitCode()) && 
              stepExecution.getSkipCount() > 0) {
            return new ExitStatus("COMPLETED WITH SKIPS");
        }
        else {
            return null;
        }
    }
}

์ฐธ๊ณ 

Last updated