6.Spring Batch의 Flow
Spring Batch

6.Spring Batch의 Flow

반응형

개요

  • Flow는 이전 수업에서 배웠던 개념들이 Job과 Step보다 좀 더 유연하다.
  • 이번 단원에서 FlowJob, SimpleFlow, FlowStep에 대하여 학습해보자

FlowJob 개념 및 API 소개

1. 기본개념

  • Step 을 순차적으로만 구성하는 것이 아닌 특정한 상태에 따라 흐름을 전환하도록 구성할 수 있으며 FlowJobBuilder 에 의해 생성된다
  • Step 이 실패 하더라도 Job 은 실패로 끝나지 않도록 해야 하는 경우
    • 한 Job에서 연속적인 Step이 있을
  • Step 이 성공 했을 때 다음에 실행해야 할 Step 을 구분해서 실행 해야 하는경우
  • 특정 Step은 전혀 실행되지 않게 구성 해야 하는 경우
  • Flow 와 Job 의 흐름을 구성하는데만 관여하고 실제 비즈니스 로직은 Step 에서 이루어진다
  • 내부적으로 SimpleFlow 객체를 포함하고 있으며 Job 실행 시 호출한다

2. SimpleJob vs FlowJob의 비교

  • SimpleJob과 다르게 FlowJob은 각 상황에 맞게 Job을 유연하게 실행할 수 있다.

3.JobFlowBuilder 살펴보기

  • Flow : start, from, next
  • Transition: on, to, stop(), fail(), end(), stopAndRestart

4.SimplerJobBuilder VS FlowBuilder 차이

  • SimpleJobBuilder 관련된 메소드는 start, next, on, split 4가지 뿐이다.
  • 그러나 Flow와 과련된 메소드는 더 많은 기능을 지원한다.

5.FlowBuilder와 TranstionBuilder 살펴보기

  • FlowBuilder는 최종적으로 SimpleFlow 객체를 생성한다.
  • TransitionBuilder는 어떤 Step으로 이동할 지(to), 정지(stop), 실패(fail), 종료, 재시작(startAndRestart())할 지 결정해주는 Builder이다.

예제코드

package com.example.springbatch_6_1_flowjobconcept;

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;

/**
 * packageName    : com.example.springbatch_6_1_flowjobconcept
 * fileName       : FlowJobConfiguration
 * author         : namhyeop
 * date           : 2022/07/26
 * description    :
 * step1의 성공이냐 실패냐에 따라 다른 Step이 실행되는 예제
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/07/26        namhyeop       최초 생성
 */
@RequiredArgsConstructor
@Configuration
public class FlowJobConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job batchJob(){
        return jobBuilderFactory.get("batchJob")
                .start(step1())
                //on은 step에서 반환하는 실행 결과를 개칭하여 매핑하는 역할을 한다. 반환 값으로는 TransitionBuilder 객체를 반환한다.
                .on("COMPLETED").to(step3())
                //from은 이전 단계에서 정의한 step의 flow를 추가적으로 정의한다.
                .from(step1())
                .on("FAILED").to(step2())
                //end는 build() 앞에 위치하면 FlowsBuilder를 종료하고 SimpleFlow 객체를 생성한다.
                .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("step1 has executed");
                        throw new RuntimeException("step1 was failed");
//(1)                        return RepeatStatus.FINISHED;
                    }
                }).build();
    }

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

    @Bean
    public Step step3(){
        return stepBuilderFactory.get("step3")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                        System.out.println("step3 has executed");
                        return RepeatStatus.FINISHED;
                    }
                }).build();
    }
}  
  • 1번 부분의 주석을 설정하고 해제하는 시도를 통해 다른 Step이 실행되는것을 확인해보자

FlowJob-start()/next()

1.Start와 next 사용 위치 파악하기

2.Step으로 Job을 구성하는 경우 VS Flow로 Job을 구성하는 경우

예제코드

package com.example.springbatch_6_2_flowjob;

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.job.builder.FlowBuilder;
import org.springframework.batch.core.job.flow.Flow;
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;

/**
 * packageName    : com.example.springbatch_6_2_flowjob
 * fileName       : StartNextConfiguration
 * author         : namhyeop
 * date           : 2022/07/26
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/07/26        namhyeop       최초 생성
 */

@RequiredArgsConstructor
@Configuration
public class StartNextConfiguration {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job batchJob() {
        return jobBuilderFactory.get("batchJob")
                .start(flowA())
                .next(step3())
                .next(flowB())
                .next(step6())
                .end()
                .build();
    }

    public Flow flowB() {
        FlowBuilder<Flow> flowBuilder = new FlowBuilder<>("flowB");
        flowBuilder.start(step4())
                .next(step5())
                .end();

        return flowBuilder.build();
    }

