관리 메뉴

나만의공간

Spring Batch #10 (Flow / Scope 의 이해) 본문

IT/Spring Batch

Spring Batch #10 (Flow / Scope 의 이해)

밥알이 2023. 10. 26. 10:40

FlowJob

기본개념

  • Step을 특정한 상태에 따라 흐름을 전환하도록 구성할 수 있으며, FlowJobBuilder에 의해 생성됩니다.
    • Step이 실패하더라도 Job은 실패로 끝나지 않도록 해야 하는 경우
    • Step이 성공했을때 다음에 실행해야 할 Step을 구분해야 할 경우
    • 특정 Step은 전혀 실행되지 않게 구성해야 하는 경우
  • Flow와 Job의 흐름을 구성하는데만 관여하고 실제 로직은 Step에서 이뤄집니다.
  • 내부적으로 SimpleFlow 객체를 포함하고 있으면 Job 실행시 호출 됩니다.

배치상태유형

  • FlowJob은 조건에 따라 분기되어 실행되는데 그에 대한 조건상태를 이용합니다.

BatchStatus

  • Batch Status는 JobExecution과 StepExecution의 속성으로 Job과 Step의 실행 상태를 나타냅니다.
    COMPLETED, STARTING, STARTED, STOPPING, STOPPED, FAILED, ABANDONED, UNKNOWN 총 8종류의 ENUM이 있습니다.
  • ANANDONED와 FAILED의 차이점은 FAILED는 실패 후 재실행 시 재시작이 되지만, ABANDONED는 재실행시 미실행 됩니다.
    • SimpleJob
      1. 마지막 Step의 BatchStatus 값을 Job의 최종 BatchStatus에 반영합니다.
      2. Step이 실패한 경우 해당 Step이 마지막 Step이 되어 Job의 BatchStatus값으로 반영합니다.
    • FlowJob
      1. Flow내 Step의 ExitStatus값을 FlowExecutionStatus에 반영합니다.
      2. 마지막 Flow의 FlowExecutionStatus값을 Job의 최종 BatchStatus값으로 반영합니다.

ExitStatus

  1. JobExecution과 StepExecution의 속성으로 Job과 Step의 실행 후 종료되는 상태를 나타냅니다.
    기본적으로 ExitStatus는 BatchStatus와 동일한 값으로 설정됩니다.
    임의의 값으로 다르게 설정할 수도 있습니다..
    • SimpleJob
      1. 마지막 Step의 ExitStatus 값을 Job의 최종 ExitStatus값으로 반영합니다.
    • FlowJob
      1. Flow내 Step의 ExitStatus값을 FlowExecutionStatus값으로 저장합니다.
      2. 마지막 Flow의 FlowExecutionStatus값을 Job의 최종 ExitStatus값으로 반영합니다.

FlowExecutionStatus

  • FlowExecution의 속성으로 Flow의 실행 후 최종 결과 상태를 나타냅니다.
  • Flow 내 Step이 실행되고 ExitStatus값을 FlowExecutionStatus값으로 저장하게 됩니다.
  • FlowJob의 BatchStatus에 관여합니다.
  • COMPLETED, STOPPED, FAILED, UNKNOWN의 상태가 있습니다.

API 소개

start() / next()

  • start에는 Flow와 Step이 모두 올수 있음.
  • Flow가 오게 되면 JobFlowBuilder가 반환되고, Step이 오면 SimpleJobBuilder가 반환됨
  • SimpleJobBuilder도 on을 지원하기 때문에 start에 step을 인자로 넣고 뒤에서 on을 사용하면 JobFlowBuilder가 반환됨
  • next는 Step, Flow, JobExecutionDecider 타입을 받을 수 있음.

