Spring Batch 5 마이그레이션 — Jakarta·JobBuilder

2026-05-03확률과 통계 마스터 노트

Spring Batch 마스터 노트 시리즈 8편 완결편. Spring Boot 3.x · Spring Batch 5 · Java 17의 결정적 변화 — @EnableBatchProcessing 제거와 자동 설정 대체, JobBuilder/StepBuilder 직접 생성, chunk(size, transactionManager) 시그니처, jakarta.* 네임스페이스, mysql-connector-j 변경까지 마이그레이션 체크리스트 9단계로.

이 글은 Spring Batch 마스터 노트 시리즈의 마지막 편이자 여덟 번째 편입니다. 1~7편이 Spring Batch 4.x 기준 코드였다면, 마지막은 Spring Batch 5로 마이그레이션 하는 자리.

Spring Boot 3.x는 Spring Framework 6.x · Java 17 · Jakarta EE 9+를 기반으로 합니다. 이 변화에 따라 Spring Batch 5도 API가 일부 변경됐어요. @EnableBatchProcessing 의 의미가 뒤집히고, JobBuilder/StepBuilder 생성 방식이 바뀌었습니다. 운영 중인 4.x 코드를 5.x로 옮기는 9단계 체크리스트로 마무리.

처음 마이그레이션이 어렵게 느껴지는 이유

이유는 두 가지예요.

첫째, 변경점이 한 곳이 아니라 여러 곳에 흩어져 있습니다. @EnableBatchProcessing 제거 + JobBuilder 생성 방식 변경 + chunk 시그니처 + jakarta 네임스페이스 + MySQL 커넥터 + Java 버전 — 한 가지 빠뜨리면 빌드가 안 되거나 런타임 오류.

둘째, @EnableBatchProcessing 의미가 뒤집혔어요. 4.x에서는 "이 어노테이션이 배치 빈을 만들어 줘서 필수"였는데, 5.x에서는 "이 어노테이션이 자동 설정을 막으니 절대 쓰지 마세요"가 됐습니다. 정확히 반대.

해결법은 한 가지예요. 9단계 체크리스트 를 차례대로 따라가세요. 한 단계씩 밟으면 어렵지 않아요.

주요 변경사항 한눈에

구분 Spring Batch 4.x Spring Batch 5.x
배치 활성화 @EnableBatchProcessing 필수 자동 설정으로 불필요
JobBuilder JobBuilderFactory.get("name") new JobBuilder("name", jobRepository)
StepBuilder StepBuilderFactory.get("name") new StepBuilder("name", jobRepository)
chunk 설정 .<I,O>chunk(size) .<I,O>chunk(size, transactionManager)
Java 버전 8+ 17+
Spring Boot 2.x 3.x
Spring Framework 5.x 6.x
EE 네임스페이스 javax.* jakarta.*
DB 드라이버 mysql-connector-java mysql-connector-j

@EnableBatchProcessing — 의미가 뒤집힌 어노테이션

4.x에서의 역할

다음 빈들을 자동 제공:

  • JobBuilderFactory
  • StepBuilderFactory
  • JobLauncher
  • JobRepository
  • JobExplorer
  • PlatformTransactionManager
// 4.x — 필수
@Configuration
@EnableBatchProcessing
public class BatchConfiguration {
    
    @Autowired
    private JobBuilderFactory jobBuilderFactory;
    
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
}

5.x — 제거해야

Spring Boot 3.x 자동 설정이 자동으로 배치 인프라 제공.

// 5.x — @EnableBatchProcessing 제거!
@Configuration
public class BatchConfiguration {
    // JobBuilderFactory, StepBuilderFactory 사용 안 함
    // → JobBuilder, StepBuilder 직접 생성
}

여기서 정말 중요한 시험 함정 — 5.x에서 @EnableBatchProcessing을 남겨 두면 자동 설정이 비활성화됩니다. 빌드는 되지만 의도치 않은 동작이 발생할 수 있어요. 반드시 제거.

JobBuilder/StepBuilder — 직접 생성

4.x 방식

@Autowired
private JobBuilderFactory jobBuilderFactory;

@Autowired
private StepBuilderFactory stepBuilderFactory;

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

@Bean
public Step myStep() {
    return stepBuilderFactory.get("myStep")
        .<Product, Product>chunk(10)
        .reader(reader())
        .writer(writer())
        .build();
}

5.x 방식

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

@Bean
public Step myStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
    return new StepBuilder("myStep", jobRepository)
        .<Product, Product>chunk(10, transactionManager)
        .reader(reader())
        .writer(writer())
        .build();
}

메서드 파라미터로 의존성 주입