    @Bean
    public Flow flowA() {
        FlowBuilder<Flow> flowBuilder = new FlowBuilder<>("flowA");
        flowBuilder.start(step1())
                .next(step2())
                .end();
        return flowBuilder.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(">> step1 has executed");
                        return RepeatStatus.FINISHED;
                    }
                })
                .build();
    }

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

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

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

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

    public Step step6() {
        return stepBuilderFactory.get("step6")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                        System.out.println(">> step6 has executed");
                        return RepeatStatus.FINISHED;
                    }
                }).build();
    }
}
  • flow에 맞게 실행순서가 다르게 실행되는것을 확인하면 된다.
  • on 옵션을 부여하여 실패가 안나는 경우와 다르게 step 에서 실패할 경우 Job execution은 실패를 반환한다.

Transition-배치상태 유형(BatchStatus/ExitStatus/FlowExecutionStatus)

개요

  • Transition 자체로는 전환된다. 전이된다라는 의미를 가지고 있다.
  • ABC가 있을 경우 A의 상태에 따라 B로 전이 되거나 C로 전이되는 느낌과 같다.

1.배치 상태 유형 종류 3가지

  • BatchStatus
    • JobExecution 과 StepExecution의 속성으로 Job 과 Step 의 종료 후 최종 결과 상태가 무엇인지 정의
    • SimpleJob
      • 마지막 Step 의 BatchStatus 값을 Job 의 최종 BatchStatus 값으로 반영
        • 예를 들어 step1, step2,step3 중에 가장 마지막으로 실행한 step의 BacthStatus값을 반영한다는 의미이다.
      • Step 이 실패할 경우 해당 Step 이 마지막 Step 이 된다
    • FlowJob
      • Flow 내 Step 의 ExitStatus 값을 FlowExecutionStatus 값으로 저장
      • 마지막 Flow 의 FlowExecutionStatus 값을 Job 의 최종 BatchStatus 값으로 반영
    • BatchStatus 상태 값의 종류
      • COMPLETED, STARTING, STARTED, STOPPING, STOPPED, FAILED, ABANDONED, UNKNOWN
      • ABANDONED 는 처리를 완료했지만 성공하지 못한 단계와 재시작시 건너 뛰어야하는 단계

  • ExitStatus
    • JobExecution 과 StepExecution의 속성으로 Job 과 Step 의 실행 후 어떤 상태로 종료되었는지 정의
    • 기본적으로 ExitStatus 는 BatchStatus 와 동일한 값으로 설정된다
    • SimpleJob
      • 마지막 Step 의 ExitStatus 값을 Job 의 최종 ExitStatus 값으로 반영
    • FlowJob
      • Flow 내 Step 의 ExitStatus 값을 FlowExecutionStatus 값으로 저장
      • 마지막 Flow 의 FlowExecutionStatus 값을 Job 의 최종 ExitStatus 값으로 반영
    • ExitStatus의 상태 값의 종류
      • UNKNOWN, EXECUTING, COMPLETED, NOOP, FAILED, STOPPED
      • exitCode 속성으로 참조

💡 다양한 속성코드를 로직에 따라 마지막에 exitcode에 저장한다.

  • FlowExecutionStatus
    • FlowExecution 의 속성으로 Flow 의 실행 후 최종 결과 상태가 무엇인지 정의
    • Flow 내 Step 이 실행되고 나서 ExitStatus 값을 FlowExecutionStatus 값으로 저장
    • FlowJob 의 배치 결과 상태에 관여함
    • FlowExecutionStatus의 상태 값 종류
      • COMPLETED, STOPPED, FAILED, UNKNOWN

💡 4개의 상태 값 종류를 enum형태로 반환하는것을 볼 수 있다.

2.배치 상태의 흐름

package com.example.spring_batch_6_3_transition;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.builder.FlowBuilder;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * packageName    : com.example.spring_batch_6_3_transition
 * fileName       : BatchStatusExitStatusConfiguration
 * author         : namhyeop
 * date           : 2022/07/26
 * description    :
 * ExitStatus의 반환값을 통해 flowJob이 실행되는 예제
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/07/26        namhyeop       최초 생성
 */
@RequiredArgsConstructor
@Configuration
public class BatchStatusExitStatusConfiguration {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    /*
    @Bean
    public Job batchJob() {
        return this.jobBuilderFactory.get("batchJob")
                .start(step1())
                .next(step2())
                .build();
    }*/

    @Bean
    public Job batchJob() {
        return this.jobBuilderFactory.get("batchJob")
                .start(step1())
                .on("FAILED")
                .to(step2())
                .end()
                .build();
    }

    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println(">> step1 has executed");
                    contribution.setExitStatus(ExitStatus.FAILED);
                    return RepeatStatus.FINISHED;
                })
                .build();
    }

    @Bean
    public Step step2() {
        return stepBuilderFactory.get("step2")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println(">> step2 has executed");
                    return RepeatStatus.FINISHED;
                })
                .build();
    }
}
  • 이전 시간에 배운 on의 반환값에 따라 Flow가 실행되는 예제이다.

Transition-on()/to()/stop(),fail(),end(),stopAndRestart()

