[Spring] Batch Application 생성하기

GilLog·2021년 12월 23일
2

Spring

목록 보기
22/22

#import

Spring Batch[docs.Spring.io]

Spring Batch[기억보단 기록을]

[에러노트] Spring Batch : Unknown column 'E.JOB_CONFIGURATION_LOCATION' in 'field list'


Batch Application

Batch Application특정 대용량 데이터 처리 등의 빈번하지 않은 비즈니스 로직

사용량이 적은 시간대에 단발성으로 처리하는 Application을 의미한다.
해당 글로 간략하게 참고

조건

Batch Application은 아래 다섯 가지 조건을 만족해야 한다.

1. 대용량 데이터 처리 가능

Batch Application대량의 데이터를 읽거나 조작하는 등,

특정 처리 로직을 수행할 수 있어야 한다.

2. 자동화 가능

Batch Application은 구동 후 개발자의 관여 없이도 스스로 실행되며

반복적으로 로직을 처리할 수 있어야 한다.
심각한 예외 상황을 제외

3. 신뢰 가능

BatchApplication은 로직 수행 중 문제가 발생하였을 경우,

Logging등의 형태문제가 된 부분에 대해 Tracking이 가능해야 한다.

4. 견고한 구조

Batch Application은 로직 수행 중 예외적인 데이터나 상황에 대해

중단 없이 처리할 수 있어야 한다.
예외 상황이나 충돌 상황에 대한 견고한 대비 필요

5. 성능

Batch Application은 대용량의 데이터를 처리하게 되는데

이때 예상한 Resource(시간, 메모리 등) 내에 로직 처리를 완료 하거나,

동시에 실행되는 다른 Application이나 비즈니스 로직의 성능방해되지 않게 실행되어야 한다.


Java Spring Framework를 사용하여 Web Application을 구동 중이라면,

Spring Batch를 통해 Batch Application을 생성하여 사용할 수 있다.

오늘은 간단한 Spring Batch 구성 요소와 사용 방법에 대해 알아보려한다.

Spring Boot, Java8, Mysql, Gradle 환경에서 예제 구동


Spring Batch

먼저 Spring Batch를 사용하기 전 간단하게 구성요소와 처리 방식에 대해 살펴보려 한다.

구성요소

Spring Batch가장 큰 단위하나 하나 Batch의 단위인 Job,

Job 내부에서 각 로직 처리 순서부분에 해당하는 Step

Step안에 포함Tasklet 이나 [Reader, Processor, Writer ] 단위로 구성된다.

Job, Step, Tasklet, [Reader, Processor, Writer] 의 요소들그림으로 표현하면 아래와 같다.

하나의 Batch 단위인 Job여러개의 Step으로 구성되어있고,

Step에는 Tasklet 단위로 구성되거나,

[Reader, Processor, Writer] 세 요소 묶음 형태로 구성된다.


이때 Step을 구성하는 Tasklet과 [Reader, Processor, Writer] 세 요소 묶음

병렬형태로 같이 포함되어 구성될 수 없다.
Reader > Processor > Tasklet 마무리 형태 불가능


처리 방식

Spring Batch에서 Job은 하나의 Batch 작업 단위를 의미한다.

Job을 처리하는 과정을 살펴보면 아래 그림과 같은데,


by docs.spring.io - job configure

Spring Batch에서 Batch 작업(Job)JobRepository를 통해 Instance 되며

작업(Job)의 실행JobLauncher를 통해 아래 Diagram과 같은 형태로 구동된다.


by docs.spring.io - job configure

Spring에서 @EnableBatchProcessing Annotation을 사용하면,

아래 6가지 요소들Bean으로 등록해주어 개발자가 사용할 수 있게 된다.

  • JobRepository
  • JobLauncher
  • JobRegistry
  • PlatformTransactionManager
  • JobBuilderFactory
  • StepBuilderFactory

Spring Batch 구현 형태

앞서 살펴본 내부 구성요소를 변경 하지 않고 사용할 때

JobBuilderFactoryStepBuilderFactory

두 가지 Bean을 사용하여 대부분의 Batch Application을 구현할 수 있다.


