[SpringBatch 실습] 17. JobFlowBuilder 흐름 제어하기 (Step 성공/실패에 따라 분기처리)

반응형
728x90
반응형

Job 생성

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/*
--job.name=flowTestJob
 */
@Configuration
@RequiredArgsConstructor
public class FlowJobTestConfiguration {

    // job 생성
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job flowTestJob() {
        return this.jobBuilderFactory.get("flowTestJob")
                .start(flowTestStep1())
                    .on("COMPLETED")
                    .to(flowTestStep3())
                .from(flowTestStep1())
                    .on("FAILED")
                    .to(flowTestStep2())
                .end()
                .build();
    }

    @Bean
    public Step flowTestStep1() {
        return stepBuilderFactory.get("flowTestStep1")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                        System.out.println("flowTestStep1");

                        // 고의 에러 발생
                        throw new RuntimeException("step1 was failed");
                        // return RepeatStatus.FINISHED;
                    }
                })
                .build();
    }

    @Bean
    public Step flowTestStep2() {
        return stepBuilderFactory.get("flowTestStep2")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                        System.out.println("flowTestStep2");
                        return RepeatStatus.FINISHED;
                    }
                })
                .build();
    }

    @Bean
    public Step flowTestStep3() {
        return stepBuilderFactory.get("flowTestStep3")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                        System.out.println("flowTestStep3");
                        return RepeatStatus.FINISHED;
                    }
                })
                .build();
    }
}

1) flowTestJob()을 보자.

@Bean
public Job flowTestJob() {
    return this.jobBuilderFactory.get("flowTestJob")
            /* 각각의 스텝은 독립적으로 생성되어 Tasklet 이 생성된다.
             * Step 은 자기만의 Tasklet 을 가진다. */
            .start(flowTestStep1())
                // 위 스텝이 성공하게되면, step3 번을 실행시키고,
                // 위 스텝이 실패하게되면, step2 번을 실행시켜보자.
                .on("COMPLETED")
                .to(flowTestStep3())
            .from(flowTestStep1())
                // STEP1 : FAILED, STEP2 : COMPLETED
                .on("FAILED")
                .to(flowTestStep2()) // step1 이 실패했어도 JOB은 COMPLETED
            .end()
            .build();
}

 

실행흐름 
Step String pattern Next Step
flowTestStep1() COMPLETED flowTestStep3()
FAILED flowTestStep2()

 

 

 

flowTestStep1()을 고의로 에러를 발생시키자.

@Bean
public Step flowTestStep1() {
    return stepBuilderFactory.get("flowTestStep1")
            .tasklet(new Tasklet() {
                @Override
                public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                    System.out.println("flowTestStep1");

                    // 고의 에러 발생
                    throw new RuntimeException("step1 was failed");
                    // return RepeatStatus.FINISHED;
                }
            })
            .build();
}

 

실행결과
flowTestStep1
flowTestStep2

 

실행흐름

위에서 정리했던 실행흐름을 보자.

Step String pattern Next Step
flowTestStep1() COMPLETED flowTestStep3()
FAILED flowTestStep2()

flowTestStep1()의 EXIT_CODE가 FAILED이기 때문에 flowTestStep2()가 수행되었다.

 

 

 

DB 테이블 조회

1) BATCH_JOB_EXECUTION

COLUMN VALUE
STATUS COMPLETED
EXIT_CODE COMPLETED
JOB_EXECUTION_ID 8

 

2) BATCH_JOB_INSTANCE

COLUMN VALUE
JOB_NAME flowTestJob

 

3) BATCH_STEP_EXECUTION

  • flowTestStep1
COLUMN VALUE
STEP_NAME flowTestStep1
EXIT_CODE FAILED
STATUS ABANDONED
JOB_EXECUTION_ID 8

 

  • flowTestStep2
COLUMN VALUE
STEP_NAME flowTestStep2
EXIT_CODE COMPLETED
STATUS COMPLETED
JOB_EXECUTION_ID 8

 

 

 

flowTestStep1()을 다시 정상 수행시키자.

@Bean
public Step flowTestStep1() {
    return stepBuilderFactory.get("flowTestStep1")
            .tasklet(new Tasklet() {
                @Override
                public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                    System.out.println("flowTestStep1");

                    // 고의 에러 발생
//                        throw new RuntimeException("step1 was failed");
                     return RepeatStatus.FINISHED;
                }
            })
            .build();
}

 

실행결과
flowTestStep1
flowTestStep3

 

실행흐름

위에서 정리했던 실행흐름을 보자.

Step String pattern Next Step
flowTestStep1() COMPLETED flowTestStep3()
FAILED flowTestStep2()

 

flowTestStep1()의 EXIT_CODE가 COMPLETED 이므로 flowTestStep3()이 실행되었다.

 

 

 

DB 테이블 조회

1) BATCH_JOB_EXECUTION

COLUMN VALUE
STATUS COMPLETED
EXIT_CODE COMPLETED
JOB_EXECUTION_ID 10

 

2) BATCH_JOB_INSTANCE

COLUMN VALUE
JOB_NAME flowTestJob

 

3) BATCH_STEP_EXECUTION

  • flowTestStep1
COLUMN VALUE
STEP_NAME flowTestStep1
EXIT_CODE COMPLETED
STATUS COMPLETED
JOB_EXECUTION_ID 10

 

  • flowTestStep3
COLUMN VALUE
STEP_NAME flowTestStep3
EXIT_CODE COMPLETED
STATUS COMPLETED
JOB_EXECUTION_ID 10

 

 

 

Step 오류 발생시키기 - 다른 방법

1) throw new Exception()

@Bean
public Step flowTestStep1() {
    return stepBuilderFactory.get("flowTestStep1")
            .tasklet(new Tasklet() {
                @Override
                public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                    System.out.println("flowTestStep1");

                    // 고의 에러 발생
                    throw new RuntimeException("step1 was failed");
//                        contribution.setExitStatus(ExitStatus.FAILED);
//                         return RepeatStatus.FINISHED;
                }
            })
            .build();
}

 

BATCH_STEP_EXECUTION

COLUMN VALUE
STEP_NAME flowTestStep1
EXIT_CODE ABANDONED
STATUS FAILED
JOB_EXECUTION_ID 10

 

2) EXIT_STATUS를 직접 설정

public Step flowTestStep1() {
    return stepBuilderFactory.get("flowTestStep1")
            .tasklet(new Tasklet() {
                @Override
                public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                    System.out.println("flowTestStep1");

                    contribution.setExitStatus(ExitStatus.FAILED);
                    return RepeatStatus.FINISHED;
                }
            })
            .build();
}

BATCH_STEP_EXECUTION

COLUMN VALUE
STEP_NAME flowTestStep1
EXIT_CODE FAILED
STATUS COMPLETED
JOB_EXECUTION_ID 10

 

 

ABANDONED vs FAILED

ABANDONED Job 실패 후, 재실행시 수행되지 않고 건너뛴다.
FAILED Job 실패 후, 재실행시 수행된다.

 

 

 

 

반응형

Designed by JB FACTORY