오늘 하루에 집중하자
  • [Spring Batch] 2. 스프링 배치 시작
    2024년 06월 08일 19시 26분 54초에 업로드 된 글입니다.
    작성자: nickhealthy

    들어가기 전에!


    해당 게시글은 인프런 정수원님의 Spring Batch 강의를 듣고 추후 복기하고자 공부한 내용을 가볍게 정리한 것입니다.

    문제가 될 시 삭제하겠습니다.

     

    또한 현재 스프링 배치의 최신버전과 상이한 내용이 있습니다.

    해당 내용 참고해서 읽어주시면 감사하겠습니다!

     

    스프링 배치 개발 환경 설정


    개발 환경

    • JDK 1.8 이상
    • Spring Boot 2.7.5
    • DB - H2, MySQL
    • Maven

    스프링 부트에서는 아래와 같이 라이브러리를 추가해주면 스프링 배치를 사용할 수 있다.

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-batch</artifactId>
    </dependency>

     

    스프링 배치 활성화

    `@EnableBatchProcessing`: 스프링 배치가 작동하기 위해 선언해야 하는 어노테이션

    • 필요한 빈들을 생성하고, 초기화에 필요한 설정들을 도와주는 어노테이션
    • 총 4개의 설정 클래스를 실행시키며, 스프링 배치의 모든 초기화 및 실행 구성이 이루어진다.
    • 스프링 부트가 실행되면서 스프링 배치의 자동 설정 클래스가 실행된다.
      • 빈으로 등록된 모든 Job을 검색 및 초기화와 동시에 Job을 수행하도록 구성한다.

     

    스프링 배치 초기화 설정 클래스

    스프링 배치를 사용하기 위해 어떤 초기화 과정을 진행하는지에 대한 계략적인 내용이다.

    - `@EnableBatchProcessing` 어노테이션을 사용했을 때

    스프링 배치 초기화 설정 클래스 동작 순서

     

     

    1. `SimpleBatchConfiguration`
      • `JobBuilderFactory`와 `StepBuilderFactory` 생성
      • 스프링 배치의 주요 구성 요소를 생성한다.
      • 프록시 객체로 생성된다.
    2. `BatchConfigurerConfiguration`
      • `BasicBatchConfigurer`
        • `SimpleBatchConfiguration`(1번)에서 생성한 프록시 객체의 실제 대상 객체를 생성하는 설정 클래스
        • 빈으로 의존성 주입을 받아서 주요 객체들을 참조해서 사용할 수 있다.
      • `JpaBatchConfigurer`
        • JPA 관련 객체를 생성하는 설정 클래스
      • `BatchConfigurer` 인터페이스를 구현하여 사용자 정의 설정 클래스를 사용할 수 있다.
        • 위 두 개의 클래스도 해당 인터페이스를 구현하여 정의한 것
    3. `BatchAutoConfiguration`
      • 스프링 배치가 초기화될 때 자동으로 실행되는 설정 클래스
      • Job을 수행하는 `JobLauncherApplicationRunner` 빈을 생성
        • `ApplicationRunner` 인터페이스로 구현된 클래스는 스프링 부트가 자동으로 잡을 실행 시킨다.
        • 따라서 `JobLauncherApplicationRunner`도 `ApplicationRunner` 인터페이스로 구현된 클래스이다.

     

    Spring Batch에서 Hello World! 찍기


    들어가기 전에!

    스프링 배치는 배치 작업의 진행 상태, 결과 등을 위한 메타 데이터, 즉 기본적으로 가지고 있는 DB 스키마가 있는데 이 스키마가 생성되지 않으면 배치 작업 시 오류가 난다. 따라서 해당 스키마가 MySQL과 같은 DB에 생성 되어있던가, 아니면 H2 같은 인메모리 DB를 사용해야 스프링 배치가 정상적으로 작동하게 된다.

     

    잠깐 사용할 것이라면 다음과 같이 H2 인메모리 DB를 설정하면 된다.

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>

     

     

    스프링 배치 기본 흐름

    • Job: Job은 배치에서 가장 큰 단위를 뜻하며, 작업을(Step) 수행하는 총 집합이라고 보면 된다.
      • Job은 Step를 감싸고 있으며, 최소 한 개 이상의 Step를 포함하고 있어야 한다.
    • Step: Step은 작업의 항목 또는 단계를 뜻하며, Step은 Tasklet를 포함하고 있다.
    • Tasklet: Tasklet은 실제 작업의 내용이나 비즈니스 로직을 담고 있는 곳이다.

    Spring Batch 프로세스의 기본 구성

     

     

    소스 코드

    위에서 언급한 스프링 배치 기본 흐름대로 Job이 Step를 감싸고 있고, Step이 Tasklet를 감싸는 형태로 있다.
    그리고 Tasklet에서 실제 비즈니스 로직(Hello World!)을 작성하게 된다.

    package io.spring.batch.helloworld;
    
    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;
    
    @RequiredArgsConstructor
    @Configuration
    public class HelloJobConfiguration {
    
        private final JobBuilderFactory jobBuilderFactory;
        private final StepBuilderFactory stepBuilderFactory;
    
        @Bean
        public Job helloJob() {
            return jobBuilderFactory.get("helloJob")        // 잡 생성 및 이름 지정
                    .start(helloStep1())                    // 잡을 시작할 스텝 지정
                    .next(helloStep2())                     // helloStep1() 종료 후 다음 스텝 지정
                    .build();
        }
    
        @Bean
        public Step helloStep2() {
            return stepBuilderFactory.get("helloStep2")     // 스텝 생성 및 이름 지정
                    .tasklet(new Tasklet() {                // Tasklet(실제 비즈니스 로직)를 작성
                        @Override
                        public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
                            System.out.println("Hello World2 execute!!");
                            // Tasklet은 기본적으로 무한 반복인데, 다음과 같이 종료 코드를 작성해주어야 한번만 실행하게 된다.
                            // null도 아래의 코드와 마찬가지로 한번만 실행되고 종료하게 된다.
                            return RepeatStatus.FINISHED;
                        }
                    }).build();
        }
    
        @Bean
        public Step helloStep1() {
            return stepBuilderFactory.get("helloStep1")
                    .tasklet(new Tasklet() {
                        @Override
                        public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
                            System.out.println("Hello World1 execute!!");
                            return RepeatStatus.FINISHED;
                        }
                    }).build();
        }
    
    }

     

    스프링 배치 DB 스키마 개념 및 생성


    DB 스키마의 역할

    1. 스프링 배치 메타 데이터

    • 스프링 배치의 실행 및 관리를 위한 목적으로 여러 도메인들(Job, Step, JobParameters..)의 정보들을 저장, 업데이트, 조회할 수 있는 스키마를 제공
    • 과거, 현재의 실행에 대한 세세한 정보, 실행에 대한 성공과 실패 여부 등을 일목요연하게 관리함으로써 배치 운용에 있어 리스크 발생 시 빠른 대처 가능
    • DB와 연동할 경우 필수적으로 메타 테이블이 생성되어야 함

     

    2. DB 스키마 제공

    • 파일 위치: `/org/springframework/batch/core/schema*.sql`
    • 스프링 배치에서 DB 유형별로 스키마 스크립트를 제공함

     

    3. 스키마 생성 설정

    생성방법에는 아래와 같이 크게 두 가지 방법이 있다.

    • 수동 생성 - 스프링 배치에서 제공하는 쿼리 복사 후 직접 실행
    • 자동 생성 - `spring.batch.jdbc.initialize-schema` 설정해야 함, 설정 안 할 시 기본값(EMBEDDED)적용
      • `ALWAYS`
        • 스크립트 항상 실행
        • RDBMS 설정이 되어 있을 경우, 내장 DB(인메모리 등) 보다 우선적으로 실행
      • `EMBEDDED`(기본값)
        • 내장 DB일 때만 실행되며 스키마가 자동으로 생성됨
      • `NEVER`
        • 스크립트를 항상 실행 안함, 내장 DB일 경우 스크립트가 생성이 안되기 때문에 오류 발생
        • 운영 환경에서는 수동으로 스크립트 생성 후 설정하는 것을 권장

     

    DB 스키마 정보

    아래와 사진과 같이 스프링 배치에서 6개의 주요 스키마가 있다.

     

     

    Job 관련 테이블

    • BATCH_JOB_INSTANCE
      • Job이 실행될 때 JobInstance 정보가 저장되며, job_name과 job_key를 키로 하여 하나의 데이터가 저장
      • 동일한 job_name과 job_key로 중복 저장될 수 없다.
    • BATCH_JOB_EXECUTION
      • Job의 실행 상태 정보가 저장되며 Job 생성, 시작, 종료 시간, 실행 상태, 메시지 등을 관리
    • BATCH_JOB_EXECUTION_PARAMS
      • Job과 함께 실행되는 JobParameter 정보를 저장
    • BATCH_JOB_EXECUTION_CONTEXT
      • Job의 실행동안 여러가지 상태정보, 공유 데이터를 직렬화(JSON 형식)해서 저장
      • Step 간 서로 공유가 가능

     

    Step 관련 테이블

    • BATCH_STEP_EXECUTION
      • Step의 실행정보가 저장되며 Step 생성, 시작, 종료 시간, 실행 상태, 메시지 등을 관리
    • BATCH_STEP_EXECUTION_CONTEXT
      • Step의 실행동안 여러가지 상태정보, 공유 데이터를 직렬화(JSON 형식)해서 저장
      • Step 별로 저장되며 Step 간 서로 공유할 수 없음

     

    DB 스키마 자동생성/수동생성

    우선 이전에 H2 인메모리 DB 사용이 아닌 MySQL를 사용하기 위해 MySQL의 의존성을 추가한다.

    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.0.33</version>
    </dependency>

     

    또한 가볍게 테스트할 땐 유동적으로 인메모리 DB를 사용하기 위해 스프링의 프로파일 기능을 사용한다.

    spring:
      profiles:
        active: local
    #  main:
    #    allow-bean-definition-overriding: true
    
    ---
    spring:
      config:
        activate:
          on-profile: local
      datasource:
        hikari:
          jdbc-url: jdbc:h2:mem:testdb;
          username: sa
          password:
          driver-class-name: org.h2.Driver
      batch:
        jdbc:
          initialize-schema: embedded
        job:
          enabled: false
    
    ---
    spring:
      config:
        activate:
          on-profile: mysql
      datasource:
        hikari:
          jdbc-url: jdbc:mysql://localhost:3306/springbatch?useUnicode=true&characterEncoding=utf8
          username: ID
          password: PASSWORD
          driver-class-name: com.mysql.cj.jdbc.Driver
      batch:
        jdbc:
          initialize-schema: always
    #    job:
    #      enabled: false

     

     

    수동생성

    `/org/springframework/batch/core/schema*.sql` 경로에 있는 스크립트를 복사해서 생성하면 된다.

     

    자동생성

    `application.yml` 파일에 아래와 같이 등록하게 되면 스프링 배치가 실행될 때 자동으로 스키마가 등록된다.

    스키마가 이미 생성되어 있다면 생성되어진 스키마를 사용하게 된다.

    spring.batch.jdbc.initialize-schema: always

     

    위에서 Hello World!를 출력했을 때와 동일한 구조의 코드이다.

    해당 코드를 실행하게 되면 DB에 총 9개의 객체가 생성되고, 그 중 3개는 시퀀스, 6개는 메타 데이터를 위한 테이블이 생성된다.

    package io.spring.batch.helloworld;
    
    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;
    
    @Configuration
    @RequiredArgsConstructor
    public class DbJobConfiguration {
    
        private final JobBuilderFactory jobBuilderFactory;
        private final StepBuilderFactory stepBuilderFactory;
    
        @Bean
        public Job job() {
            return jobBuilderFactory.get("DbJobConfiguration").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 start!");
                    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 start!");
                    return RepeatStatus.FINISHED;
                }
            }).build();
        }
    }

     

    DB 스키마 컬럼 정보

    [JOB]

     

    BATCH_JOB_INSTANCE

    컬럼명 설명
    JOB_INSTANCE_ID 고유하게 식별할 수 있는 기본 키(PK)
    VERSION 업데이트 될 때 마다 1씩 증가
    JOB_NAME Job을 구성할 때 부여하는 Job의 이름
    JOB_KEY job_name과 jobParameter를 합쳐 해싱한 값을 저장

     

    BATCH_JOB_EXECUTION

    컬럼명 설명
    JOB_EXECUTION_ID JobExecution을 고유하게 식별할 수 있는 기본 키(PK)
    JOB_INSTANCE와 1:M 관계
    VERSION 업데이트 될 때 마다 1씩 증가
    JOB_INSTANCE_ID JOB_INSTANCE의 키 저장(외래키)
    CREATE_TIME 실행(Execution)이 생성된 시점을 TimeStamp 형식으로 기록
    START_TIME 실행(Execution)이 생성된 시점을 TimeStamp 형식으로 기록
    END_TIME 실행이 종료된 시점을 TimeStamp으로 기록하며,
    Job 실행 도중 오류가 발생해서 Job이 중단된 경우 값이 저장되지 않을 수 있음(NULL)
    STATUS 실행 상태(BatchStatus) 저장
    (COMPLETED, FAILED, STOPPED..)
    EXIT_CODE 실행 종료코드(ExitStatus) 저장
    (COMPLETED, FAILED..)
    EXIT_MESSAGE Status가 실패인 경우 실패 원인 등의 내용을 저장
    LAST_UPDATED 마지막 실행(Execution) 시점을 TimeStamp 형식으로 기록

     

    BATCH_JOB_EXECUTION_PARAMS

    컬럼명 설명
    JOB_EXECUTION_ID JobExecution 식별 키(FK)
    JOB_EXECUTION 과는 1:M 관계
    TYPE_CD STRING, DATE, LONG, DOUBLE 타입 정보
    KEY_NAME 파라미터 키 이름
    STRING_VAL 파라미터 문자 값
    DATE_VAL 파라미터 날짜 값
    LONG_VAL 파라미터 LONG 값
    DOUBLE_VAL 파라미터 DOUBLE 값
    IDENTIFYING 식별여부(TRUE, FALSE)

     

    BATCH_JOB_EXECUTION_CONTEXT

    컬럼명 설명
    JOB_EXECUTION_ID JobExecution 식별 키(PK, FK)
    JOB_EXECUTION 마다 생성
    SHORT_CONTEXT JOB의 실행 상태 정보,
    공유데이터 등의 정보를 문자열로 저장
    SERIALIZED_CONTEXT 직렬화(Serialized)된 전체 컨텍스트

     

    [STEP]

    BATCH_STEP_EXECUTION

    컬렴명 설명
    STEP_EXECUTION_ID Step의 실행정보를 고유하게 식별할 수 있는 기본 키(PK)
    VERSION 업데이트 될 때마다 1씩 증가
    STEP_NAME Step을 구성할 때 부여하는 Step 이름
    JOB_EXECUTION_ID JobExecution 기본 키(FK)
    JobExecution과는 1:M 관계
    START_TIME 실행(Execution)이 시작된 시점을 TimeStamp 형식으로 기록
    END_TIME 실행이 종료된 시점을 TimeStamp으로 기록하며,
    Job 실행 도중 오류가 발생해서 Job이 중단된 경우 값이 저장되지 않을 수 있음(NULL)
    STATUS 실행 상태(BatchStatus)를 저장
    (COMPLETED, FAILED, STOPPED..)
    COMMIT_COUNT 트랜잭션 당 커밋되는 수를 기록
    READ_COUNT 실행시점에 Read한 Item 수를 기록
    FILTER_COUNT 실행 도중 필터링 된 Item 수를 기록
    WRITE_COUNT 실행 도중 저장되고 커밋된 Item 수를 기록
    READ_SKIP_COUNT 실행 도중 Read가 Skip 된 Item 수를 기록
    WRITE_SKIP_COUNT 실행 도중 Write가 Skip 된 Item 수를 기록
    PROCESS_SKIP_COUNT 실행 도중 Process가 Skip 된 Item 수를 기록
    ROLLBACK_COUNT 실행 도중 RollBack이 일어난 수를 기록
    EXIT_CODE 실행 종료코드(ExitStatus)를 저장
    (COMPLETED, FAILED..)
    EXIT_MESSAGE Status가 실패할 경우 실패 원인 등의 내용을 저장
    LAST_UPDATED 마지막 실행(Execution) 시점을 TimeStamp 형식으로 기록

     

    BATCH_STEP_EXECUTION_CONTEXT

    컬럼명 설명
    JOB_EXECUTION_ID StepExecution 식별 키(PK, FK)
    STEP_EXECTION 마다 생성
    SHORT_CONTEXT STEP의 실행 상태정보
    공유데이터 등의 정보를 문자열로 저장
    SERIALIZED_CONTEXT 직렬화(Serialized)된 전체 컨텍스트

     

     

    댓글