1.Transition API 종류

  • on의 매개변수인 Pattern은 Step의 EXItStatus 반환형인 Create, Fali, Exist, Stop을 넣는 자리이다.
  • 즉 최종 종류 코드의 값에 따라 로직을 분기할 수 있는것이다.

2.Transition의 개념

기본개념

  • Transition
    • Flow 내 Step 의 조건부 전환(전이)을 정의함
    • Job 의 API 설정에서 on(String pattern) 메소드를 호출하면 TransitionBuilder 가 반환되어 Transition Flow 를 구성할 수 있음
    • Step의 종료상태(ExitStatus) 가 어떤 pattern 과도 매칭되지 않으면 스프링 배치에서 예외을 발생하고 Job 은 실패
      • on을 사용한 이후 부터 TransactionBuilder는 반환이 되어있고 이후 매칭 정보가 없으므로 Exception이 발생한다.
      • 💡 on을 사용하고 다음에 to로 가는 로직을 안 작성해줘서 발생한 오류를 기억하자
    • transition은 구체적인 것부터 그렇지 않은 순서로 적용된다

API

  • on(String pattern)
    • Step의 실행 결과로 돌려받는 종료상태(ExitStatus)와 매칭하는 패턴 스키마, BatchStatus 와 매칭하는 것이 아님
    • pattern 과 ExitStatus 와 매칭이 되면 다음으로 실행할 Step 을 지정할 수 있다.
    • 특수문자는 두 가지만 허용
      • “*” : 0개 이상의 문자와 매칭, 모든 ExitStatus 와 매칭된다
      • ”?” : 정확히 1개의 문자와 매칭
      • ex) “c*t”는 “cat”과 “count”에 매칭되고, “c?t”는 “cat”에는 매칭되지만 “count”
  • to()
    • 다음으로 실행할 단계를 지정
  • from()
    • 이전 단계에서 정의한 Transition 을 새롭게 추가 정의함

3.Job 을 중단하거나 종료하는 Transition API

  • Flow 가 실행되면 FlowExecutionStatus 에 상태값이 저장되고 최종적으로 Job 의 BatchStatus 와 ExitStatus 에 반영된다.
  • Step 의 BatchStatus 및 ExitStatus 에는 아무런 영향을 주지 않고 Job 의 상태만을 변경한다
  • stop()
    • FlowExecutionStatus 가 STOPPED 상태로 종료되는 transition
    • Job 의 BatchStatus 와 ExitStatus 가 STOPPED 으로 종료됨
  • fail()
    • FlowExecutionStatus 가 FAILED 상태로 종료되는 transition
    • Job 의 BatchStatus 와 ExitStatus 가 FAILED 으로 종료됨
  • end()
    • FlowExecutionStatus 가 COMPLETED 상태로 종료 되는 transition
    • Job 의 BatchStatus 와 ExitStatus 가 COMPLETED 으로 종료됨
    • Step 의 ExitStatus 가 FAILED 이더라도 Job 의 BatchStatus 가 COMPLETED 로 종료하도록 가능하며 이 때 Job의 재시작은 불가능함
  • stopAndRestart(Step or Flow or JobExecutionDecider)
    • stop() transition 과 기본 흐름은 동일
    • 특정 step에서 작업을 중단하도록 설정하면 중단 이전의 Step 만 COMPLETED 저장되고 이후의 step 은 실행되지 않고 STOPPED 상태로 Job 종료
    • Job 이 다시 실행됐을 때 실행해야 할 step 을 restart 인자로 넘기면 이전에COMPLETED 로 저장된 step 은 건너뛰고 중단 이후 step 부터 시작한다

4.Transition 작업 실행 예제

  • 코드를 작성하기 전 각 Step들을 수행하는 Flow를 그림으로 표현한 예제이다.
  • 지금 FlowJob의 주황색 표시가 전부 Transition을 의미한다.
  • 가장 우측 Transtion 마름모 B임, *은 오타

  • 밑에 두 번째 사진을 보면 contribution.setExitStatus(ExitStatus.FAILED)가 있다.
  • EXITSTATUS 값을 FAILEDS로 설정하는것이다.(다른 응답 코드도 가능)
    • 즉 자기가 원하는 응답 코드를 내려줄 수 있다는것을 의미한다.

4.Transition 코드를 실제로 보고 분석하자

  • .from이 사실상 else if와 같은 역할이다. 차이점이 있다면 On을 사용할경우 TransitionBuilder가 반환되어진다. 그렇기 때문에 분기점을 지정안할 경우 TransitionBuilder의 내부값이 null이 되어서 예외가 발생한다. 그러므로 반드시 성공하여 진입하는 경우가 아니면 분기점을 지정해줘야 한다.
  • 그리고 FlowJob(On을 사용한경우)을 사용할 경우 step이 실패할 경우에도 사용자의 의도대로 흐름이 흘러간것이기 때문에 COMPLTED가 나온다.

💡 SIMPLEJOB과 FLOWJOB의 STEP의 성공 유무에 따른 status 상태의 차이점을 반드시 기억하자

