스프링 부트 3 및 스프링 배치 5의 새로운 기능과 변경사항을 알아보자
1. 공식에 따른 SpringBoot 3에서 스프링 배치의 변경된 점
스프링 부트 3과 함께 출시된 스프링 배치 5는 많은 중요한 인프라 변경사항을 포함하고 있다. 이러한 변경사항은 스프링 배치를 사용하는 개발자들에게 더 나은 데이터 관리, 효율적인 트랜잭션 처리, 그리고 유연한 배치 작업 구성을 가능하게 한다.
1-1. 데이터 소스 및 트랜잭션 매니저 업데이트
이전 스프링 배치 버전에서는 인메모리 작업 저장소가 사용되었다. 인메모리 작업 저장소는 데이터를 임시 메모리에 저장하는 방식으로, 애플리케이션을 재시작하면 데이터가 사라지는 단점이 있었다.
스프링 배치 5에서는 이 인메모리 작업 저장소 구현을 제거하고, 대신 JDBC(Java Database Connectivity) 기반의 JobRepository를 사용한다. 이는 데이터를 데이터베이스에 저장하여 애플리케이션을 재시작해도 데이터가 유지되도록 한다.
이제 개발자는 애플리케이션에 DataSource(데이터 소스)와 PlatformTransactionManager(트랜잭션 매니저)를 명시적으로 정의해야 한다. 이는 데이터 저장 및 트랜잭션 관리가 좀 더 명확하고 유연해진다는 의미다.
1-2. 트랜잭션 매니저 빈 노출
이전에는 @EnableBatchProcessing 어노테이션을 사용할 때 자동으로 트랜잭션 매니저 빈(bean)이 애플리케이션 컨텍스트에 노출되었다.
이제 스프링 배치 5에서는 이 어노테이션이 애플리케이션 컨텍스트에 트랜잭션 매니저 빈을 노출하지 않는다. 이는 개발자가 자신의 트랜잭션 매니저를 사용할 때 충돌을 방지할 수 있게 한다.
1-3. @EnableBatchProcessing의 새로운 속성
이 어노테이션은 이제 배치 인프라 빈을 구성하기 위한 추가적인 옵션과 매개변수를 제공한다.
이러한 새로운 속성들 덕분에, 이전에 필요했던 사용자 정의 BatchConfigurer가 더 이상 필요 없게 되었다. 이는 배치 구성을 좀 더 쉽게 만든다.
1-4. 새로운 구성 클래스: DefaultBatchConfiguration
DefaultBatchConfiguration은 새로 추가된 클래스로, 배치 인프라 빈을 구성하는 데 사용된다.
이 클래스는 필요에 따라 커스터마이징할 수 있는 기본 구성을 제공한다. 이는 개발자가 자신의 필요에 맞게 배치 작업을 더 쉽게 조정할 수 있게 해 준다.
이러한 변경사항들은 스프링 배치에서 더 명시적이고 유연한 구성 옵션을 제공하며, 스프링 생태계 전체의 발전과 일관성을 유지하고 있다. 이를 통해 개발자는 데이터 관리와 배치 작업을 더욱 효율적으로 처리할 수 있게 되었다.
2. 공식문서에서 알려주는 코드 작성 가이드
2-1. Job 구성
Job 객체는 여러 구성 옵션을 가지고 있으며, JobRepository가 필요하다. JobBuilder를 사용하여 Job을 구성할 수 있다.
예를 들어, 여러 Step을 포함하는 Job을 다음과 같이 정의할 수 있다.
// Job 정의
@Bean
public Job sampleJob(JobRepository jobRepository, Step sampleStep) {
return new JobBuilder("sampleJob", jobRepository)
.start(sampleStep)
.build();
}
이 코드는 'sampleJob'이라는 이름의 Job을 생성한다. JobBuilder는 JobRepository와 시작할 Step을 인자로 받아 Job을 구성한다. 여기서 sampleStep은 Job이 실행할 첫 번째 Step을 의미한다.
2-2. @EnableBatchProcessing의 사용
스프링 배치의 설정을 간소화하기 위한 어노테이션으로, 이를 사용하면 StepScope, JobScope와 같은 스프링 배치 관련 빈들이 자동으로 생성된다. 또한, 배치 작업을 위해 필요한 DataSource와 PlatformTransactionManager도 설정해야 한다.
2-3. DefaultBatchConfiguration 클래스 사용
스프링 배치 5.0부터 제공되는 클래스로, 기본 배치 인프라 빈을 프로그래밍 방식으로 설정할 수 있다. @EnableBatchProcessing와 동일한 기능을 제공하지만, 보다 명시적인 설정이 가능하다.
즉, 클래스 상단에 @EnableBatchProcessing를 적어줄 필요가 없다.
@Configuration
class MyJobConfiguration extends DefaultBatchConfiguration {
// Job 정의
@Bean
public Job sampleJob(JobRepository jobRepository, Step sampleStep) {
return new JobBuilder("sampleJob", jobRepository)
.start(sampleStep)
.build();
}
}
위 코드에서는 MyJobConfiguration 클래스를 통해 Job을 설정한다. DefaultBatchConfiguration을 상속받아, JobBuilder를 이용하여 Job을 정의한다.
위 코드는 Job과 Step을 정의하는 기본 방법을 보여준다. 실제 구현 시에는 필요에 따라 playerLoad, gameLoad, playerSummarization 등의 메서드를 구현하고, 데이터 소스 및 트랜잭션 관리자를 적절히 설정해야 한다.
2-4. 스프링 배치에서 스텝을 정의하는 방법
스프링 배치에서 스탭을 정의하는 방법은 다음과 같다.
@Bean
public Step sampleStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("sampleStep", jobRepository)
.<String, String>chunk(10, transactionManager) // 청크 크기 및 트랜잭션 매니저 지정
.reader(itemReader()) // ItemReader 구현체
.processor(itemProcessor()) // ItemProcessor 구현체
.writer(itemWriter()) // ItemWriter 구현체
.build();
}
여기서 chunk(10, transactionManager)는 한 번에 처리할 아이템의 수를 10으로 설정하고, 해당 트랜잭션을 관리할 매니저를 지정한다. itemReader(), itemProcessor(), itemWriter()는 각각 읽기, 처리, 쓰기 작업을 정의하는 메서드다.
이렇게 정의된 스텝은 Job에 포함될 수 있으며, 배치 작업의 일부로 실행된다. 이 방식으로 스프링 배치에서 다양한 데이터 처리 및 배치 작업을 효율적으로 구성하고 관리할 수 있다.
2-5. Reader, Processor, Writer
// ItemReader 구현체
private ItemReader<String> itemReader() {
return new ListItemReader<>(List.of("item1", "item2", "item3"));
}
// ItemProcessor 구현체
private ItemProcessor<String, String> itemProcessor() {
return item -> "Processed " + item;
}
// ItemWriter 구현체
private ItemWriter<String> itemWriter() {
return items -> items.forEach(System.out::println);
}
여기서 ItemReader는 데이터를 읽는 역할을, ItemProcessor는 읽은 데이터를 처리하는 역할을, ItemWriter는 처리된 데이터를 쓰는 역할을 수행한다.
💡 스프링 배치에서 아이템을 읽을 때 ItemReader<String>과 ListItemReader<사용자 정의 객체>와 같은 두 가지 방식을 사용할 수 있다. 이 둘 사이의 주된 차이점은 얼마나 구체적인 클래스를 사용하느냐에 달려있다.
첫 번째 방식인 ItemReader<String>을 사용하면, 실제로 어떤 ItemReader를 사용하는지 숨길 수 있다.
예를 들어 ListItemReader나 다른 어떤 ItemReader 구현체를 사용할 수 있지만, 바깥쪽에서는 그저 ItemReader 인터페이스만 보인다. 이렇게 하면 나중에 다른 종류의 ItemReader로 바꾸기 쉬워진다. 코드를 유연하게 만들고 싶을 때 이 방법이 좋다.
반면에, ListItemReader<사용자 정의 객체>처럼 구체적인 클래스를 반환하면, 그 클래스의 특정 기능이나 메소드를 사용할 수 있다.
예를 들어 ListItemReader에만 있는 특별한 기능이 필요하다면, 이 방식이 더 나을 수 있다.
두 방식 모두 유효하지만, 사용하는 상황에 따라 선택해야 한다. 첫 번째 방식인 인터페이스를 반환하는 방식이 일반적으로 더 권장되는 방식이다, 왜냐하면 이는 구현 세부 사항을 숨기고 코드의 유연성을 높여주기 때문이다. 하지만 특정 기능이 필요하다면, ListItemReader<사용자 정의 객체> 같은 구체적인 클래스를 사용하는 것도 좋은 방법이다.
3. 전체 코드 설명
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.DefaultBatchConfiguration;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import java.util.List;
@Slf4j
@Configuration
class MyJobConfiguration extends DefaultBatchConfiguration {
// Job 정의
@Bean
public Job sampleJob(JobRepository jobRepository, Step sampleStep) {
return new JobBuilder("sampleJob", jobRepository)
.start(sampleStep)
.build();
}
// Step 정의
@Bean
public Step sampleStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("sampleStep", jobRepository)
.<String, String>chunk(10, transactionManager)
.reader(itemReader())
.processor(itemProcessor())
.writer(itemWriter())
.build();
}
// ItemReader 구현체
private ItemReader<String> itemReader() {
return new ListItemReader<>(List.of("item1", "item2", "item3"));
}
// ItemProcessor 구현체
private ItemProcessor<String, String> itemProcessor() {
return item -> "Processed " + item;
}
// ItemWriter 구현체
private ItemWriter<Object> itemWriter() {
return items -> items.forEach(item -> log.info(item.toString()));
}
}
이 코드는 스프링 부트 3에서 권장되는 DefaultBatchConfiguration을 확장하는 방법으로 구성됐다:
- Job 및 Step은 @Bean 어노테이션을 사용하여 스프링 컨테이너에 빈으로 등록된다.
- ItemReader, ItemProcessor, ItemWriter는 Step 정의 내에서 직접 사용된다.
이 구성 방식은 스프링 배치 5.0의 새로운 접근 방식을 반영한다.
기존의 @EnableBatchProcessing 대신 DefaultBatchConfiguration을 확장함으로써 보다 명시적인 구성이 가능해진다.
이번에 처음으로 스프링 부트 3 버전에서 배치를 적용해 봤는데 생각보다 자잘하게 바뀐 부분이 많아서 정리했다.
이제 다음 글에서 실제로 스프링 부트 3 버전에서 스프링 배치를 적용하는 과정을 설명하겠다.
👇 이렇게 설정했을때 배치 테이블이 자동생성되지 않는 오류를 만났다면?? 👇
[Spring Boot] Spring Batch 코드 작성 후 실행 시 Table doesn't exist 에러 해결
이 포스트는 Team chillwave에서 사이드 프로젝트 중 적용했던 부분을 다시 공부하며 기록한 것입니다.
시간이 괜찮다면 팀원 '개발자의 서랍'님의 블로그도 한번 봐주세요 :)
'Spring > Spring Boot' 카테고리의 다른 글
[Spring Boot] 스프링 배치에서 chunk와 JPA pageSize 설정의 관계성 (0) | 2023.11.27 |
---|---|
[Spring Boot] 스프링 부트 3.X 버전에서 Spring Batch 테이블 자동 생성 안되는 에러 (0) | 2023.11.27 |
[Spring Boot] 분산 시스템에서의 SNS/SQS 메시지 처리: 단일 책임 원칙과 Zipkin의 Trace ID 에러 핸들링을 중심으로 (0) | 2023.11.24 |
[Spring Boot] Zipkin에서 URL 추적 설정: Sampler와 Sleuth를 활용한 스프링 부트 적용 (0) | 2023.11.22 |
[Spring Boot] AWS 분산 시스템에서 Zipkin으로 로그 추적하기: SNS/SQS 통합 실전 적용 (1) | 2023.11.20 |