Job이란

  • 배치 계층 구조의 가장 상위에 있는 개념으로 하나의 배치 작업 자체를 의미합니다.
  • Job Configuration을 통해 생성되는 객체 단위로서 배치 작업을 어떻게 구성하고 실행할 것인지 전체적으로 설정하고 명세해놓은 객체입니다.
  • 배치 Job을 구성하기 위한 최상위 인터페이스이며 스프링 배치가 기본 구현체인 SimpleJob과 FlowJob 등 구현체를 제공해줍니다.
  • 여러 Step를 포함하고 있는 컨테이너로 반드시 하나 이상의 Step으로 구성해야 합니다.

스프링 배치가 기본적으로 제공해주는 구현체

  • SimpleJob
    • Job내에 구성되어있는 Step을 순차적으로 실행시킵니다.
  • FlowJob
    • SimpleJob과 달리 특정 조건과 흐름에 따라 Step을 실행시킵니다.
    • Flow 객체를 실행시켜서 작업을 진행합니다.

 

  • SimpleJob의 기본 예제 코드
@Configuration
@RequiredArgsConstructor
public class SimpleJobExample {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job job() {
        return jobBuilderFactory.get("jobEx")
               .start(step1())
               .next(step2())
               .build();

    }

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

    }

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

    }
}

 

  • job()에 설정해놓은 step1을 실행을 하고 step2 순서로 실행이 됩니다.

 

※참고 : JobBuilderFactory, StepBuilderFactory, get("문자열"), RepeatStautus.FINISHED, start, next, tasklet 등은 해당 포스팅에서 다루지 않고 아래 포스팅에서 다루니 참고하실 분은 아래 포스팅에 들어가서 확인해주세요!

https://hoestory.tistory.com/39

 

[Spring Batch] @EnableBatchProcessing을 이용하여 스프링 배치 사용하기

1. 스프링 배치를 사용하기 위한 의존성 추가 스프링 배치를 사용하기 위해서는 build.gradle 또는 pom.xml에 스프링 배치에 대한 의존성을 추가해줘야 합니다. build.gradle implementation 'org.springframework..

hoestory.tistory.com

 

Job의 계층 구조

  • Job
    • 최상위 인터페이스
  • AbstractJob
    • Job 인터페이스를 구현하는 추상 클래스
    • 속성
      • name : Job의 이름
      • restartable : 재시작 여부(기본값 true)
      • JobRepository : 메타 데이터 저장소
      • JobExecutionListener : Job 이벤트 리스너
      • JobParametersIncrementer : JobParameter 증가기
      • JobParametersValidator : JobParameter 검증기
      • SimpleStepHandler : Step 실행 핸들러
  • SimpleJob, FlowJob
    • AbstractJob 추상 클래스를 상속받은 기본적으로 제공해주는 구현체

여기서 잠깐!
스프링 배치에서 실행 정보, 상태 등을 저장하는 테이블을 생성하는 방법에 대해 잠시 알아보겠습니다!!

※ 수동으로 생성하는 방법

 

여기서 자신의 프로젝트에 설정한 데이터베이스.sql을 들어가면 작성되어있는 쿼리문이 있습니다. 쿼리문을 사용하여 생성할 수 있습니다.

 

※ 자동으로 생성하는 방법

application.yml

spring :
  batch :
    jdbc :
      initialize-schema: always

 

JobInstance

  • Job이 실행될 때 생성되는 Job의 논리적 실행 단위 객체로서 고유하게 식별 가능한 작업 실행을 나타냅니다.
  • Job의 설정과 구성은 동일하지만 Job이 실행되는 시점에 처리하는 내용은 다릅니다.
    • ex) 하루에 한 번씩 배치 Job이 실행된다면 각각 실행되는 Job을 JobInstance로 표현합니다.
  • JobInstance의 생성과 실행
    • 처음에 실행할 때 스프링 부트가 자동으로 실행시켜주는 JobLauncher에 매개변수로 Job과 JobParameter를 JobLauncher.run 메서드의 매개변수로 넘겨주면 실행이 되고 JobInstance가 생성이 됩니다.
    • 두 번째로 실행할 때 jobLauncher.run에 매개변수로 넘겨준 파라미터 값이 동일할 경우 이미 존재하는 JobInstance를 리턴해주면서 아래와 같은 예외 메시지가 발생합니다.
Caused by: org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException:
A job instance already exists and is complete for parameters={key=value}. 
If you want to run this job again, change the parameters.

 