@Configuration
public class BatchConfiguration {
    
    // JobRepository와 TransactionManager는 Spring Boot 자동 설정으로 생성됨
    
    @Bean
    public Step csvStep(JobRepository jobRepository,
                        PlatformTransactionManager transactionManager) {
        return new StepBuilder("csvStep", jobRepository)
            .<Product, Product>chunk(10, transactionManager)
            .reader(csvReader())
            .processor(productProcessor())
            .writer(dbWriter())
            .build();
    }
    
    @Bean
    public Job csvJob(JobRepository jobRepository, Step csvStep) {
        return new JobBuilder("csvJob", jobRepository)
            .incrementer(new RunIdIncrementer())
            .start(csvStep)
            .build();
    }
}

@Autowired 필드 선언 불필요 — 메서드 파라미터로 받음. 더 명시적이고 테스트에도 유리.

Tasklet Step — TransactionManager 추가

4.x

@Bean
public Step taskletStep() {
    return stepBuilderFactory.get("taskletStep")
        .tasklet((contribution, chunkContext) -> {
            System.out.println("Task 실행");
            return RepeatStatus.FINISHED;
        })
        .build();
}

5.x

@Bean
public Step taskletStep(JobRepository jobRepository,
                        PlatformTransactionManager transactionManager) {
    return new StepBuilder("taskletStep", jobRepository)
        .tasklet((contribution, chunkContext) -> {
            System.out.println("Task 실행");
            return RepeatStatus.FINISHED;
        }, transactionManager)  // transactionManager 추가
        .build();
}

Conditional Flow — 큰 변화 없음

4.x

@Bean
public Job conditionalJob() {
    return jobBuilderFactory.get("conditionalJob")
        .start(step1())
            .on("COMPLETED").to(step2())
            .on("FAILED").fail()
        .from(step2())
            .on("*").end()
        .end()
        .build();
}

5.x

@Bean
public Job conditionalJob(JobRepository jobRepository,
                          Step step1, Step step2) {
    return new JobBuilder("conditionalJob", jobRepository)
        .start(step1)
            .on("COMPLETED").to(step2)
            .on("FAILED").fail()
        .from(step2)
            .on("*").end()
        .end()
        .build();
}

흐름 제어 메서드(on/to/from/end) 자체는 동일. JobBuilder 생성 방식만 변경.

Jakarta EE 네임스페이스

Java EE → Jakarta EE 브랜드 변경. 패키지 javax.*jakarta.*.

변경 전 (javax) 변경 후 (jakarta)
javax.validation.* jakarta.validation.*
javax.validation.constraints.* jakarta.validation.constraints.*
javax.persistence.* jakarta.persistence.*
javax.transaction.* jakarta.transaction.*
javax.servlet.* jakarta.servlet.*
javax.annotation.* jakarta.annotation.*

Validation 어노테이션 변경

4.x

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Min;
import javax.validation.constraints.Max;

public class Product {
    @NotNull
    private Integer productId;
    
    @NotBlank
    private String productName;
    
    @Min(0)
    private Double productPrice;
}

5.x

import jakarta.validation.constraints.NotBlank;  // javax → jakarta
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Max;

public class Product {
    @NotNull
    private Integer productId;
    
    @NotBlank
    private String productName;
    
    @Min(0)
    private Double productPrice;
}

여기서 시험 함정이 하나 있어요. JSR-303 Validation 어노테이션 도 패키지가 바뀝니다. BeanValidatingItemProcessor를 쓰는 도메인 모델 모두 import 변경 필요.

pom.xml 변경

Spring Boot 버전

<!-- 4.x / 2.x -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.x</version>
</parent>
<!-- 5.x / 3.x -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.0.0</version>
</parent>

MySQL Connector

<!-- 구버전 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<!-- 신버전 -->
<dependency>
    <groupId>com.mysql</groupId>                    <!-- 그룹 ID 변경 -->
    <artifactId>mysql-connector-j</artifactId>      <!-- 아티팩트 ID 변경 -->
    <scope>runtime</scope>
</dependency>

Java 버전

<!-- 4.x -->
<properties>
    <java.version>11</java.version>
</properties>

<!-- 5.x -->
<properties>
    <java.version>17</java.version>  <!-- 최소 17 필수 -->
</properties>

전체 마이그레이션 예시

Before (4.x)

import javax.validation.constraints.NotBlank;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;

@Configuration
@EnableBatchProcessing  // 4.x 필수
public class BatchConfiguration {
    
    @Autowired
    private JobBuilderFactory jobBuilderFactory;
    
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
    
    @Autowired
    private DataSource dataSource;
    