Transition

  • Flow내 Step의 조건부 전환을 정의합니다.
  • Job의 API설정에서 on(String Pattern) 메서드를 호출하면 TransactionBuilder가 반환되어, Transition Flow를 구성할 수 있음.
  • Step의 종료상태(ExitStatus)가 어떤 Pattern과도 매칭되지 않으면 예외가 발생
  • API
    • on(String Pattern)
      1. Step의 실행 결과로 받는 종료상태(ExitStatus)와 매칭하는 패턴스키마
      2. Pattern과 ExitStatus와 매칭되면 다음 실행할 Step을 지정할 수 있음.
      3. 특수문자는 2가지만 허용합니다.
        1. *: 0개 이상의 문자와 매칭
        2. e.g: c*t은 cat, count와 매칭
        3. ?: 정확히 1개의 문자와 매칭
        4. e.g: c?t는 cat과 매칭, count는 매칭 불가
    • to()
      1. 다음 실행할 단계 지정
    • from()
      1. 이전 단계에서 정의한 Transition을 새롭게 추가해서 정의
      2. 앞선 분기 이외의 새로운 분기를 만든다고 생각하면 됨.
  • 중단 API
    • Flow가 실행되면 FlowExecutionStatus에 상태값이 저장되고, 최종적으로 Job의 BatchStatus와 ExitStatus에 반영되지만, Step의 BatchStatus와 ExitStatus에는 아무런 영향을 주지 않습니다.
    • stop()
      1. FlowExecutionStatus가 STOPPED 상태로 종료
      2. Job의 BatchStatus와 ExitStatus가 모두 Stopped로 종료
    • fail()
      1. FlowExecution가 FAILED상태로 종료
      2. Job의 BatchStatus와 ExitStatus가 모두 FAILED로 종료됩니다.
    • end()
      1. FlowExecution가 COMPLETED 상태로 종료
      2. Job의 BatchStatus와 ExitStatus가 모두 COMPLETED로 종료됩니다.
      3. Step의 ExitStatus가 FAILED이더라도 Job의 BatchStatus가 COMPLETED로 종료되도록 하여 Job의 재시작이 불가능
      4. 하나의 SimpleFlow 객체를 생성할 때도 사용됩니다.(flow를 다 정의하고 마지막에 붙인다고 생각) -> SimpleFlow는 뒤에서 설명합니다.
    • stopAndReStart(Step or Flow or JobExecutionDecider)
      1. stop()과 기본 흐름은 동일
      2. 특정 step에서 작업을 중단하도록 설정하면 중단 이전의 Step만 COMPLETED로 저장되고 이후의 Step은 실행되지 않고 STOPPED 상태로 종료
      3. job이 다시 실행됐을 때 실행해야 할 Step을 인자로 넘기면 이전에 COMPLETED로 저장된 Step은 건너뛰고 중단 이후 Step부터 시작

예시

package com.study.springbatch.domain.step10;

import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
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.flow.support.state.StepState;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Configuration
@RequiredArgsConstructor
@Slf4j
public class TenJobOne {

	private final JobBuilderFactory jobBuilderFactory;
	private final StepBuilderFactory stepBuilderFactory;

	@Bean
	public Job tenJobOneJob() {
		return jobBuilderFactory.get("tenJobOne")
			.start(step1())
				.on("COMPLETED")
				.to(step2())
				.on("FAILED")
				.to(step3())
			.from(step1())
				.on("FAILED")
				.end()
			.from(step2())
				.on("COMPLETED")
				.to(step4())
				.next(step5())
				.end()
			.incrementer(new RunIdIncrementer())
			.build();
	}

	@Bean
	public Step step5() {
		return stepBuilderFactory.get("tenStepFive")
			.tasklet((stepContribution, chunkContext) -> {
				log.info("tenStepFive 호출됨");
				return RepeatStatus.FINISHED;
			}).build();
	}

	@Bean
	public Step step4() {
		return stepBuilderFactory.get("tenStepFour")
			.tasklet((stepContribution, chunkContext) -> {
				log.info("tenStepFour 호출됨");
				return RepeatStatus.FINISHED;
			}).build();
	}

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

	@Bean
	public Step step2() {
		return stepBuilderFactory.get("tenStepTwo")
			.tasklet((stepContribution,chunkContext) -> {
			log.info("tenStepTwo 호출됨");
			// return RepeatStatus.FINISHED;
				throw new RuntimeException("tenStepTwo Failed");
		}).build();
	}

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

	}
}

위 코드에 흐름을 하나씩 설명하겠습니다.

  • step1 성공 => step2실패 => step3 진행
    • step1이 정상적으로 실행되고 step2실행도중 오류가 발생하면 .on("FAILED")에 의해서 step3를 실행하고 종료
  • step1 실패
    • .from(step1()).on("FAILED")에 의해 Step을 재구성하여 step1이 실패 하면 종료
  • step2 성공
    • .from(step2()).on("COMPLETED").to(step4()).next(step5())에 의해 step2가 성공하면 step4,step5 실팽

 

 

 

 

 

 

 

 

 

 

 

Comments