Job의 이름과 파라미터의 값이 동일하여 위와 같은 예외가 발생하게 됩니다. 이유는 Job의 이름과 파라미터의 해시값은 중복되지 말고 하나만 존재해야 하는데 중복 저장을 하려고 하기 때문입니다.

  • Job(N) : JobInstance(1) 즉 N:1 관계입니다
  • BATCH_JOB_INSTANCE 테이블과 매핑
    • Job_Name(job)과 Job_Key(JobParameter 해시값)가 동일한 데이터는 저장할 수 없습니다.

 

 

  • 1, 2 : 스프링 부트가 JobLanuncher을 실행시키거나 또는 직접 ApplicationRunner 인터페이스 구현하여 실행을 하여 Job을 실행하기 위해 Job과 JobParameters를 매개변수로 넘깁니다.
  • 3,4 : JobRepository가 Job을 실행시켜 생성된 JobInstance에 대한 정보를 데이터베이스에 있는지 없는지를 확인합니다.
  • 5 : 데이터베이스에 존재 유무를 파악합니다.
  • 6
    • no : 새로운 JobInstance를 생성합니다.
    • yes : 있으면 기존의 JobInstance를 리턴하고 위에 작성된 예외 메시지와 동일하게 예외가 발생합니다.

 

JobInstance에 대한 예제

스프링 부트가 자동으로 JobLauncher을 실행시켜서 하는 게 아닌 직접 ApplicationRunner를 구현하는 예제입니다.

 

스프링 부트가 자동으로 실행 안 하게 해주는 설정

application.yml

 

spring :
  job:
    enabled: false
@Component
class CustomLauncher implements ApplicationRunner {

    @Autowired
    private JobLauncher jobLauncher;

    @Autowired
    private Job job;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        JobParameters jobParameters = new JobParametersBuilder()
                .addString("name", "user1").toJobParameters();
        jobLauncher.run(job, jobParameters);

    }

@Configuration
@RequiredArgsConstructor
public JobInstanceExample {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;


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

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

                    }
                }).build();
    }

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

 

  • 아래의 데이터는 위에 작성한 코드를 실행했을 때 BATCH_JOB_INSTANCE에 저장되는 데이터입니다.

 

 

  • JOB_NAME : 빈으로 등록해준 job에서 jobBuilderFactory.get("인자") 인자 값입니다.
  • JOB_KEY : CustomLauncher에서 addString("name", "user1")에 들어가 있는 user1의 해시값입니다.

 

JobParameter

  • Job을 실행할 때 함께 포함되어 사용되는 파라미터를 가진 객체입니다.
  • 하나의 Job이 존재할 수 있는 여러 개의 JobInstance를 구분하기 위한 용도입니다.
  • JobParamter와 JobInstance는 1:1 관계입니다.
  • JobParameters : LinkedMap <String, JobParameter>의 형태로 되어있습니다.
  • JobParameter는 파라미터 값을 저장합니다.
  • 파라미터의 데이터 타입 종류 : String, Long, Double, Date

 

생성 및 바인딩하는 방법

  • 어플리케이션 실행 시 주입
    • java -jar 빌드 파일. jar 파라미터명=값
      ex) java -jar batch.jar date(date)=20221009 cnt(long)=12 avg(double)=3.5 str=스프링 배치
  • 코드로 생성
    • JobParameterBuilder, DefaultJobParametersConverter
  • SpEL 이용
    • @Value("#{parameter [키]}"), @JobScope, @StepScope 선언 필수

 

BATCH_JOB_EXECUTION_PARAM 테이블과 매핑

 

  • JOB_EXECUTION과 1:M의 관계

 

JobParameter에 대한 예제

@Component
class CustomLauncher implements ApplicationRunner {

    @Autowired
    private JobLauncher jobLauncher;

    @Autowired
    private Job job;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        JobParameters jobParameters = new JobParametersBuilder()
                .addString("name", "user1")
                .addLong("people",100L)
                .addDouble("avg",1.5)
                .addDate("created",new Date())
                .toJobParameters();
        jobLauncher.run(job, jobParameters);

    }

@Configuration
@RequiredArgsConstructor
public JobInstanceExample {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;


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

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

                    }
                }).build();
    }

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

 

  • CustomLauncher에 run 메서드 내에 addString, addLong, addDate, addDouble를 이용하여 파라미터 값 설정할 수 있습니다.
  • 파라미터 값 설정 후 jobLauncher.run에 Job과 파라미터를 넣어줍니다.
  • 파라미터 값을 사용하는 곳은 step에서 동작하는 tasklet내에서 StepContribution stepContribution, ChunkContext chunkContext를 이용하여 파라미터 값을 사용할 수 있습니다. 둘의 차이점과 공통점은 밑에서 설명하겠습니다.
  • 밑에 사진은 파라미터값을 설정을 하고 실행을 하였을 때 JOB_EXECUTION_PARAMETERS에 저장되는 데이터들입니다.

 

 

