5.Spring Batch의 Step
Spring Batch

5.Spring Batch의 Step

반응형

StepBuilderFactory

1. StepBuilderFactory

  • StepBuilder 를 생성하는 팩토리 클래스로서 get(String name) 메서드 제공
  • StepBuilderFactory.get(“stepName")
  • “stepName” 으로 Step 을 생성

2. StepBuilder

  • Step을 구성하는 설정 조건에 따라 다섯 개의 하위 빌더 클래스를 생성하고 실제 Step 생성을 위임한다
  • TaskletStepBuilder
    • TaskletStep 을 생성하는 기본 빌더 클래스
  • SimpleStepBuilder
    • TaskletStep 을 생성하며 내부적으로 청크기반의 작업을 처리하는 ChunkOrientedTasklet 클래스를 생성한다
  • PartitionStepBuilder
    • PartitionStep 을 생성하며 멀티 스레드 방식으로 Job 을 실행
  • JobStepBuilder
    • JobStep 을 생성하여 Step 안에서 Job 을 실행한다
  • FlowStepBuilder
    • FlowStep 을 생성하여 Step 안에서 Flow 를 실행한다

3.SetpBuilder의 하위 Step의 생성 흐름도

  • tasklet()은 이전 예외에서 익명 객체를 만들어서 계속 사용해 왔다.
    • Tasklet이 사용되는 경우는 TaskleteStepBuildir가 사용된다.
  • 2번째로 chunk(chunkSize)와 chunk(competePolicy)를 사용해서 simplerStepBuilder 클래스를 생성한다. 둘 다 TaskleteStepBuilder를 생성하며각자 다른 StepBuilder를 생성한다고 이해하자
  • 세 번째로 Partiioner(StepName, Partioner)와 Patitioner(Step)을 이용해서 PartitionStepBuilder를 생성한다.
  • 네 번째로 flow(flow)를 사용해서 FlowStepBuilder를 생성한다.

4.StepBuilderFactory의 클래스 구조

  • JobBuilderFactory와 유사한 구조로 이루어져있다.
  • StepBuilder는 StepBuilderHelper를 상속한다.
  • CoomonStepProperties에는 공통으로 사용하는 정보가 저장되어있다.
  • StepBuilderFactory는 StepBuilderHelper를 참조하고 있다.
  • StepBuilderHelper는 자신의 메타데이터 정보를 StepJobRepository에 저장한다.
  • AbstractTaskletStepBuilder는 StepBuilderHelper를 참조한다.
  • 맨 밑에 위의 5개의 Builder(TaskletStepBuilder, SimplerStepBuilder, PartitionStepBuilder, JobStepBuilder, FlowStepBuilder)는 각각의 Step(TaskletStep, PartionStep, JobStep, FlowStep)을 생성한다.

예제 코드

  • 주석의 각각의 StepBuilder가 어떤 경우에 사용되는지만 확인하면 된다.
package com.example.springbatch_4_1_batchinitconfiguration;

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.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.*;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

/**
 * packageName    : com.example.springbatch_4_1_batchinitconfiguration
 * fileName       : StepBuilderconfiguration
 * author         : namhyeop
 * date           : 2022/07/25
 * description    :
 * StepBuilder Factory와 관련된 예제를 진행해보는 코드이다.
 * 개념에서는 StepBuilderFactory에 5개의 종류가 있다고 설명하였다. 5개의 StepBuilderFactory를 걸치는 과정을 확인해보자
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/07/25        namhyeop       최초 생성
 */
@Configuration
@RequiredArgsConstructor
public class StepBuilderConfiguration {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job batchJob(){
        return this.jobBuilderFactory.get("batchJob")
                .incrementer(new RunIdIncrementer())
                .start(step1())
                .next(step2())
//                .next(step3())
                .next(step4())
                .next(step5())
                .build();
    }

    //tasklket -> TaskletStepBuilder
    private Step step1() {
        return stepBuilderFactory.get("step1")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println("step1 has executed");
                    return RepeatStatus.FINISHED;
                })
                .build();
    }

    //chunk -> SimpleStepBuilder 사용
    @Bean
    public Step step2(){
        return stepBuilderFactory.get("step2")
                .<String, String>chunk(3)
                //데이터를 읽는 영역
                .reader(new ItemReader<String>() {
                    @Override
                    public String read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
                        return null;
                    }
                })
                //데이터 로직을 처리하는 영역
                .processor(new ItemProcessor<String, String>() {
                    @Override
                    public String process(String s) throws Exception {

                        return null;
                    }
                })
                //로직을 처리한 데이터를 쓰는 영역
                .writer(list->{})
                .build();
    }

    //partiioner -> PartitionStepBuilder 반환
    @Bean
    public Step step3(){
        return stepBuilderFactory.get("step3")
                .partitioner(step1())
                .gridSize(2)
                .build();
    }

    //job -> JobStepBuilder 반환
    @Bean
    public Step step4(){
        return stepBuilderFactory.get("step4")
                .job(job())
                .build();
    }

    //flow -> FlowStepBuilder 반환
    @Bean
    public Step step5(){
        return stepBuilderFactory.get("step5")
                .flow(flow())
                .build();
    }

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

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

