spring batch(3) Job Flow

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
	private Step step1() {
		// TODO Auto-generated method stub
		return stepBuilderFactory.get("step1")
				.tasklet((contribution, chunkContext) -> {
					log.info(">>>>>>> This is Step1");
					return RepeatStatus.FINISHED;
				})
				.build();
	}

	@Bean
	private Step step2() {
		// TODO Auto-generated method stub
		return stepBuilderFactory.get("step2")
				.tasklet((contribution, chunkContext) -> {
					log.info(">>>>>>> This is Step2");
					return RepeatStatus.FINISHED;
				})
				.build();
	}

	@Bean
	private Step step3() {
		// TODO Auto-generated method stub
		return stepBuilderFactory.get("step3")
				.tasklet((contribution, chunkContext) -> {
					log.info(">>>>>>> This is Step3");
					return RepeatStatus.FINISHED;
				})
				.build();
	}
}

지정한 Batch Job만 실행되도록 설정

//src/main/resources/application.properties

spring.batch.job.names: ${job.name:NONE}
  • Spring Batch가 실행될 때, Program arguments로 job.name 값이 넘어오면 해당 값과 일치하는 Job만 실행
  • 코드의 의미는 job.name이 있으면 job.name 값을 할당하고 없으면 NONE을 할당하겠다는 의미
  • spring.batch.job.names에 NONE이 할당되면 어떤 배치도 실행하지 않겠다는 의미 (값이 없을 때 모든 배치가 실행되지 않도록 막는 역할)

실제 운영 환경에서는 java -jar batch-application.jar --job.name=simpleJob 과 같이 배치를 실행함

조건별 흐름 제어(flow)

  • next()가 순차적으로 Step의 순서를 제어할 때 앞의 step에서 오류가 발생했을 때 나머지 뒤에 있는 step들은 실행되지 못함
  • 상황에 따라 조건별로 Step을 사용하기 위해 흐름 제어를 사용함
@Slf4j
@Configuration
@RequiredArgsConstructor
public class StepNextConditionalJobConfiguration {

	private final JobBuilderFactory jobBuilderFactory;
	private final StepBuilderFactory stepBuilderFactory;
	
	@Bean
	public Job stepNextConditionalJob() {
		return jobBuilderFactory.get("stepNextConditionalJob")
				.start(conditionalJobStep1())
					.on("FAILED")
					.to(conditionalJobStep3())
					.on("*")
					.end()
				.from(conditionalJobStep1())
					.on("*")
					.to(conditionalJobStep2())
					.next(conditionalJobStep3())
					.on("*")
					.end()
				.end()
				.build();
	}


	@Bean
	public Step conditionalJobStep1() {
		// TODO Auto-generated method stub
		return stepBuilderFactory.get("step1")
				.tasklet((contribution, chunkContext) -> {
					log.info(">>>> This is stepNextConditionalJob Step1");
					contribution.setExitStatus(ExitStatus.FAILED);
					return RepeatStatus.FINISHED;
				})
				.build();
	}
	
	@Bean
	public Step conditionalJobStep2() {
		// TODO Auto-generated method stub
		return stepBuilderFactory.get("conditionalJobStep2")
				.tasklet((contribution, chunkContext) -> {
					log.info(">>>> This is stepNextConditionalJob Step2");
					return RepeatStatus.FINISHED;
				})
				.build();
	}
	
	@Bean
	public Step conditionalJobStep3() {
		// TODO Auto-generated method stub
		return stepBuilderFactory.get("conditionalJobStep3")
				.tasklet((contribution, chunkContext) -> {
					log.info(">>>> This is stepNextConditionalJob Step3");
					return RepeatStatus.FINISHED;
				})
				.build();
	}
}

on()

  • 캐치할 ExitStatus 지정
  • * 일 경우 모든 ExitStatus가 지정됨

to()

  • 다음으로 이동할 Step 지정

from()

  • 일종의 이벤트 리스너 역할
  • 상태값을 보고 일치하는 상태라면 to() 에 포함된 step 을 호출함
  • step1의 이벤트 캐치가 FAILED로 되어있는 상태에서 추가로 이벤트 캐치하려면 from() 을 써야만 함

