[spring batch] spring batch 5 migration

개요

spring boot3가 release되면서 spring batch 5로 업그레이드 되었습니다.
간단하게 종속성 업그레이드로 끝나는것이 아니라 DB 스키마 변경과 jobBuilder, stepBuilder 작동 방식도 변경되었습니다.
이 부분에 대해 간단하게 정리하도록 하겠습니다.

JDK 17

spring batch 5는 jdk 17이 최소 사양인 spring framework 6 기반으로 합니다.

jakarta 패키지

javax 패키지는 전부 다 jakarta로 변경해야 합니다.

DB 스키마 업데이트

oracle과 sql-server인 경우는 sequence 관련 업데이트가 있습니다.
그외 모든 플랫폼은 아래와 같은 스키마 변경 사항이 있습니다.

BATCH_JOB_EXECUTION

JOB_CONFIGURATION_LOCATION 컬럼은 사용되지 않습니다.
아래 쿼리로 컬럼을 삭제하도록 합니다.

ALTER TABLE BATCH_JOB_EXECUTION DROP COLUMN JOB_CONFIGURATION_LOCATION;

BATCH_JOB_EXECUTION_PARAMS

batch parameter로 사용되던 string, date, long, double형 대신 <T> 제레릭으로 받을수 있게 변경되었습니다.

그리하여 각 해당 컬럼들은 삭제되고 PARAMETER_NAME, PARAMETER_TYPE, PARAMETER_VALUE 컬럼이 새로 생성되었죠.

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 ,
+++	PARAMETER_NAME VARCHAR(100) NOT NULL ,
+++	PARAMETER_TYPE VARCHAR(100) NOT NULL ,
+++	PARAMETER_VALUE VARCHAR(2500) ,
IDENTIFYING CHAR(1) NOT NULL ,
constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID)
references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
);

아래 쿼리를 통해 변경하도록 합니다.

ALTER TABLE BATCH_JOB_EXECUTION_PARAMS DROP COLUMN DATE_VAL;
ALTER TABLE BATCH_JOB_EXECUTION_PARAMS DROP COLUMN LONG_VAL;
ALTER TABLE BATCH_JOB_EXECUTION_PARAMS DROP COLUMN DOUBLE_VAL;

ALTER TABLE BATCH_JOB_EXECUTION_PARAMS CHANGE COLUMN TYPE_CD PARAMETER_TYPE VARCHAR(100);
ALTER TABLE BATCH_JOB_EXECUTION_PARAMS CHANGE COLUMN KEY_NAME PARAMETER_NAME VARCHAR(100);
ALTER TABLE BATCH_JOB_EXECUTION_PARAMS CHANGE COLUMN STRING_VAL PARAMETER_VALUE VARCHAR(2500);

spring batch 5로 migration하는 경우 위 ALTER 쿼리 실행시 크리티컬한 버그가 있습니다.

ALTER TABLE BATCH_JOB_EXECUTION_PARAMS CHANGE COLUMN TYPE_CD PARAMETER_TYPE VARCHAR(100);

위 쿼리가 문제인데 spring batch 4일 경우 Long 타입은 TYPE_CD에 LONG 으로 저장되어 있습니다.

하지만 spring batch 5일 경우 PARAMETER_TYPE 에 java.lang.Long 으로 저장됩니다.

spring batch 5에서 신규로 만들어진 job이면 문제없지만 이전 버전부터 사용하고 있던 job을 실행하면 오류를 뿜뿜하고 나올 것입니다.

아래와 같이 여러 대안이 있죠.

  1. string, date, long, double 의 각각 패키지명까지 포함하여 update 처리
  2. 테이블 삭제 후 재생성(not recommend)
  3. spring-cloud-data-flow 2.11.x 버전 적용

각각의 장단점이 있으니 신중하게 고려해서 선택하는것이 좋습니다.

BATCH_STEP_EXECUTION

CREATE_TIME 컬럼이 신규로 생성되었으며 START_TIME 컬럼에 NOT_NULL 조건이 삭제됩니다.

아래 쿼리로 변경하면 됩니다.

ALTER TABLE BATCH_STEP_EXECUTION ADD CREATE_TIME DATETIME(6) NOT NULL DEFAULT '1970-01-01 00:00:00';
ALTER TABLE BATCH_STEP_EXECUTION MODIFY START_TIME DATETIME(6) NULL;

Job repository/explorer 구현 삭제

spring batch 4에서는 아래와 같이 BatchConfigurer을 직접 구현해야 했습니다.