TaskletStep-개념 및 API 소개

1. 기본개념

  • 스프링 배치에서 제공하는 Step 의 구현체로서 Tasklet 을 실행시키는 도메인 객체
  • RepeatTemplate 를 사용해서 Tasklet의 구문을 트랜잭션 경계 내에서 반복해서 실행함
    • 쉽게 말해서 실행할 로직을 담을 수 있다는 의미이다. (Tasklet안에 로직 작성했던것을 기억해내자)
    • 또한 Transaction 경계내에서 작업이 이루어지므로 별도의 Transaction을 선언해주지 않아도 된다.
  • Task 기반과 Chunk 기반으로 나누어서 Tasklet 을 실행함
    • 이전에 만들었던 사용자 정의용 Task와 Chunk로 나누어진다.

2. Task vs Chunk 기반 비교

  • 스프링 배치에서 Step의 실행 단위는 크게 2가지로 나누어짐 → Chunk, Task
  • chunk 기반
    • 하나의 큰 덩어리를 n개씩 나눠서 실행한다는 의미로 대량 처리를 하는 경우 효과적으로 설계 됨
    • ItemReader, ItemProcessor, ItemWriter 를 사용하며 청크 기반 전용 Tasklet 인 ChunkOrientedTasklet 구현체가 제공된다
  • Task 기반
    • ItemReader 와 ItemWriter 와 같은 청크 기반의 작업 보다 단일 작업 기반으로 처리되는 것이 더 효율적인 경우
    • 주로 Tasklet 구현체를 만들어 사용
    • 대량 처리를 하는 경우 chunk 기반에 비해 더 복잡한 구현 필요

 

   💡 대용량 처리를 한다면 chunk를, 단순한 단일 작업 처리라면 Task를 사용하자

3.TaskletStepBuilder의 메소드

4.이번에 살펴볼 TaskletStepBuilder의 부분을 그림으로 확인하자

예제코드

package com.example.springbatch_4_1_batchinitconfiguration;

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.item.ItemProcessor;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;
import java.util.List;
import java.util.Locale;

/**
 * packageName    : com.example.springbatch_4_1_batchinitconfiguration
 * fileName       : TaskletStepConfiguration
 * author         : namhyeop
 * date           : 2022/07/25
 * description    :
 * Task, Chunk 타입 두 가지 비교테스트를 작성한다.
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/07/25        namhyeop       최초 생성
 */

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

    @Bean
    public Job batchJob(){
        return this.jobBuilderFactory.get("batchJob")
                .start(taskStep())
                .build();
    }

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

    @Bean
    public Step chunkStep(){
        return stepBuilderFactory.get("chunkStepNamePosition")
                .<String, String>chunk(10)
                .reader(new ListItemReader<>(Arrays.asList("item1","item2","item3","item4","item5")))
                .processor(new ItemProcessor<String, String>() {
                    @Override
                    public String process(String item) throws Exception {
                        return item.toUpperCase();
                    }
                })
                .writer(new ItemWriter<String>() {
                    @Override
                    public void write(List<? extends String> items) throws Exception {
                        items.forEach(item -> System.out.println(items));
                    }
                }).build();
    }
}
  • Task와 Chunk 두 가지 비교테스트를 작성해보았다.
  • 각각의 멀티스레드 또는 단일 스레드 상황에 따라 알맞게 chunk와 Task를 사용하자