💡 또한 From에 대해서 사용시 주의하자. 이것은 필자의 개인적 주의이다.
학습할 때 너무 많은 시행착오를 겪었다.
핵심은 step의 실행 지점의 재정의란걸 기억하자.
step에서 다른 step으로 흐름이 넘어갔다면 넘어간 흐름의 재정의이다.

예제 코드

package com.example.SpringBatch_6_3_Transition;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.builder.FlowBuilder;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * packageName    : com.example.spring_batch_6_3_transition
 * fileName       : BatchStatusExitStatusConfiguration
 * author         : namhyeop
 * date           : 2022/07/26
 * description    :
 * Transition을 활용한flowJob 테스트 예제
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/07/26        namhyeop       최초 생성
 */
@RequiredArgsConstructor
@Configuration
public class BatchStatusExitStatusConfiguration2 {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job batchJob(){
        return this.jobBuilderFactory.get("batchJob")
                .start(step1())
                    .on("FAILED")
                    .to(step2())
                    .on("FAILED")
                    .stop()
                .from(step1())
                    .on("*")
                    .to(step3())
                    .next(step4())
                    .next(step6())
//                    .end() //여기서 SimpleFlow 객체 생성
                .from(step2())
                    .on("*")
                    .to(step5())
                    .end()
                .build();
    }

    @Bean
    public Flow flow(){
        FlowBuilder<Flow> flowBuilder = new FlowBuilder<>("flow");
        flowBuilder.start(step3())
                .next(step4())
                .end();
        return flowBuilder.build();
    }

    @Bean
    public Step step1(){
        return stepBuilderFactory.get("step1")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println(">> step1 has executed");
//(1)
                contribution.setExitStatus(ExitStatus.FAILED);
                    return RepeatStatus.FINISHED;
                }).build();
    }

    @Bean
    public Step step2(){
        return stepBuilderFactory.get("step2")
                .flow(flow())
                .build();
    }

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

    @Bean
    public Step step4(){
        return stepBuilderFactory.get("step4")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println("step4 has executed");
                    return RepeatStatus.FINISHED;
                }).build();
    }

    @Bean
    public Step step5(){
        return stepBuilderFactory.get("step5")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println("step5 has executed");
                    return RepeatStatus.FINISHED;
                }).build();
    }

    @Bean
    public Step step6(){
        return stepBuilderFactory.get("step6")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println(">> step6 has executed");
                    return RepeatStatus.FINISHED;
                }).build();
    }

}
  • 주의 깊게 봐야할 점은 step1이 수행된 이후 step2의 Completed 반환 결과 이후로직이다.
  • Step2의 실패이기 때문에 Step2의 재정의 from 로직으로 이동한다는것을 잊지 말자

사용자 정의 EXitStatus

1.기본 개념

  • ExitStatus에 존재하지 않는 exitCod를 새롭게 정의해서 설정할 수 있다.
  • StepExecutionListener 의 afterStep() 메서드에서 Custom exitCode 생성 후 새로운 ExitStatus를 정의한다.
  • Step 실행 후 완료 시점에서 현재 exitCode 를 사용자 정의 exitCode 로 수정할 수 있음

  • 그림의 좌측은 new PassChekingListenr를 정의하고 그 안에 사용자 정의 exitCode를 정의하는것을 코드로 표현한 그림이다.
  • StepExcutionListener 인터페이스를 상속하여 afterStep영역에 사용자 정의 코드로 반환하게 치환하는것을 확인 할 수 있다.
  • 좌측 맨 아래 사진은 실제 afterStep 이라는 Spring Batch의 구현 클래스의 일부 코드를 가져온것이다.
  • 실제 Step의 실행 상태를 가져온뒤 StepExution에 저장하고 마지막으로 afterStep에 사용자 정의 exitCode가 있을 경우 저장하는것을 확인 할 수 있다.
  • 우측의 그림은 StepExutionListener 인터페이스를 재정의하였을 경우 사용자 정의 ExitStaus가 어떻게 반환되는지를 표현한 그림이다.
  • 만약 StepExutionListenr가 정의되지 않았다면 COMPLTED와 DO_PASS 문자열이 일치하지 않아 Step2가 실행된다.
  • 하지만 StepExutionLIster를 통해 새로운 ExiitStatus를 정의하고 ON 안의 응답 코드와 같게 구현한다면 Step3가 수행된다는것을 표현한 그림이다.

예제 코드

package com.example.springbatch_6_4_customexitstatus;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * packageName    : com.example.springbatch_6_4_customexitstatus
 * fileName       : CustomExitStatusConfiguration
 * author         : namhyeop
 * date           : 2022/07/30
 * description    :
 * ExitStatus를 Copplted, Stop 말고 사용자가 원하는 ExitStatus를 만드는 예제
 * 쉽께 말해서 ExitStaus의 커스텀을 만드는예제이다.
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/07/30        namhyeop       최초 생성
 */
