본문 바로가기
개발/Spring

[Spring] 스프링 배치(Spring Batch) 가이드 따라가기 (1)

by devhooney 2022. 12. 10.
728x90

스프링 배치 가이드

 

- 지난 포스팅에서 튜토리얼 보고 따라해봤는데, 이번에는 개념부터 천천히 공부해보자.

- 책을 보고 공부하려 했으나, 스프링과 부트의 배치 사용 문법이 많이 달라 이동욱님의 블로그를 보고 공부했다.

http://devhooney.tistory.com/134

 

[Spring] 스프링 배치(Spring Batch) 맛보기

스프링 배치 맛보기 Batch 대량의 데이터를 효율적으로 처리하는 일괄 처리 기능은 많은 사용 사례에 이상적입니다. Spring Batch의 산업 표준 처리 패턴 구현을 통해 JVM에서 강력한 배치 작업을 구

devhooney.tistory.com

 

 

728x90

1. Simple Job 생성하기

@EnableBatchProcessing
@SpringBootApplication
public class SpringBatchApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBatchApplication.class, args);
    }

}

 

- @EnableBatchProcessing

Spring Batch 기능 활성화

 

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

    @Bean
    public Job simpleJob() {
        return jobBuilderFactory.get("simpleJob")
                .start(simpleStep1())
                .build();
    }

    @Bean
    public Step simpleStep1() {
        return stepBuilderFactory.get("simpleStep1")
                .tasklet((contribution, chunkContext) -> {
                    log.info("Step1!!!!");
                    return RepeatStatus.FINISHED;
                })
                .build();
    }
    
}

 

- jobBuilderFactory.get("simpleJob")

simpleJob이라는 이름으로 Job을 생성

 

- stepBuilderFactory.get("simpleStep1")

simpleStep1이라는 이름으로 Step을 생성

 

- tasklet()

  • Step 안에서 수행될 기능을 명시
  • tasklet은 Step 안에서 단일로 수행될 커스텀한 기능을 선언할 때 사용

 

 

배치의 구조인데,

Job은 Step으로 구성되어 있고, Step은 위 그림엔 없지만, Item ~ 들과 tasklet로 구성되어 있다. 

 

이상태로 실행하면

2022-12-09 09:39:23.850  INFO 9520 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=simpleJob]] launched with the following parameters: [{}]
2022-12-09 09:39:23.873  INFO 9520 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [simpleStep1]
2022-12-09 09:39:23.879  INFO 9520 --- [           main] c.e.s.job.SimpleJobConfiguration         : Step1!!!!
2022-12-09 09:39:23.883  INFO 9520 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [simpleStep1] executed in 10ms

잘 실행된다.

여기까지는 지난번 Spring.io 의 튜토리얼과 거의 같은 난이도.

 

 

2. MariaDB와 Spring Batch 실행하기

- Spring Batch는 메타 데이터 테이블이 필요하다.

- 메타데이터에는

  • 이전에 실행한 job
  • 최근 실패한 batch parameter가 무엇이고, 성공한 것은 무엇인지
  • 다시 실행하면 어디서부터 시작할 지
  • 어떤 job에는 어떤 step이 있는지
  • step들 중 성공한 step과 실패한 step

등의 데이터가 담겨있다.

 

https://docs.spring.io/spring-batch/docs/3.0.x/reference/html/metaDataSchema.html

 

이러한 테이블들을 직접 생성해줘야 한다.

Spring Batch에 해당 스키마가 존재하므로 복사해서 만들어주면 된다.

귀찮을테니 쿼리를 저장

-- Autogenerated: do not edit this file

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  NOT NULL PRIMARY KEY ,
   VERSION BIGINT  ,
   JOB_INSTANCE_ID BIGINT NOT NULL,
   CREATE_TIME DATETIME(6) NOT NULL,
   START_TIME DATETIME(6) DEFAULT NULL ,
   END_TIME DATETIME(6) DEFAULT NULL ,
   STATUS VARCHAR(10) ,
   EXIT_CODE VARCHAR(2500) ,
   EXIT_MESSAGE VARCHAR(2500) ,
   LAST_UPDATED DATETIME(6),
   JOB_CONFIGURATION_LOCATION VARCHAR(2500) NULL,
   constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID)
   references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID)
) ENGINE=InnoDB;

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);

 

 

 

 

 

 

 

 

3. Batch_job_instance

테이블 생성 하고 실행 구성을 mysql용으로 하나 만들어주고(기존 것 복사해서 이름, profile명 변경) 실행하면 잘된다.

실행 후 

select * from batch_job_instance;

 

쿼리를 실행하면 데이터가 들어있다.

 

 

이 테이블은 Job Parameter에 따라 생성되는 테이블이다.

Job Paramter는 배치가 실행될 때 외부에서 받을 수 있는 파라미터이다.

같은 Job이라도 Job Parameter가 다르면 테이블에 저장되며, Job Parameter가 같으면 저장되지 않는다.

 

 

테스트를 위해서 코드를 수정하고

    @Bean
    public Job simpleJob() {
        return jobBuilderFactory.get("simpleJob")
//                .start(simpleStep1())
                .start(simpleStep1(null))
                .build();
    }

    @Bean
    @JobScope
//    public Step simpleStep1() {
    public Step simpleStep1(@Value("#{jobParameters[requestDate]}") String requestDate) {
        return stepBuilderFactory.get("simpleStep1")
                .tasklet((contribution, chunkContext) -> {
                    log.info("Step1!!!!");
                    log.info("requestDate = {}", requestDate);
                    return RepeatStatus.FINISHED;
                })
                .build();
    }

 

@JobScope는 Step 선언문에서만 사용 가능.

Scope는 스프링 컨테이너에서 빈이 관리되는 범위이다.

singleton, prototype, request, session, application이 있으며 기본은 singleton으로 생성된다.

 

구성편집에서 프로그램인수(Program argument)를 넣어준다.

 

실행을 하면

인수가 잘 넘어가서 로그에 찍혔다.

 

다시 batch_job_instance를 보면

select * from batch_job_instance;

 

데이터가 추가 되었다.

한 번더 실행했을 때 추가되는 지 보면

에러가 난다.

 

파라미터를 바꿔서 실행하면

 

잘되고,

 

데이터도 들어가 있다.

 

결론적으로 동일한 Job이 Job Parameter가 달라지면 그때마다 BATCH_JOB_INSTANCE에 생성되며, 동일한 Job Parameter는 여러개 존재할 수 없다.

 

 

 

 

 

- 참고

https://jojoldu.tistory.com/325

728x90