TaskletStep-tasklet()

1. 기본개념

  • Tasklet 타입의 클래스를 설정한다
    • Tasklet
      • Step 내에서 구성되고 실행되는 도메인 객체로서 주로 단일 태스크를 수행하기위한 것
      • TaskletStep 에 의해 반복적으로 수행되며 반환값에 따라 계속 수행 혹은 종료한다
      • RepeatStatus - Tasklet 의 반복 여부 상태 값
        • RepeatStatus.FINISHED - Tasklet 종료, RepeatStatus 을 null 로 반환하면 RepeatStatus.FINISHED로 해석됨
        • RepeatStatus.CONTINUABLE - Tasklet 반복
        • RepeatStatus.FINISHED가 리턴되거나 실패 예외가 던져지기 전까지 TaskletStep 에 의해 while 문 안에서 반복적으로 호출됨 (무한루프 주의)
  • 익명 클래스 혹은 구현 클래스를 만들어서 사용한다
  • 이 메소드를 실행하게 되면 TaskletStepBuilder 가 반환되어 관련 API 를 설정할 수 있다.
  • Step에 오직 하나의 Tasklet 설정이 가능하며 두 개 이상을 설정 했을 경우 마지막에 설정한 객체가 실행된다

2.구조

3.Tasklet API 예제

예제 코드

package com.example.SpringBatchTasklet;

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.launch.support.RunIdIncrementer;
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_4_1_batchinitconfiguration
 * fileName       : Taskletconfiguration
 * author         : namhyeop
 * date           : 2022/07/25
 * description    :
 * Tasklet 테스트 파일
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/07/25        namhyeop       최초 생성
 */
@Configuration
@RequiredArgsConstructor
public class TaskletConfiguration {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job BatchJob(){
        return this.jobBuilderFactory.get("batchJob")
                .incrementer(new RunIdIncrementer())
                .start(step1())
                .next(step2())
                .build();
    }

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

    @Bean
    public Step step2(){
        return stepBuilderFactory.get("step2")
                .tasklet(new CustomTasklet())
                .build();
    }
}
package com.example.SpringBatchTasklet;

import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;

/**
 * packageName    : com.example.springbatch_4_1_batchinitconfiguration
 * fileName       : CustomTasklet
 * author         : namhyeop
 * date           : 2022/07/25
 * description    :
 * Tasklet Custom 동작파일
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/07/25        namhyeop       최초 생성
 */
public class CustomTasklet implements Tasklet {
    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        String stepName = contribution.getStepExecution().getStepName();
        String jobName = chunkContext.getStepContext().getJobName();

        System.out.println("stepName = " + stepName);
        System.out.println("jobName = " + jobName);
        return RepeatStatus.FINISHED;
    }
}
  • Tasklet에 사용자 정의 Tasklet을 동작하는 예제이다.

TaskletStep-startLimit()/allowStartIfComplete()

Start

1.기본개념

  • Step의 실행 횟수를 조정할 수 있다
  • Step 마다 설정할 수 있다
  • 설정 값을 초과해서 다시 실행하려고 하면 StartLimitExceededException이 발생
  • start-limit의 디폴트 값은 Integer.MAX_VALUE

2.API

allowStartIfComplte()