end()

  • end는 FlowBuilder를 반환하는 end와 FlowBuilder를 종료하는 end 2개가 있음
  • on("*") 뒤에 있는 end는 FlowBuilder를 반환하는 end
  • build() 앞에 있는 end는 FlowBuilder를 종료하는 end
  • FlowBuilder를 반환하는 end 사용시 계속해서 from 을 이어갈 수 있음

Batch Status vs. Exit Status

  • BatchStatus는 Job 또는 Step의 실행결과를 Spring에서 기록할 때 사용하는 Enum
  • BatchStatus로 사용되는 값은 COMPLETED, STARTING, STARTED, STOPPING, STOPPED, FAILED, ABANDONED, UNKNOWN
  • on() 메서드가 참조하는 값은 Step의 ExitStatus(Step의 실행 후 상태)
  • Spring Batch는 기본적으로 ExitStatus의 exitCode는 Step의 BatchStatus와 같도록 설정돼 있음

ExitCode 커스터마이징

.start(step1())
    .on("FAILED")
    .end()
.from(step1())
    .on("COMPLETED WITH SKIPS")
    .to(errorPrint1())
    .end()
.from(step1())
    .on("*")
    .to(step2())
    .end()
public class SkipCheckingListener extends StepExecutionListenerSupport {

    public ExitStatus afterStep(StepExecution stepExecution) {
        String exitCode = stepExecution.getExitStatus().getExitCode();
        if (!exitCode.equals(ExitStatus.FAILED.getExitCode()) && 
              stepExecution.getSkipCount() > 0) {
            return new ExitStatus("COMPLETED WITH SKIPS");
        }
        else {
            return null;
        }
    }
}
  • StepExecutionListener 에서 먼저 Step이 성공적으로 수행되었는지 확인하고, StepExecution의 skip 횟수가 0보다 클 경우 COMPLETED WITH SKIPS 의 exitCode를 갖는 ExitStatus를 반환

Decide

  • 위 방식의 문제점
    • Step이 담당하는 역할이 2개 이상이 됨, 실제 해당 Step이 처리해야할 로직외에도 분기처리를 시키기 위해 ExitStatus 조작이 필요함
    • 다양한 분기 로직 처리의 어려움, ExitStatus를 커스텀하게 고치기 위해서는 Listener를 생성하고 Job Flow에 등록하는 번거로움이 존재함
  • 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())
					.on("ODD")
					.to(oddStep())
				.from(decider())
					.on("EVEN")
					.to(evenStep())
				.end()
				.build();
	}
	
	@Bean
	public Step startStep() {
		return stepBuilderFactory.get("startStep")
				.tasklet((contribution, chunkContext) -> {
					log.info(">>>>> Start!");
					return RepeatStatus.FINISHED;
				})
				.build();
	}
	
	@Bean
	public Step evenStep() {
		return stepBuilderFactory.get("evenStep")
				.tasklet((contribution, chunkContext) -> {
					log.info(">>>>> 짝수");
					return RepeatStatus.FINISHED;
				})
				.build();
	}
	
	@Bean
	public Step oddStep() {
		return stepBuilderFactory.get("oddStep")
				.tasklet((contribution, 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) {
			// TODO Auto-generated method stub
			Random random = new Random();
			int randomNum = random.nextInt(50) + 1;
			log.info("랜덤숫자 : {}", randomNum);
			
			if(randomNum % 2 ==0) {
				return new FlowExecutionStatus("EVEN");
			}else {
				return new FlowExecutionStatus("ODD");
			}
		}
	}
}

  • start() : Job Flow의 첫번째 Step 시작
  • next() : startStep 이후에 decider 를 실행
  • from() : 이벤트 리스너 역할, decider의 상태값을 보고 일치하는 상태라면 to() 에 포함된 step 호출
  • JobExecutionDecider 인터페이스를 구현한 OddDecider에서 랜덤하게 숫자를 생성하여 홀수/짝수에 따라 상태를 반환
  • Step으로 처리하는게 아니기 때문에 ExitStatus가 아닌 FlowExecutionStatus로 상태 관리

Categories:

Updated:

Comments