@RequiredArgsConstructor
@Configuration
public class CustomExitStatusConfiguration {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job batchJob() {
        return this.jobBuilderFactory.get("batchJob")
                .start(step1())
                    .on("FAILED")
                    .to(step2())
                    .on("PASS")
                    .stop()
                .end()
                .build();
    }

    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println(">> step1 has executed");
                    contribution.getStepExecution().setExitStatus(ExitStatus.FAILED);
                    return RepeatStatus.FINISHED;
                }).build();
    }

    @Bean
    public Step step2() {
        return stepBuilderFactory.get("step2")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println(">>step2 ahs executed");
                    //아래 처럼 ExitStatus를 Completed로 바꿔줘다 Exeution의 실행결과는 FAILD이다.
                    //이러한 이유는 BatchJob에서 step2의 분기로직을 검증하는 on 단계에서 실패했기 때문이다.
                    //Execution의 실행결과가 마지막 Step의 성공이냐 실패냐의 전체원칙은 변하지 않으니 기억하자
//                    contribution.getStepExecution().setExitStatus(ExitStatus.COMPLETED);

                    return RepeatStatus.FINISHED;
                })
                .listener(new PassCheckingListener())
                .build();
    }
}
package com.example.springbatch_6_4_customexitstatus;

import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;

/**
 * packageName    : com.example.springbatch_6_4_customexitstatus
 * fileName       : PassCheckingListener
 * author         : namhyeop
 * date           : 2022/07/30
 * description    :
 * 사용자 정의 ExitStatus를 구현한 구현체, ExitStatus가 FAILDE가 아닐 경우 PASS를 반환한다.
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/07/30        namhyeop       최초 생성
 */
public class PassCheckingListener implements StepExecutionListener {
    @Override
    public void beforeStep(StepExecution stepExecution) {

    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        String exitCode = stepExecution.getExitStatus().getExitCode();
        if(!exitCode.equals(ExitStatus.FAILED.getExitCode())){
            return new ExitStatus("PASS");
        }
        return null;
    }
}
  • StepExecutionListenr를 정의한 PassCheckingListener를 사용하지 않고 실행하여 Execution이 FAILD가 나오는지 확인하자
  • StepExecutionListenr를 정의한 PassCheckingListener를 사용하여 실행하여 실행결과가 Job에서 설정한대로 Stop이 나오는지 확인하자

JobExecutionDecider

1. 기본개념

  • ExitStatus 를 조작하거나 StepExecutionListener 를 등록할 필요 없이 Transition 처리를 위한 전용 클래스
  • Step 과 Transiton 역할을 명확히 분리해서 설정 할 수 있음
  • Step 의 ExitStatus 가 아닌 JobExecutionDecider 의 FlowExecutionStatus 상태값을 새롭게 설정해서 반환함
    • JobFlow는 FlowExecutionStatus 값을 보고 판단하는데 결과적으로 FlowExecutionStatus의 값에 따라 Status 값을 반환한다.

2.구조

3.JobExecutionDecider 예제 분석

4.예제 코드

package com.example.springbatch_6_5_jobexecutiondecider;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * packageName    : com.example.springbatch_6_5_jobexecutiondecider
 * fileName       : JobExecutionDeciderConfiguration
 * author         : namhyeop
 * date           : 2022/07/31
 * description    :
 * JobExecutionDecider 예제.
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/07/31        namhyeop       최초 생성
 */
@Configuration
@RequiredArgsConstructor
public class JobExecutionDeciderConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job job(){
        return jobBuilderFactory.get("batchJob")
                .incrementer(new RunIdIncrementer())
                .start(step())
                .next(decider())
                .from(decider()).on("ODD").to(oddStep())
                .from(decider()).on("EVEN").to(evenStep())
                .end()
                .build();
    }

    @Bean
    public JobExecutionDecider decider(){
        return new CustomDecider();
    }
    public Step step() {
        return stepBuilderFactory.get("startStep")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println("This is the start tasklet");
                    return RepeatStatus.FINISHED;
                }).build();
    }

    @Bean
    public Step evenStep(){
        return stepBuilderFactory.get("evenStep")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println(">>EvenStep has executed");
                    return RepeatStatus.FINISHED;
                }).build();
    }

    @Bean
    public Step oddStep(){
        return stepBuilderFactory.get("oddStep")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println(">>OddStep has executed");
                    return RepeatStatus.FINISHED;
                }).build();
    }

}
package com.example.springbatch_6_5_jobexecutiondecider;

import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;

/**
 * packageName    : com.example.springbatch_6_5_jobexecutiondecider
 * fileName       : CustomDecider
 * author         : namhyeop
 * date           : 2022/07/31
 * description    :
 * CustomDecider 예제, count 숫자에 따란 반환 문자열이 달라지는 예제
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/07/31        namhyeop       최초 생성
 */
public class CustomDecider implements JobExecutionDecider {

    private int count = 0;
    @Override
    public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
        count++;
        System.out.println("count = " + count);
        if(count % 2 == 0){
            return new FlowExecutionStatus("EVEN");
        }else{
            return new FlowExecutionStatus("ODD");
        }
    }
}
  • CustomDecider의 count 초기 설정 값에 따라 oddStep, EvenStep중 실행될 Step이 결정된다.