    @Bean
    public FlatFileItemReader<Product> csvReader() {
        // ...
    }
    
    @Bean
    public Step csvStep() {
        return stepBuilderFactory.get("csv-step")
            .<Product, Product>chunk(10)
            .reader(csvReader())
            .processor(processor())
            .writer(writer())
            .build();
    }
    
    @Bean
    public Job csvJob() {
        return jobBuilderFactory.get("csv-job")
            .incrementer(new RunIdIncrementer())
            .start(csvStep())
            .build();
    }
}

After (5.x)

import jakarta.validation.constraints.NotBlank;  // javax → jakarta

// @EnableBatchProcessing 제거
// JobBuilderFactory, StepBuilderFactory import 불필요

@Configuration
public class BatchConfiguration {
    
    // Factory 필드 선언 불필요 — 메서드 파라미터
    
    @Autowired
    private DataSource dataSource;
    
    @Bean
    public FlatFileItemReader<Product> csvReader() {
        // 동일한 설정
    }
    
    @Bean
    public Step csvStep(JobRepository jobRepository,
                        PlatformTransactionManager transactionManager) {
        return new StepBuilder("csv-step", jobRepository)
            .<Product, Product>chunk(10, transactionManager)
            .reader(csvReader())
            .processor(processor())
            .writer(writer())
            .build();
    }
    
    @Bean
    public Job csvJob(JobRepository jobRepository, Step csvStep) {
        return new JobBuilder("csv-job", jobRepository)
            .incrementer(new RunIdIncrementer())
            .start(csvStep)
            .build();
    }
}

Tasklet Step 마이그레이션

// 4.x
@Bean
public Step initStep() {
    return stepBuilderFactory.get("initStep")
        .tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED)
        .build();
}

// 5.x
@Bean
public Step initStep(JobRepository jobRepository,
                     PlatformTransactionManager transactionManager) {
    return new StepBuilder("initStep", jobRepository)
        .tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED,
                 transactionManager)
        .build();
}

Job 파라미터 — 큰 변화 없음

// 4.x · 5.x 모두 동일
JobParameters params = new JobParametersBuilder()
    .addString("reportDate", "2026-01-01")
    .addLong("startTime", System.currentTimeMillis())
    .toJobParameters();
// @StepScope · @Value도 동일
@Bean
@StepScope
public FlatFileItemReader<Product> csvReader(
        @Value("#{jobParameters['inputFile']}") String inputFile) {
    
    FlatFileItemReader<Product> reader = new FlatFileItemReader<>();
    reader.setResource(new FileSystemResource(inputFile));
    return reader;
}

마이그레이션 9단계 체크리스트

1. Spring Boot 버전 업그레이드

  • pom.xmlspring-boot-starter-parent 버전을 3.x.x
  • Java 버전 최소 17

2. MySQL Connector 의존성 변경

  • mysql:mysql-connector-javacom.mysql:mysql-connector-j

3. @EnableBatchProcessing 제거

  • @Configuration 클래스에서 어노테이션 제거

4. JobBuilderFactory 제거

  • @Autowired JobBuilderFactory 필드 제거
  • 메서드 파라미터로 JobRepository 받기
  • jobBuilderFactory.get("name")new JobBuilder("name", jobRepository)

5. StepBuilderFactory 제거

  • @Autowired StepBuilderFactory 필드 제거
  • 메서드 파라미터로 JobRepositoryPlatformTransactionManager 받기
  • stepBuilderFactory.get("name")new StepBuilder("name", jobRepository)

6. chunk() 메서드에 TransactionManager 추가

  • .<I,O>chunk(size).<I,O>chunk(size, transactionManager)

7. Tasklet step에 TransactionManager 추가

  • .tasklet(tasklet).tasklet(tasklet, transactionManager)

8. javax → jakarta

  • import javax.validation.*import jakarta.validation.*
  • import javax.persistence.*import jakarta.persistence.*
  • 기타 javax.* 확인·변경

9. 빌드 및 테스트

  • 컴파일 오류 수정
  • 단위 테스트
  • 통합 테스트

흔한 마이그레이션 오류

'Class not found: javax.validation.constraints.NotBlank'

원인: Boot 3.x로 업그레이드했지만 javax.validation 사용 중. 해결: import javax.*import jakarta.*.

'No qualifying bean of type JobBuilderFactory'

원인: 5.x에서 JobBuilderFactory 제거됨. 해결: JobBuilder 직접 생성.

'chunk method missing transactionManager parameter'

원인: 5.x chunk() 시그니처 변경. 해결: chunk(size)chunk(size, transactionManager).

'could not find mysql-connector-java'