지금까지 살펴본 Spring Batch의 구성 요소들을 통해

실제 구현할 때는 아래와 같은 코드 형태이다.


// 환경 설정 관련 Class 임을 명시
@Configuration
// Spring Batch Bean들을 주입받기 위해 사용
@EnableBatchProcessing
// JobBuilderFactory, StepBuilderFactory를 생성자 형태로 DI(의존성 삽입)
@RequiredArgsConstructor
public class GillogBatchConfig {
    // @EnalbeBatchProcessing에 의해 Bean 등록된 요소 DI
    private final JobBuilderFactory jobs;
    private final StepBuilderFactory steps;

    // 하나의 Batch 단위 Job 선언 및 Bean 등록
    @Bean
    public Job job(@Qualifier("step1") Step step1, @Qualifier("step2") Step step2) {
        return jobs.get("gillogJob").start(step1).next(step2).build();
    }

    // Job을 구성하는 Step을 [Reader, Processor, Writer] 단위로 구현 및 Bean 등록
    @Bean
    protected Step step1(ItemReader<Person> reader,
                         ItemProcessor<Person, Person> processor,
                         ItemWriter<Person> writer) {
        return steps.get("step1")
            .<Person, Person> chunk(10)
            .reader(reader)
            .processor(processor)
            .writer(writer)
            .build();
    }


    // Job을 구성하는 Step을 Tasklet 단위로 구현 및 Bean 등록
    @Bean
    protected Step step2(Tasklet tasklet) {
        return steps.get("step2")
            .tasklet(tasklet)
            .build();
    }
}

Spring Boot 구현 예제

먼저 해당 예제는 Spring Boot 2.5.6, Gradle, Java8, MySQL 환경에서 구현되었다.

해당 환경의 Project가 존재한다는 가정하에 시작한다.

Spring Batch Dependency 추가

먼저 build.gradle에 아래와 같이 Spring Batch Dependency를 추가해주자.

  
dependencies {

    ...
    compile('org.springframework.boot:spring-boot-starter-batch')
    testCompile('org.springframework.batch:spring-batch-test')
    
}

위 문구 추가 후 Gradle을 Compile

@EnableBatchProcessing Annotation 추가

해당 Spring Boot의 프로젝트 최상단 Application.class
@SpringBootApplication Annotation 선언 Class

@EnableBatchProcessing를 추가해주자.

import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@EnableBatchProcessing
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Spring Batch 관련 DB Table 추가

Spring BatchBatch 구동 및 Instance 관련 Meta-Data Table이 필요하다.

해당 Project에 연동한 DB Schema에 Spring Batch Meta-Data Table이 존재하지 않을 경우,

Project 구동시에 Error가 발생하게 된다.

관련 DDL은 각 IDE의 파일명 검색을 통해서 schema- 라는 문구를 검색하면 찾을 수 있다.
Intellij 기준 Window : Shift + Ctrl + N, Mac : Shift + Command + O

혹시나 파일명 검색이 힘든 상황이라면 아래 MySQL 기준 아래 DDL을 사용하자.


CREATE TABLE BATCH_JOB_INSTANCE  (
        JOB_INSTANCE_ID BIGINT  NOT NULL PRIMARY KEY ,
        VERSION BIGINT ,
        JOB_NAME VARCHAR(100) NOT NULL,
        JOB_KEY VARCHAR(32) NOT NULL,
        constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY)
) ENGINE=InnoDB;