FlowJob 아키텍쳐

1.FlowJob의 전체적인 흐름

  • FlowJob은 SimpleFlow에서 의해서 Handling 되는데 이건 뒤에서 알아본다

2.현재 학습중인 영역

예제코드

package com.example.springbatch_6_6_flowjob;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.builder.FlowBuilder;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * packageName    : com.example.springbatch_6_6_flowjob
 * fileName       : FlowJobConfiguration
 * author         : namhyeop
 * date           : 2022/08/02
 * description    :
 * FlowJob의 흐름을 확인하는 예제
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/08/02        namhyeop       최초 생성
 */
@RequiredArgsConstructor
@Configuration
public class FlowJobConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job job(){
        return jobBuilderFactory.get("batchJob")
                .start(flow())
                .next(step3())
                .end()
                .build();
    }

    @Bean
    public Flow flow(){
        FlowBuilder<Flow> flowBuilder = new FlowBuilder<>("flow");
        flowBuilder.start(step1())
                .next(step2())
                .end();
        return flowBuilder.build();
    }
    @Bean
    public Step step1(){
        return stepBuilderFactory.get("step1").tasklet((contribution, chunkContext) -> {
            System.out.println(">> step1 has executed");
            return RepeatStatus.FINISHED;
        }).build();
    }

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

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

SimplerFlow-개념 및 API 소개

1. 기본개념

  • 스프링 배치에서 제공하는 Flow 의 구현체로서 각 요소(Step, Flow, JobExecutionDecider) 들을 담고 있는 State 를 실행시키는 도메인 객체
  • FlowBuilder 를 사용해서 생성하며 Transition 과 조합하여 여러 개의 Flow 및 중첩 Flow 를 만들어 Job 을 구성할 수 있다

2. 구조

3.SimpleFlow 반환위치 설명

예제코드

package com.example.springbatch_6_7_simpleflow;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.builder.FlowBuilder;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * packageName    : com.example.springbatch_6_7_simpleflow
 * fileName       : SimpleFlowConfiguration
 * author         : namhyeop
 * date           : 2022/08/02
 * description    :
 * SimpleFlow 흐름을 확인하는 테스트 예제
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/08/02        namhyeop       최초 생성
 */

@Configuration
@RequiredArgsConstructor
public class SimpleFlowConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job job(){
        return jobBuilderFactory.get("batchJob")
                .start(flow())
                .next(step3())
                .end() // end에서 simpleflow를 생성하는것을 잊지 말자
                .build();
    }

    @Bean
    public Flow flow(){
        FlowBuilder<Flow> builder = new FlowBuilder<>("flow");
        builder.start(step1())
                .next(step2())
                .end();
        return builder.build();
    }

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

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

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

SImpleFlow 예제

1.SimpleFlow를 습득하기 위한 Flow속 Flow 예제

  • end에서 SimpleFlow가 생성되므로 하나의 Flow로 묶는 개념을 기억하자

예제코드

package com.example.springbatch_6_8_simpleflowexample;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.builder.FlowBuilder;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * packageName    : com.example.springbatch_6_8_simpleflowexample
 * fileName       : SimpleFlowConfiguration
 * author         : namhyeop
 * date           : 2022/08/02
 * description    :
 * SimpleFlow 심화예제
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/08/02        namhyeop       최초 생성
 */

@RequiredArgsConstructor
@Configuration
public class SimpleFlowConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job job() {
        return jobBuilderFactory.get("batchJob")
                .start(flow1())
                    .on("COMPLETED")
                    .to(flow2())
                .from(flow1())
                    .on("FAILED")
                    .to(flow3())
                .end()
                .build();
    }

    @Bean
    public Flow flow1() {
        FlowBuilder<Flow> builder = new FlowBuilder<>("flow");
        builder.start(step1())
                .next(step2())
                .end();
        return builder.build();
    }

    @Bean
    public Flow flow2() {
        FlowBuilder<Flow> builder = new FlowBuilder<>("flow2");
        builder.start(flow3())
                .next(step5())
                .next(step6())
                .end();
        return builder.build();
    }

    @Bean
    public Flow flow3() {
        FlowBuilder<Flow> builder = new FlowBuilder<>("flow3");
        builder.start(step3())
                .next(step4())
                .end();
        return builder.build();
    }

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

    @Bean
    public Step step2() {
        return stepBuilderFactory.get("step2")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println(">> step2 has executed");
                    //실패 로직 유도
//                    throw new RuntimeException("step2 was failed");
                    return RepeatStatus.FINISHED;
                }).build();
    }

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

    @Bean
    public Step step4() {
        return stepBuilderFactory.get("step4")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println(">> step4 has executed");
                    return RepeatStatus.FINISHED;
                }).build();
    }

    @Bean
    public Step step5() {
        return stepBuilderFactory.get("step5")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println(">> step5 has executed");
                    return RepeatStatus.FINISHED;
                }).build();
    }

    @Bean
    public Step step6() {
        return stepBuilderFactory.get("step6")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println(">> step6 has executed");
                    return RepeatStatus.FINISHED;
                }).build();
    }

}