원인: 아티팩트 ID 변경. 해결: mysql-connector-javamysql-connector-j.

'@EnableBatchProcessing disables auto-configuration'

원인: 5.x에서 이 어노테이션이 자동 설정 차단. 해결: 어노테이션 제거.

Spring Batch 5의 새 기능

DefaultBatchConfiguration 커스터마이징

배치 인프라 커스터마이징.

@Configuration
public class CustomBatchConfiguration extends DefaultBatchConfiguration {
    
    @Autowired
    private DataSource dataSource;
    
    @Override
    protected DataSource getDataSource() {
        return dataSource;
    }
    
    // PlatformTransactionManager, JobRepository 등 오버라이드 가능
}

Observability 통합

Spring Micrometer + 분산 추적. BatchMetrics로 Job/Step 메트릭 자동 수집.

버전 호환성 표

구분 Spring Boot 2.x Spring Boot 3.x
Spring Batch 4.x 5.x
Spring Framework 5.x 6.x
Java 최소 8 17
Jakarta EE javax.* jakarta.*
@EnableBatchProcessing 필수 사용 안 함
JobBuilderFactory 사용 Deprecated
StepBuilderFactory 사용 Deprecated
chunk() chunk(size) chunk(size, txManager)
MySQL Driver mysql-connector-java mysql-connector-j

시험 직전 한 번 더 — 자주 헷갈리는 함정 모음

여기까지가 8편의 핵심입니다. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.

  • Spring Batch 5 = Spring Boot 3.x · Java 17 · Jakarta EE 9+
  • @EnableBatchProcessing 의미가 뒤집힘 — 4.x 필수 / 5.x 제거
  • 5.x에서 자동 설정으로 배치 인프라 제공
  • JobBuilderFactory·StepBuilderFactory 제거 — Builder 직접 생성
  • new JobBuilder("name", jobRepository)
  • new StepBuilder("name", jobRepository)
  • 메서드 파라미터로 JobRepository·PlatformTransactionManager 받음
  • @Autowired 필드 선언 불필요
  • chunk(size)chunk(size, transactionManager)
  • tasklet(tasklet)tasklet(tasklet, transactionManager)
  • 조건부 흐름 메서드(on/to/from/end) 동일
  • javax.*jakarta.* 모든 EE 네임스페이스
  • javax.validation.*jakarta.validation.* (JSR-303)
  • javax.persistence.*jakarta.persistence.* (JPA)
  • Spring Boot 버전 — 3.x.x
  • Java 최소 — 17
  • mysql-connector-javamysql-connector-j
  • 그룹 ID도 mysqlcom.mysql
  • JobParameters · @StepScope · @Value — 변화 없음
  • 9단계 체크리스트 — Spring Boot → MySQL → @EnableBatchProcessing 제거 → Factory 제거 → chunk → tasklet → jakarta → 테스트
  • DefaultBatchConfiguration 커스터마이징 가능
  • Spring Micrometer로 Observability 통합

시리즈 마무리 — 8편 전체 요약

여기까지 오신 분이 있다면 진심으로 축하드립니다. 시리즈 전체에서 잡아 둔 핵심을 마지막으로 정리합니다.

  • 1편 — 입문 — Job·Step·Chunk·JobRepository 기초 모델
  • 2편 — Job 설정@EnableBatchProcessing·JobBuilder·조건부 흐름
  • 3편 — 청크 처리 — Reader/Processor/Writer 패턴, 변환·필터링·검증
  • 4편 — ItemReader — FlatFile/JdbcCursor/JdbcPaging 선택 가이드
  • 5편 — ItemWriter — FlatFile/JdbcBatch/Composite, Bulk INSERT
  • 6편 — Job Flow — 6가지 리스너·ExecutionContext·PromotionListener
  • 7편 — 오류 처리 — Skip·Retry·Restart, SkipPolicy·SkipListener
  • 8편 — Spring Batch 5 (현재 글) — 마이그레이션 9단계 체크리스트

이 8편이 Spring Batch의 거의 모든 영역을 다룹니다. 다음 단계로 가신다면 Remote Partitioning(분산 처리), Spring Cloud Data Flow와 통합, Kubernetes 배치 같은 주제가 자연스러운 확장이에요.

시리즈 다른 편

공식 문서: Spring Batch 5 Migration GuideSpring Boot 3 Migration에서 추가 자료를 볼 수 있어요.

Spring Batch는 한 번에 정복되는 도구가 아니에요. 매번 새 데이터·새 비즈니스 요구·새 함정과 만나며 평생 다듬어 가는 영역입니다. 시리즈 끝까지 읽어 주셔서 감사합니다.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!