1. 기본개념

  • 재시작 가능한 job 에서 Step의 이전 성공 여부와 상관없이 항상 step 을 실행하기 위한 설정
    • Job은 성공이나 실패를 할 수 있다.
      • 성공할 경우 작업은 다시 실행되지 않는다.
    • Step도 성공이나 실패 둘 중 한 가지이다.
      • Job과 마차간지로 성공이나 실패를 할 수 있다.
    • 이런 두 작업을 성공과 실패 상관없이 매번 반복적으로 실행할 때 사용하는게 allowStartIfComplte이다.
  • 실행 마다 유효성을 검증하는 Step이나 사전 작업이 꼭 필요한 Step에 사용한다.
  • 정리하면 기본적으로 COMPLETED 상태를 가진 Step은 Job 재시작시 실행하지 않고 스킵한다
  • 그러나 allowStartifCcomplete가 “true”로 설정된 step은 항상 실행한다

2.흐름도

  • Job에서 Step이 실행되고 해당 Step마다 먼저 작업이 성공했는지 아닌지 구분한다.
  • 작업이 성공되지 않은 작업이라면 Tasklet이 실행되고 작업이 성공했다면 allowStartComplete 옵션을 확인한다.
    • allowStartComplete 옵션이 YES일 경우에는 해당 작업이 한 번 더 실행되고 그렇지 않다면 NextStep을 통해 다음 Step으로 넘어간다.

3.allowStartInfComplete API 예제

  • 위처럼 성공할 경우 성공한 Step도 반복적으로 수행해준다.

예제코드

package com.example.SpringBatch_5_4_Step_startLimitAndAllowStartIfComplte;

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_5_4_Step_startLimitAndAllowStartIfComplte
 * fileName       : Limit_AllowConfiguration
 * author         : namhyeop
 * date           : 2022/07/25
 * description    :
 * allowStartIfComplete와 startLimite를 쓴 복합 예제 코드
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/07/25        namhyeop       최초 생성
 */

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

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

    private Step step1() {
        return stepBuilderFactory.get("step1")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                        System.out.println("stepContribution = " + contribution + ", chunkContext = " + chunkContext);
                        return RepeatStatus.FINISHED;
                    }
                })
                .allowStartIfComplete(true)
                .build();
    }

    private Step step2(){
        return stepBuilderFactory.get("step2")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                        System.out.println("stepContribution = " + contribution + ", chunkContext = " + chunkContext);
                        throw new RuntimeException("");
//                        return RepeatStatus.FINISHED;
                    }
                })
                .startLimit(3)
                .build();
    }
}
  • 테스트 스토리
    • Step1에 allowStatrtIfComplter를 주석처리하고 작업을 돌리는 경우
      • 위 상태에서 Step2에 startLimit를 주석처리하고 작업을 돌렸을 경우.
      • 위 상태에서 Step2에 startLimit를 주석처리 안하고 돌렸을 경우
    • Step1에 allowStatrtIfComplter를 주석처리 안하고 작업을 돌리는 경우
      • 위 상태에서 Step2에 startLimit를 주석처리하고 작업을 돌렸을 경우.
      • 위 상태에서 Step2에 startLimit를 주석처리 안하고 돌렸을 경우

💡 startLImit는 말 그대로 작업에 대한 총 반복횟수이기 때문에 DB에 저장된 StepExcution 작업을 기준으로 한다. 나는 Step안에서 돌아가는 작업의 반혹횟수로 착각하여 잘못이해하는 바람에 시간을 낭비했다.

💡 allowStartIfComplter를 안하고 동작할 경우 다음 Step 자체가 동작하지 않기 때문에 이 부분을 기억하고 예제 테스트를 진행하자. 나중에 이 기록에 다시 한번 작업을 확인하는데 도움을 줄것이다.

TaskletStep 아키텍쳐

  • 이 쳅터에서는 TaskletStep 아키텍쳐에 대해서 설명한다.