spring batch 5에서는 BatchConfigurer가 제거되었고 DefaultBatchConfiguration를 상속하여 커스터마이징만 하면 됩니다.

as-is

@Configuration
public class CustomBatchConfigurer implements BatchConfigurer, BeanPostProcessor {

    private final DataSource dataSource;
    private final PlatformTransactionManager transactionManager;
    
    private JobRepository jobRepository;
    private JobLauncher jobLauncher;
    private JobExplorer jobExplorer;

    @Lazy
    public CustomBatchConfigurer(@Qualifier("dataflowLazyDataSource") DataSource dataSource,
        @Qualifier("dataflowTransactionManager") PlatformTransactionManager dataflowTransactionManager) {
        this.dataSource = dataSource;
        this.transactionManager = dataflowTransactionManager;
        initialize();
    }

    @NotNull
    @Override
    public JobRepository getJobRepository() {
        return this.jobRepository;
    }

    @NotNull
    @Override
    public PlatformTransactionManager getTransactionManager() {
        return this.transactionManager;
    }

    @NotNull
    @Override
    public JobLauncher getJobLauncher() {
        return this.jobLauncher;
    }

    @NotNull
    @Override
    public JobExplorer getJobExplorer() {
        return this.jobExplorer;
    }

    private void initialize() {
    
        try {
          this.jobRepository = createJobRepository();
          this.jobExplorer = createJobExplorer();
          this.jobLauncher = createJobLauncher();
    
        } catch (Exception e) {
          throw new BatchConfigurationException(e);
        }
    }

    private JobLauncher createJobLauncher() throws Exception {
        SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
    
        jobLauncher.setJobRepository(this.jobRepository);
        jobLauncher.afterPropertiesSet();
    
        return jobLauncher;
    }
    
    private JobExplorer createJobExplorer() throws Exception {
        JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean();
    
        jobExplorerFactoryBean.setDataSource(this.dataSource);
        jobExplorerFactoryBean.afterPropertiesSet();
    
        return jobExplorerFactoryBean.getObject();
    }

    private JobRepository createJobRepository() throws Exception {
        JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
    
        jobRepositoryFactoryBean.setDataSource(this.dataSource);
        jobRepositoryFactoryBean.setTransactionManager(this.transactionManager);
        jobRepositoryFactoryBean.afterPropertiesSet();
    
        return jobRepositoryFactoryBean.getObject();
    }
}

to-be

@Configuration
public class CustomBatchConfigurer extends DefaultBatchConfiguration {

    private final DataSource dataSource;
    private final PlatformTransactionManager transactionManager;
    
    public CustomBatchConfigurer(@Qualifier("dataflowLazyDataSource") DataSource dataSource,
        @Qualifier("dataflowTransactionManager") PlatformTransactionManager dataflowTransactionManager) {
        this.dataSource = dataSource;
        this.transactionManager = dataflowTransactionManager;
    }
    
    @Override
    protected @NotNull DataSource getDataSource() {
        return this.dataSource;
    }
    
    @Override
    protected @NotNull PlatformTransactionManager getTransactionManager() {
        return this.transactionManager;
    }
}

또한 @EnableBatchProcessing 어노테이션방식으로 dataSourceRef, transactionManagerRef 등 다양한 옵션을 넣을 수 있습니다.

@Configuration
@EnableBatchProcessing(dataSourceRef = "dataflowLazyDataSource",
transactionManagerRef = "dataflowTransactionManager")
public class CustomBatchConfigurer implements BeanPostProcessor {

}

DefaultBatchConfiguration와 @EnableBatchProcessing 동시에 사용 불가하다.

명시적 transactionManager 구성

spring batch 4까지는 @EnableBatchProcessing 를 통해 트랜잭션 매니저가 spring context에 노출되었습니다.

하지만 무조건적인 노출은 사용자 정의 트랜잭션 매니저 적용하는데 문제가 발생하게 되었죠.

관련 이슈 →

이 부분을 수정하기 위해 tasklet에서도 명시적으로 transactionManager를 구성하도록 변경되었습니다.

as-is

// Sample with v4
@Configuration
@EnableBatchProcessing
public class MyStepConfig {

    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    @Bean
    public Step myStep() {
        return this.stepBuilderFactory.get("myStep")
                .tasklet(..) // or .chunk()
                .build();
    }

}

to-be

// Sample with v5
@Configuration
@EnableBatchProcessing
public class MyStepConfig {

    @Bean
    public Tasklet myTasklet() {
       return new MyTasklet();
    }