SimpleFlow 아키텍쳐

1.SimpleFlow 아키텍쳐 분석

  • 우측이 좌측의 코드를 실행시켰을때 SimpleFlow 내부 모습이다.

2.State는 어떤 정보를 가지고 있는가?

3.Flow의 재귀적 흐름

4.다음 실행할 State 고르는과정

예제 코드

package com.example.springbatch_6_9_simpleflowarchitecture;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.builder.FlowBuilder;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * packageName    : com.example.springbatch_6_9_simpleflowarchitecture
 * fileName       : SimpleFlowConfiguration
 * author         : namhyeop
 * date           : 2022/08/02
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/08/02        namhyeop       최초 생성
 */
@Configuration
@RequiredArgsConstructor
public class SimpleFlowConfiguration {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job job(){
        return jobBuilderFactory.get("batchJob")
                .start(step1())
                .on("COMPLETED").to(step2())
                .from(step1())
                .on("FAILED").to(flow())
                .end()
                .build();
    }

    @Bean
    public Flow flow(){
        FlowBuilder<Flow> flowBuilder = new FlowBuilder<>("flow");
        flowBuilder.start(step2())
                .on("*")
                .to(step3())
                .end();
        return flowBuilder.build();
    }

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

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

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

FlowStep

1. 기본개념

  • Step 내에 Flow 를 할당하여 실행시키는 도메인 객체
  • flowStep 의 BatchStatus 와 ExitStatus 은 Flow 의 최종 상태값에 따라 결정된다.

2. API 소개

3.FlowStep 그림으로 구조화

예제 코드

package com.example.springbatch_6_10_flowstep;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.builder.FlowBuilder;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * packageName    : com.example.springbatch_6_10_flowstep
 * fileName       : FlowStepConfiguration
 * author         : namhyeop
 * date           : 2022/08/03
 * description    :
 * FlowStep 흐름
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/08/03        namhyeop       최초 생성
 */
@Configuration
@RequiredArgsConstructor
public class FlowStepConfiguration {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job job(){
        return jobBuilderFactory.get("batchJob")
                .start(flowStep())
                .next(step2())
                .build();
    }

    public Step flowStep(){
        return stepBuilderFactory.get("flowStep")
                .flow(flow())
                .build();
    }

    public Flow flow(){
        FlowBuilder<Flow> flowBuilder = new FlowBuilder<>("Flow");
        flowBuilder.start(step1()).end();
        return flowBuilder.build();
    }

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

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

@JobScope/@StepScope - 기본개념 및 설정

1. Scope

  • 스프링 컨테이너에서 빈이 관리되는 범위
  • singleton, prototype, request, session, application 있으며 기본은 singleton 으로 생성됨

2. 스프링 배치 스코프