CREATE TABLE `batch_job_execution` (
  `JOB_EXECUTION_ID` bigint(20) NOT NULL,
  `VERSION` bigint(20) DEFAULT NULL,
  `JOB_INSTANCE_ID` bigint(20) NOT NULL,
  `CREATE_TIME` datetime(6) NOT NULL,
  `START_TIME` datetime(6) DEFAULT NULL,
  `END_TIME` datetime(6) DEFAULT NULL,
  `STATUS` varchar(10) DEFAULT NULL,
  `EXIT_CODE` varchar(2500) DEFAULT NULL,
  `EXIT_MESSAGE` varchar(2500) DEFAULT NULL,
  `LAST_UPDATED` datetime(6) DEFAULT NULL,
  `JOB_CONFIGURATION_LOCATION` varchar(2500) DEFAULT NULL,
  PRIMARY KEY (`JOB_EXECUTION_ID`),
  KEY `JOB_INST_EXEC_FK` (`JOB_INSTANCE_ID`),
  CONSTRAINT `JOB_INST_EXEC_FK` FOREIGN KEY (`JOB_INSTANCE_ID`) REFERENCES `batch_job_instance` (`JOB_INSTANCE_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE BATCH_JOB_EXECUTION_PARAMS  (
        JOB_EXECUTION_ID BIGINT NOT NULL ,
        TYPE_CD VARCHAR(6) NOT NULL ,
        KEY_NAME VARCHAR(100) NOT NULL ,
        STRING_VAL VARCHAR(250) ,
        DATE_VAL DATETIME(6) DEFAULT NULL ,
        LONG_VAL BIGINT ,
        DOUBLE_VAL DOUBLE PRECISION ,
        IDENTIFYING CHAR(1) NOT NULL ,
        constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID)
        references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ENGINE=InnoDB;

CREATE TABLE BATCH_STEP_EXECUTION  (
        STEP_EXECUTION_ID BIGINT  NOT NULL PRIMARY KEY ,
        VERSION BIGINT NOT NULL,
        STEP_NAME VARCHAR(100) NOT NULL,
        JOB_EXECUTION_ID BIGINT NOT NULL,
        START_TIME DATETIME(6) NOT NULL ,
        END_TIME DATETIME(6) DEFAULT NULL ,
        STATUS VARCHAR(10) ,
        COMMIT_COUNT BIGINT ,
        READ_COUNT BIGINT ,
        FILTER_COUNT BIGINT ,
        WRITE_COUNT BIGINT ,
        READ_SKIP_COUNT BIGINT ,
        WRITE_SKIP_COUNT BIGINT ,
        PROCESS_SKIP_COUNT BIGINT ,
        ROLLBACK_COUNT BIGINT ,
        EXIT_CODE VARCHAR(2500) ,
        EXIT_MESSAGE VARCHAR(2500) ,
        LAST_UPDATED DATETIME(6),
        constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID)
        references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ENGINE=InnoDB;

CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT  (
        STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY,
        SHORT_CONTEXT VARCHAR(2500) NOT NULL,
        SERIALIZED_CONTEXT TEXT ,
        constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID)
        references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID)
) ENGINE=InnoDB;

CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT  (
        JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY,
        SHORT_CONTEXT VARCHAR(2500) NOT NULL,
        SERIALIZED_CONTEXT TEXT ,
        constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID)
        references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ENGINE=InnoDB;

CREATE TABLE BATCH_STEP_EXECUTION_SEQ (
        ID BIGINT NOT NULL,
        UNIQUE_KEY CHAR(1) NOT NULL,
        constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;

INSERT INTO BATCH_STEP_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_STEP_EXECUTION_SEQ);

CREATE TABLE BATCH_JOB_EXECUTION_SEQ (
        ID BIGINT NOT NULL,
        UNIQUE_KEY CHAR(1) NOT NULL,
        constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;

INSERT INTO BATCH_JOB_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_EXECUTION_SEQ);

CREATE TABLE BATCH_JOB_SEQ (
        ID BIGINT NOT NULL,
        UNIQUE_KEY CHAR(1) NOT NULL,
        constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;

INSERT INTO BATCH_JOB_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_SEQ);

Spring Batch : Unkown column .. Error 발생??

혹시나 직접 찾은 DDLProject 구동 시 아래와 같은 에러 문구가 발생한다면,

BATCH_JOB_EXECUTION_PARAMS Table이 존재하는 지 확인해보고,


BATCH_JOB_EXECUTION Table

JOB_CONFIGURATION_LOCATION varchar(2500) DEFAULT NULL Column이 존재하는지 확인 해주자.

bad SQL grammar [SELECT E.JOB_EXECUTION_ID, E.START_TIME, 
E.END_TIME, E.STATUS, E.EXIT_CODE, E.EXIT_MESSAGE, E.CREATE_TIME, 
E.LAST_UPDATED, E.VERSION, E.JOB_INSTANCE_ID, E.JOB_CONFIGURATION_LOCATION 
from bomdb.BATCH_JOB_EXECUTION E, bomdb.BATCH_JOB_INSTANCE 
I where E.JOB_INSTANCE_ID=I.JOB_INSTANCE_ID and I.JOB_NAME=? 
and E.START_TIME is not NULL and E.END_TIME is NULL order by E.JOB_EXECUTION_ID desc]; 
nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException:
Unknown column 'E.JOB_CONFIGURATION_LOCATION' in 'field list'

JobConfiguration 구현

여기까지 설정을 마쳤다면 이제 Batch Application 관련 Configuration Class를 생성

Batch Job을 생성하여 구동 해볼 수 있다.

아래 예제 코드와 같은 Batch Job Configuration Class를 생성해보자.


import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@RequiredArgsConstructor
@Configuration
public class TestJobConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;
    // 다른 @Service 비즈니스 로직도 DI로 사용 가능
    // private final BatchService batchService;

    @Bean
    public Job testJob() {
        return jobBuilderFactory.get("testJob")
                // testStep1 시작
                .start(testStep1())
                    // testStep1 수행 결과 ExitStatus.FAILED 일 경우
                    .on("FAILED")
                    // failStep1 수행
                    .to(failStep1())
                    // failStep1 수행 결과에 상관 없이
                    .on("*")
                    // testStep1 종료
                    .end()
                // testStep1 후에
                .from(testStep1())
                    // testStep1 수행 결과에 상관 없이
                    .on("*")
                    // tesetStep2로 다음 Step 수행
                    .to(testStep2())
                    // testStep2 수행 결과에 상관 없이
                    // 다음 수행으로 testStep3 지정
                    .next(testStep3())
                    // testStep3 수행 결과에 상관없이
                    .on("*")
                    // stepFlow 종료(FlowBuilder 반환)
                    .end()
                // 해당 Job 종료(FlowBuilder 종료)
                .end()
                .build();
    }
    
    @Bean
    public Step testStep1() {
        return stepBuilderFactory.get("testStep1")
                .tasklet((contribution, chunkContext) ->{
                
                    // Batch 로직 구현
                    ...
                    
                    // 아래 구문으로 로직 도중 FAILED 처리 가능
                    // ExitStatus 안의 값에 따라 분기 가능
                    contribution.setExitStatus(ExitStatus.FAILED);
                    
                    // 정상 종료 시 상태 Return
                    return RepeatStatus.FINISHED;
                })
                .build();
    }
                
    @Bean
    public Step failStep1() {
        return stepBuilderFactory.get("failStep1")
                .tasklet((contribution, chunkContext) ->{
                
                    // Batch 로직 fail 분기 로직
                    ...
                    
                    // 정상 종료 시 상태 Return
                    return RepeatStatus.FINISHED;
                })
                .build();
    }
    
    @Bean
    public Step testStep2() {
        return stepBuilderFactory.get("testStep2")
                .tasklet((contribution, chunkContext) ->{
                
                    // Batch 로직 구현
                    ...
                    
                    // 정상 종료 시 상태 Return
                    return RepeatStatus.FINISHED;
                })
                .build();
    }
        
    @Bean
    public Step testStep3() {
        return stepBuilderFactory.get("testStep3")
                .tasklet((contribution, chunkContext) ->{
                
                    // Batch 로직 구현
                    ...
                    
                    // 정상 종료 시 상태 Return
                    return RepeatStatus.FINISHED;
                })
                .build();
    }
}

해당 Job 예제 형식을 참고하여 Job을 생성해,

Step 마다의 분기로 원하는 Batch Application를 구현해보자.

profile
🚀 기록보단 길록을 20.10 ~ 22.02 ⭐ Move To : https://gil-log.github.io/

1개의 댓글

comment-user-thumbnail
2023년 9월 25일

spring batch 첫 공부중인데 에러에 막혀서 허덕이고 있다가 많은 도움이 되었습니다 ㅠㅠ 감사합니다!

답글 달기