    @Bean
    public Step myStep(JobRepository jobRepository, Tasklet myTasklet, PlatformTransactionManager transactionManager) {
        return new StepBuilder("myStep", jobRepository)
                .tasklet(myTasklet, transactionManager) // or .chunk(chunkSize, transactionManager)
                .build();
    }

}

JobBuilderFactory/StepBuilderFactory deprecated

JobBuilderFactory/StepBuilderFactory가 deprecated되었으며 spring context에서도 삭제되었습니다.

메서드는 아직 삭제되지 않았지만 intellij에서 warning 메시지를 노출할 것입니다.

아래와 같이 name, jobRepository를 parameter로 하여 생성하도록 합니다.

as-is

// Sample with v4
@Configuration
@EnableBatchProcessing
public class MyJobConfig {

    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    @Bean
    public Job myJob(Step step) {
        return this.jobBuilderFactory.get("myJob")
                .start(step)
                .build();
    }

}

to-be

// Sample with v5
@Configuration
@EnableBatchProcessing
public class MyJobConfig {

    @Bean
    public Job myJob(JobRepository jobRepository, Step step) {
        return new JobBuilder("myJob", jobRepository)
                .start(step)
                .build();
    }

}

spring batch 5 migration

2023

[linux] shell script version compare

최대 1 분 소요

개요 linux를 사용하다 보면 version 비교하는 기능이 필요합니다. 특히 기존 설치된 패키지의 version을 확인하여 업데이트할 경우가 있겠죠. 아래와 같이 간단한 shell script로 구현할 수 있습니다.

[jenkins] jenkins docker install

1 분 소요

ci/cd 오픈소스 도구로 가장 많이 사랑 받는 jenkins에 대해 포스팅 해보겠습니다. 먼저 설치부터 해야겠지요? 항상 패키지 매니저로 설치했었는데 이번에는 docker로 설치해보도록 하겠습니다.

[springboot] springboot history

2 분 소요

springboot 탄생 배경 springboot란 spring framework를 좀 더 쉽게 개발/배포할려는 목적으로 만들어 졌습니다. 2012년 Mike Youngstrom은 spring 프레임워크에서 컨테이너 없는 웹 애플리케이션 아키텍처에 대한 지원을 요청하는 spring...

[springboot] springboot3 querydsl 적용

1 분 소요

개요 springboot3로 메이저 업그레이드 되면서 JPA + querydsl 셋팅 환경에 변화가 생겼습니다. 기존 의존성으로는 작동하지 않고 jakarta classification을 추가해야 작동하는 이슈가 발생합니다. springboot3부터 javax -> jakar...

[springboot] springboot3 migration

최대 1 분 소요

개요 2022년 하반기에 springboot3가 공식 release 되었습니다. springboot2가 2018년 상반기에 release되고 나서 새롭게 판올림 버전으로 가장 큰 변화로는 아래와 같습니다. spring framework 6 적용 최소 사양 JDK 17 ...

[springboot] springboot initializer

최대 1 분 소요

개요 항상 intellij ultimate 버전만 사용하고 있었는데 무슨 바람이 난건지.. intellij ce 버전에 도전하였습니다. springboot 프로젝트 생성이며.. 그 밖에 기본적으로 될꺼라 싶은것 중에 안되는 녀석들도 꽤 있더군요. 이번 시간엔 간단하게 spingbo...

[querydsl] querydsl No release for a long time

최대 1 분 소요

개요 JPA를 spring data jpa + querydsl과의 조합으로 접하는 경우가 많습니다. spring data jpa에서 제공해주는 specification으로도 충분히 해낼수 있지만 querydsl에 비할바는 아닙니다. entity에 wrapper Q클래스를 생성하여 ...

[jekyll] jekyll install

6 분 소요

개요 오랫동안 방치했던 블로그를 다시 열면서 jekyll를 다시 설치해봤습니다. 설치 jekyll 프로젝트로 이동하여 아래 명령어를 입력합니다. gem install jekyll bundler Fetching pathutil-0.16.2.gem Fetching terminal-t...

맨 위로 이동 ↑

2021

[linux] Parse yaml

최대 1 분 소요

bash를 사용하여 yaml 파일을 파싱 및 환경 변수로 손쉽게 등록할 수 있습니다.

[유틸리티] Mock Http Status Test

최대 1 분 소요

외부 통신에 대한 Error 처리는 앱을 더욱 더 견고하게 만들 수 있습니다. Error 처리를 위해 엔드포인트에 대한 Http Status Code를 억지로 생성하는것은 매우 귀찮은 일이라고 할까요? 보다 간편하게 Mock 서버를 두는게 더 효율적이라고 볼 수 있습니다.

맨 위로 이동 ↑