  • @JobScope, @StepScope
    • Job과 Step의 빈 생성과 실행에 관여하는 스코프
    • 프록시 모드를 기본값으로 하는 스코프 - @Scope(value = "job", proxyMode = ScopedProxyMode.TARGET_CLASS)
    • 해당 스코프가 선언되면 빈이 생성이 어플리케이션 구동시점이 아닌 빈의 실행시점에 이루어진다
      • @Values를 주입해서 빈의 실행 시점에 값을 참조할 수 있으며 일종의 Lazy Binding 이 가능해 진다
        • Lazy Biding의 의미는 내가 지정한 어떤 변수 값이 Step또는 Job이 실행될때 결합된다는 의미이다.
      • @Value("##{jobParameters[파라미터명]}"), @Value("##{jobExecutionContext[파라미터명]”}), @Value("##{stepExecutionContext[파라미터명]”})
      • @Values 를 사용할 경우 빈 선언문에 @JobScope , @StepScope 를 정의하지 않으면 오류를 발생하므로 반드시 선언해야 함
  • 프록시 모드로 빈이 선언되기 때문에 어플리케이션 구동시점에는 빈의 프록시 객체가 생성되어 실행 시점에 실제 빈을 호출해 준다
  • 병렬처리 시 각 스레드 마다 생성된 스코프 빈이 할당되기 때문에 스레드에 안전하게 실행이 가능하다

3. @JobScope

  • Step 선언문에 정의한다
  • @Value : jobParameter, jobExecutionContext 만 사용가능

4. @StepScope

  • Tasklet이나 ItemReader, ItemWriter, ItemProcessor 선언문에 정의한다
  • @Value : jobParameter, jobExecutionContext, stepExecutionContext 사용가능

package com.example.springbatch_6_11_jobscopeandstepscope;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.JobScope;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * packageName    : com.example.springbatch_6_11_jobscopeandstepscope
 * fileName       : JobScope_StepScope_Configuration
 * author         : namhyeop
 * date           : 2022/08/03
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/08/03        namhyeop       최초 생성
 */

@Configuration
@RequiredArgsConstructor
public class JobScope_StepScope_Configuration {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job job() {
        return jobBuilderFactory.get("batchJob")
                .start(step1(null))
                .next(step2())
                .listener(new CustomJobListener())
                .build();
    }

    @Bean
    @JobScope
    public Step step1(@Value("##{jobParameters['message']}") String message) {
        System.out.println("message = " + message);
        return stepBuilderFactory.get("step1")
                .tasklet(tasklet1(null))
                .build();
    }

    @Bean
    public Step step2() {
        return stepBuilderFactory.get("step2")
                .tasklet(tasklet2(null))
                .listener(new CustomStepListener())
                .build();
    }

    @Bean
    @StepScope
    public Tasklet tasklet1(@Value("##{JobExecutionContext['name']}")String name){
        return (stepContribution, chunkContext) ->{
            System.out.println("name = " + name);
            System.out.println("tasklet1 has executed");
            return RepeatStatus.FINISHED;
        };
    }

    @Bean
    @StepScope
    public Tasklet tasklet2(@Value("##{stepExecutionContext['name2']}") String name2){
        return (stepContribution, chunkContext) ->{
            System.out.println("name2 = " + name2);
            System.out.println("tasklet2 has executed");
            return RepeatStatus.FINISHED;
        };
    }
}
package com.example.springbatch_6_11_jobscopeandstepscope;

import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;

/**
 * packageName    : com.example.springbatch_6_11_jobscopeandstepscope
 * fileName       : JobListener
 * author         : namhyeop
 * date           : 2022/08/03
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/08/03        namhyeop       최초 생성
 */
public class CustomJobListener implements JobExecutionListener {
    @Override
    public void beforeJob(JobExecution jobExecution) {
        jobExecution.getExecutionContext().put("name","user1");
    }

    @Override
    public void afterJob(JobExecution jobExecution) {

    }
}
package com.example.springbatch_6_11_jobscopeandstepscope;

import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;

/**
 * packageName    : com.example.springbatch_6_11_jobscopeandstepscope
 * fileName       : CustomStepListener
 * author         : namhyeop
 * date           : 2022/08/03
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/08/03        namhyeop       최초 생성
 */
public class CustomStepListener implements StepExecutionListener {
    @Override
    public void beforeStep(StepExecution stepExecution) {
        stepExecution.getExecutionContext().putString("name2", "user2");
    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        return null;
    }
}
  • 지정한 Parameter값이 다 들어가서 출력되는지 확인하자

@JobScope/@StepScope 아키텍쳐

1.Proxy 객체 생성

  • @JobScope , @StepScope 어노테이션이 붙은 빈 선언은 내부적으로 빈의 Proxy 객체가 생성된다
    • @JobScope
      • @Scope(value = "job", proxyMode = ScopedProxyMode.TARGET_CLASS)
    • @StepScope
      • @Scope(value = “step", proxyMode = ScopedProxyMode.TARGET_CLASS)
  • Job 실행 시 Proxy 객체가 실제 빈을 호출해서 해당 메서드를 실행시키는 구조

2.JobScope , StepScope

  • Proxy 객체의 실제 대상이 되는 Bean 을 등록, 해제하는 역할
  • 실제 빈을 저장하고 있는 JobContext, StepContext 를 가지고 있다

3.JobContext , StepContext

  • 스프링 컨테이너에서 생성된 빈을 저장하는 컨텍스트 역할
  • Job 의 실행 시점에서 프록시 객체가 실제 빈을 참조할 때 사용됨

4.프록시의 실행 흐름

예제코드

package com.example.springbatch_6_12_jobscopeandstepscopearchitecture;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.JobScope;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * packageName    : com.example.springbatch_6_12_jobscopeandstepscopearchitecture
 * fileName       : JobScope_StepScope_Configuration
 * author         : namhyeop
 * date           : 2022/08/04
 * description    :
 * JobScope와 StepScope 아케텍쳐 흐름을 확인하기 위한 예제
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/08/04        namhyeop       최초 생성
 */

@Configuration
@RequiredArgsConstructor
public class JobScope_StepScope_Configuration {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job job() {
        return jobBuilderFactory.get("batchJob")
                .start(step1(null))
                .build();
    }

    @Bean
    @JobScope
    public Step step1(@Value("##{JobParameters['message']}") String message) {
        System.out.println("message = " + message);
        return stepBuilderFactory.get("step1")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println(">> step1 has executed");
                    return RepeatStatus.FINISHED;
                })
                .build();
    }
}
반응형

'Spring Batch' 카테고리의 다른 글

8.Spring Batch의 Chunk와 ItemReader  (0) 2024.11.08
7.Spring Batch의 Chunk와 동작원리 살펴보기  (0) 2024.11.08
5.Spring Batch의 Step  (0) 2024.11.07
4.Spring Batch의 Job  (0) 2024.11.06
3.Spring Batch 도메인 이해하기  (0) 2024.11.02