StepContribution stepContribution, ChunkContext chunkContext 공통점과 차이점

  • 공통점 : StepExecution과 JobExecution를 필드로 가져 파라미터로 설정한 값들을 사용할 수 있습니다.
  • 차이점 : StepContribuiton를 이용해서 파라미터 값을 꺼내오면 JobParameters를 리턴하고 ChunkContext를 사용하여 파라미터 값을 꺼내오면 Map을 리턴합니다.
    • StepContribution
JobParameters jobParameters = 
             stepContribution.getStepExecution().getJobExecution().getJobParameters();
  • ChunkContext
 Map<String, Object> jobParameters1 = chunkContext.getStepContext().getJobParameters();

 

※ 설정한 파라미터 값 꺼내오는 방법

 

 JobParameters stepContributionParameters = stepContribution.getStepExecution().getJobExecution().getJobParameters();
stepContributionParameters.getDate("create");
stepContributionParameters.getDouble("avg");
stepContributionParameters.getLong("people");
stepContributionParameters.getString("name");

Map<String, Object> chunkContextParam = chunkContext.getStepContext().getJobParameters();
chunkContextParam.get("create");
chunkContextParam.get("avg");
chunkContextParam.get("people");
chunkContextParam.get("name");

 

JobExecution

  • JobInstance에 대한 한 번의 시도를 의미하는 객체로서 Job 실행 중에 발생한 정보들을 저장하고 있는 객체입니다.
    • 시작시간, 종료시간, 상태(시작됨, 완료, 종료), 종료 상태의 속성을 가집니다.
  • JobInstance와의 관계
    • JobExecution은 FAILED 또는 COMPLETED 등의 Job의 실행 결과 상태를 가지고 있습니다.
    • JobExecution의 상태가 COMPLETED일 경우 JobInstance는 완료된 상태로 간주하여 재실행이 불가능합니다.
    • JobExecution의 상태가 FAILED일 경우 JobInstance는 완료가 되지 않은 상태로 간주하여 재실행이 가능합니다.
      • 위에서 JobParameter값이 동일하면 안 된다고 하였는데 JobExecution의 상태가 FAILED일 경우 JobInstance를 계속 실행 가능합니다.
    • JobExecution의 실행 상태 결과가 COMPLETED가 될 때까지 JobInstance를 실행할 수 있습니다.

 

BATCH_JOB_EXECUTION의 관계

  • JobInstance와 JobExecution은 1 : M 관계로서 JobInstance에 대한 성공/실패 내역을 가지고 있습니다.
  • 아래 사진은 JobInstance가 완료되지 않은 상태와 완료가 된 상태일 때 BATCH_JOB_EXECUTION에 저장되는 데이터들입니다.

 

※ STATUS값이 FAILED일 때

 

BATCH_JOB_EXECUTION 테이블

 

JobInstance가 완료되지 않아 STATUS값이 FAILED값이 생깁니다. STATUS값이 FAILED라도 BATCH_JOB_INSTANCE 테이블에는 생성한 Job의 이름과 파라미터의 해시값이 저장됩니다. 그리고 BATCH_JOB_EXECUTION_PARAMS에도 저장이 됩니다.

 

BATCH_JOB_EXECUTION_PARAMS 테이블

 

 

BATCH_JOB_INSTANCE 테이블

 

 

※ STATUS값이  FAILED에서 한 번 더 실행했을 때 COMPLETED일 때

 

BATCH_JOB_EXECUTION 테이블

 

 

BATCH_JOB_EXECUTION_PARAMS 테이블

 

tl

JobInstance가 완료가 되든 안되든 파라미터 값들은 저장이 됩니다.

 

BATCH_JOB_INSTANCE 테이블

 

 


참고한 강의

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B0%B0%EC%B9%98

 

스프링 배치 - Spring Boot 기반으로 개발하는 Spring Batch - 인프런 | 강의

초급에서 중~고급에 이르기까지 스프링 배치의 기본 개념부터 API 사용법과 내부 아키텍처 구조를 심도있게 다룹니다. 그리고 스프링 배치 각 기능의 흐름과 원리를 학습하게 되고 이를 바탕으

www.inflearn.com