1.Step의 로직 순서

  • 먼저 Job이 실행되고 Step이 실행된다.
  • 이후 RepeatTemplate의 의해서 Tasklet이 반복적으로 수행된다.
  • Tasklet의 비즈니스 로직이 수행되면서 예외가 발생할 경우에는 반복이 종료되고 Step 자체가 종료된다.
  • 그렇지 Exception 없이 정상적으로 수행이 되었다면 RepeatStatus의 상태에 따라 다음 수행이 결정된다.
    • RepeatStatus.FINISHED인 경우에는 반복이 종료 다음 Step으로 이동된다.
    • RepeatStatus.CONTINUABLE인 경우에는 RepeatTEmplate으로 이동해서 Tasklet이 반복적으로 수행된다.

2.TaskletStep의 로직 순서

  • TaskletStep의 Job과 관계에서 ExcutionContenxt에 대한 메타데이터가 생성된다.
  • 이때 배치상태에 대한 설정이 이루어진다.
  • 이후 TaskletStep과 RepecatTemplate과 관계에서 StepListener 을 호출하게 된다.
  • 이후 RepeatTemplate과 Tasklet 상태에서 Repeat 설정에 따라 반복적으로 수행될지 아니면 반환할지 결정한다.
  • 반환되면서 위와 같은 로직순서를 한번더 실행한다.

3.클래스 상속 관계도(현재 TaskletStepBuilder를 학습하고 있다.)

예제 코드

package com.example.SpringBatch_5_5_Step_TaskletStepArchitecture;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.*;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.job.DefaultJobParametersExtractor;
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_5_5_Step_TaskletStepArchitecture
 * fileName       : TaskletStepArchitectureCongiguration
 * author         : namhyeop
 * date           : 2022/07/25
 * description    :
 * TaskletArchitecture에 대한 흐름을 파악하는 예제
 * 디버깅하면서 쳅터에서 배운 개념들의 클래스를 확인해보자
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/07/25        namhyeop       최초 생성
 */
@RequiredArgsConstructor
@Configuration
public class TaskletStepArchitectureCongiguration {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

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

    @Bean
    public Step jobStep(JobLauncher jobLauncher){
        return stepBuilderFactory.get("step1")
                .job(childJob())
                .launcher(jobLauncher)
                .listener(new StepExecutionListener() {
                    @Override
                    public void beforeStep(StepExecution stepExecution) {
                            stepExecution.getExecutionContext().putString("name", "user1");
                    }
                    @Override
                    public ExitStatus afterStep(StepExecution stepExecution) {
                        return null; // null == RepeatStatus.Finished
                    }
                })
                .parametersExtractor(jobParamtersExtractor())
                .build();
    }

    @Bean
    public Job childJob(){
        return this.jobBuilderFactory.get("childJob")
                .start(step1())
                .build();
    }

    private Step step1() {
        return stepBuilderFactory.get("step1")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
//                        throw new RuntimeException("step1 was failed");
                        return RepeatStatus.FINISHED;
                    }
                })
                .build();
    }

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

    @Bean
    public DefaultJobParametersExtractor jobParamtersExtractor() {
        DefaultJobParametersExtractor extractor = new DefaultJobParametersExtractor();
        extractor.setKeys(new String[]{"name"});
        return extractor;
    }
}
  • 학습한 개념들의 클래스를 디버깅하면서 확인해보자

JobStep

1. 기본개념

  • Job 에 속하는 Step 중 외부의 Job 을 포함하고 있는 Step
  • 외부의 Job 이 실패하면 해당 Step 이 실패하므로 결국 최종 기본 Job도 실패한다
  • 모든 메타데이터는 기본 Job과 외부 Job 별로 각각 저장된다.
  • 커다란 시스템을 작은 모듈로 쪼개고 job의 흐름를 관리하고자 할 때 사용할 수 있다

2.API 소개

3.예제 코드의 설계 분석

  • ParentJob에서 JobStepd을 먼저 실행한다. 이후 JobStep의 JobLauncher에서 childJob()을 실행한다.
  • 이후 childJob의 Step1이 실행되고 Step1의 Tasklet 로직이 수행된다.
  • 이때 childJob이 실패할 경우 ParentJob으로 돌아가게 되고 성공할 경우 다음 Step인 Step2를 수행한다.

