SpringBatch 에서 JobInstance, JobExecution 의 관계

반응형
728x90
반응형

들어가기전

JobInstance, JobExecution 의 개념은 알고가자.

https://devfunny.tistory.com/476?category=820618 

 

[스프링배치] 잡의 실행 (JobLauncher, JobInstance, JobExecution, JobParameters)

잡의 실행 잡의 실행은 잡 러너 (Job Runner) 에서 시작된다. 잡 러너는 잡 이름과 여러 파라미터를 받아, 잡을 실행시킨다. 스프링 배치는 2가지 잡 러너를 제공한다. 1) CommandLineJobRunner 스크립트를

devfunny.tistory.com

 

 

 

예제코드

  • JobExecutionConfiguration
package com.spring.batch.job;

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

@Configuration
@RequiredArgsConstructor
public class JobExecutionConfiguration {
    // job 생성
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job JobExecutionTestJob() {
        return this.jobBuilderFactory.get("JobExecutionTestJob")
                /* step start */
                .start(JobExecutionTestStep1())
                .next(JobExecutionTestStep2())
                .build();
    }

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

    @Bean
    public Step JobExecutionTestStep2() {
        return stepBuilderFactory.get("JobExecutionTestStep2")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                        System.out.println("JobExecutionTestStep2 was executed");
                        /** 에러 발생 */
                        // throw new RuntimeException("step2 has failed"); // 에러 발생시키기
                        return RepeatStatus.FINISHED;
                    }
                })
                .build();
    }
}

 

1) JobExecutionTestJob 이 실행되었다.

2) Job이 COMPLETED 상태로 수행 완료되었다.

테이블 exec
BATCH_JOB_INSTANCE insert row 1
BATCH_JOB_EXECUTION insert row 1 (COMPLETED)

 

3) 다시 JobExecutionTestJob 을 실행시킨다.

4) 같은 job, parameter 로 수행되었으므로 이미 JobInsatance 가 존재한다는 에러가 발생한다.

 

[변환]

1) 위 과정을 통해 INSERT 된 데이터를 모두 지우자.

2) 코드 변경 - JobExecutionTestStep2()에서 에러를 발생시키자.

...
   @Bean
    public Step JobExecutionTestStep2() {
        return stepBuilderFactory.get("JobExecutionTestStep2")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                        System.out.println("JobExecutionTestStep2 was executed");
                        /** 에러 발생 */
                        throw new RuntimeException("step2 has failed"); // 에러 발생시키기
                        //return RepeatStatus.FINISHED;
                    }
                })
                .build();
    }
...

 

3) BATCH_JOB_EXECUTION, BATCH_JOB_INSTANCE 테이블이 비워진 상태에서 JobExecutionTestJob 을 실행시킨다.

4) Job이 FAILED 상태로 수행 완료되었다.

테이블 exec
BATCH_JOB_INSTANCE insert row 1
BATCH_JOB_EXECUTION insert row 1 (FAILED)

 

5) 여기서 다시 코드를 원복하자. (에러 발생 코드를 주석 처리한다.)

...
   @Bean
    public Step JobExecutionTestStep2() {
        return stepBuilderFactory.get("JobExecutionTestStep2")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                        System.out.println("JobExecutionTestStep2 was executed");
                        /** 에러 발생 */
                        // throw new RuntimeException("step2 has failed"); // 에러 발생시키기
                        return RepeatStatus.FINISHED;
                    }
                })
                .build();
    }
...

 

6) 다시 JobExecutionTestJob 이 실행시켜보자.

7) Job이 COMPLETED 상태로 수행 완료되었다.

테이블 exec
BATCH_JOB_INSTANCE 이미 존재하므로 데이터가 새로 insert 되지 않는다.
BATCH_JOB_EXECUTION insert row 1 (COMPLETED)

 

이 이후로는 다시 해당 job을 수행시킨다면 또다시 이미 jobInstance가 존재한다고 에러가 발생할 것이다.

 

 

 

 

정리

JobExecution은 JobInstance 가 실행될 때마다 생성된다. JobInstance 는 위 예제에서 보이듯, 같은 job, jobParameter 로는 오직 1번만 실행된다. 하지만 만약 이전의 동일한 Job이 Failed 상태로 종료된다면 JobInstance를 재사용하여 잡 재실행이 가능하고 이렇게 실행될때마다 BATCH_JOB_EXECUTION은 1개씩 데이터가 insert 된다.

 

 

다시한번 아래 흐름을 이해해보자. 이해되지 않는다면 다시 처음으로 돌아가, 글을 읽어보자.

 

[첫번째 실행] JobA
JobInstanceId : 1
JobExecution
   > JobInstanceId : 1
   > JobExecutionId : 1
   > Status : COMPLETED


[두번째 실행] jobB
JobInstanceId : 2
JobExecution
   > JobInstanceId : 2
   > JobExecutionId : 2
   > Status : FAILED


[세번째 실행] jobB
JobInstanceId : 2
JobExecution
   > JobInstanceId : 2 (재사용)
   > JobExecutionId : 3
   > Status : COMPLETED

 

반응형

Designed by JB FACTORY