4.이번에 살펴볼 JobStepBuilder의 부분을 그림으로 확인하자

5.클래스 상속 관계도(현재 JobStepBuilder를 학습하고 있다.)

예제 코드

package com.example.SpringBatch_5_6_Step_JobStep;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.*;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.job.DefaultJobParametersExtractor;
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_5_5_Step_TaskletStepArchitecture
 * fileName       : TaskletStepArchitectureCongiguration
 * author         : namhyeop
 * date           : 2022/07/25
 * description    :
 * JobStep에 대한 흐름을 파악하는 예제
 * 디버깅하면서 쳅터에서 배운 개념들의 클래스를 확인해보자
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/07/25        namhyeop       최초 생성
 */
@RequiredArgsConstructor
@Configuration
public class JobStepConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job parentJob() {
        return this.jobBuilderFactory.get("parentJob")
                .start(jobStep(null))
                .next(step2())
                .build();
    }

    @Bean
    public Step jobStep(JobLauncher jobLauncher) {
        return this.stepBuilderFactory.get("jobStep")
                .job(childJob())
                .launcher(jobLauncher)
                .listener(new StepExecutionListener() {
                    //name이란 키값에 value라는 값이 user1이라는 의미이다. parametersExtractor를 이용해 추출할 수 있다.
                    //name=user1 매개변수를 설정했다고 생각하면 이해하기 쉽다.
                    @Override
                    public void beforeStep(StepExecution stepExecution) {
                        stepExecution.getExecutionContext().putString("name", "user1");
                    }

                    @Override
                    public ExitStatus afterStep(StepExecution stepExecution) {
                        return null;
                    }
                })
                .parametersExtractor(jobParametersExtractor())
                .build();
    }

    @Bean
    public Job childJob() {
        return this.jobBuilderFactory.get("childJob")
                .start(step1())
                .build();
    }

    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
//(1)                        throw new RuntimeException("step1 was failed");
                        return RepeatStatus.FINISHED;
                    }
                })
                .build();
    }

    @Bean
    public Step step2() {
        return stepBuilderFactory.get("step2")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
//(2)                        throw new RuntimeException("step2 was failed");
                        return RepeatStatus.FINISHED;
                    }
                })
                .build();
    }

    @Bean
    public DefaultJobParametersExtractor jobParametersExtractor() {
        DefaultJobParametersExtractor extractor = new DefaultJobParametersExtractor();
        extractor.setKeys(new String[]{"name"});
        return extractor;
    }
}

실행옵션(paramter 자리에 줘야함. Active profiles에 줘서 뻘짓함)

  • 먼저 1번의 주석을 해제하고 수행해보자
    • 이럴 경우 1번 step1에서 예외처리가 발생하기 때문에 ParentJob Execution은 Faild 상태가 된다. 또한 step2 는 실행되지 않는다.

Job_execution_table

Job_execution_params

  • childJob에서 실행할때 설정한 파라미터가 하나 더 추가된것을 확인할 수 있다.

Batch_Step_execution

  • 다음으로 1번을 주석처리하고 2번을 주석해제하고 수행해보자
    • 이럴 경우 step2에서 예외처리가 발생하기 때문에 ParentJob Execution은 Faild 상태가 된다. Step2는 수행되며 ChildJob 처리 자체는 성공적이므로 ChildJob의 Execution은 성공처리된다. 그러나 Step2가 실패하였으므로 ParentJob_Execution은 실패처리된다.

Job_execution_table

Job_execution_params

Batch_Step_execution

 

반응형

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

7.Spring Batch의 Chunk와 동작원리 살펴보기  (0) 2024.11.08
6.Spring Batch의 Flow  (1) 2024.11.07
4.Spring Batch의 Job  (0) 2024.11.06
3.Spring Batch 도메인 이해하기  (0) 2024.11.02
2.Spring Batch 시작하기  (0) 2024.11.02