우당탕탕

Spring Batch 처음 써보면서 겪은 시행착오와 해결 과정 공유합니다 본문

Tech/Spring

Spring Batch 처음 써보면서 겪은 시행착오와 해결 과정 공유합니다

모찌모찝 2026. 5. 20. 19:12

사실 Spring Batch를 처음 써보면서 생각보다 삽질을 많이 했어요. 간단한 배치 작업을 만들어야 했는데, 설정부터 실행까지 막히는 부분이 한두 군데가 아니더라고요.

이 글에서는 초기 설정, 핵심 구현, 자주 막혔던 에러들과 그 해결법을 최대한 자세히 다뤄볼게요. 저도 처음이라서 같은 고민하는 분들께 큰 도움이 될 것 같아요.

Spring Batch 처음 써보면서 겪은 시행착오 관련 이미지

Spring Batch 처음 써보면서 겪은 시행착오 관련 정보

개발 환경 / 버전 정보

저는 이번에 Java 17, Spring Boot 3.2.1 환경에서 Spring Batch를 처음 써봤습니다. 의존성은 spring-boot-starter-batch만 추가했습니다.

배치 작업 이렇게 만들었어요

가장 기본이 되는 배치 작업을 만들려면 Job, Step, 그리고 ItemReader/ ItemProcessor, ItemWriter가 필요하거든요. 저는 메모리에서 리스트를 읽고 가공해서 다시 로그에 쓰는 간단한 배치를 만들었어요.

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;

import java.util.Arrays;
import java.util.List;

@Configuration
@EnableBatchProcessing
public class SimpleBatchConfig {

    @Bean
    public ItemReader itemReader() {
        List data = Arrays.asList("apple", "banana", "carrot");
        return new ItemReader<>() {
            private int index = 0;
            @Override
            public String read() {
                if (index < data.size()) {
                    return data.get(index++);
                } else {
                    return null; // 더 이상 읽을 데이터가 없으면 null 리턴해야 함
                }
            }
        };
    }

    @Bean
    public ItemProcessor<String, String> itemProcessor() {
        return item -> item.toUpperCase(); // 소문자를 대문자로 바꾸는 간단한 처리
    }

    @Bean
    public ItemWriter itemWriter() {
        return items -> {
            for (String item : items) {
                System.out.println("Processed item: " + item);
            }
        };
    }

    @Bean
    public Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        return new StepBuilder("step1", jobRepository)
                .<String, String>chunk(2, transactionManager) // chunk 사이즈 2로 설정
                .reader(itemReader())
                .processor(itemProcessor())
                .writer(itemWriter())
                .build();
    }

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

이 코드에서 특히 중요한 점은 ItemReader가 데이터를 다 읽으면 꼭 null을 반환해야 한다는 것입니다. 이걸 깜빡하면 배치가 무한 대기 상태가 되더라고요. 그리고 chunk(2)로 설정해서 2개씩 처리하게 했는데, 이 부분이 생각보다 배치 처리 성능에 큰 영향을 줍니다.

Spring Batch 처음 써보면서 겪은 시행착오 관련 이미지

Spring Batch 처음 써보면서 겪은 시행착오 관련 정보

여기서 삽질했던 부분들

에러가 한창 떴던 건 JobRepository 관련 설정이었어요. 처음에는 별도 DB 없이 MapJobRepositoryFactoryBean 쓰면 된다고 알고 설정했는데, 실행하면 아래 같은 에러가 났거든요.

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.batch.core.repository.JobRepository' available

이게 왜 나는지 한참을 찾아봤는데, Spring Boot 3.x부터는 배치 관련 기본 설정이 다소 바뀌었더라고요. 그래서 의존성이나 설정을 안 맞추면 빈이 등록이 안 되는 문제가 생깁니다.

그래서 @EnableBatchProcessing을 붙이고, JobRepositoryPlatformTransactionManager는 이렇게 빈으로 직접 등록해서 문제를 해결했어요.

@Bean
public JobRepository jobRepository() throws Exception {
    MapJobRepositoryFactoryBean factory = new MapJobRepositoryFactoryBean();
    factory.setTransactionManager(transactionManager());
    return factory.getObject();
}

@Bean
public PlatformTransactionManager transactionManager() {
    return new ResourcelessTransactionManager();
}

심화: 실행 옵션과 재실행 전략

그리고 배치를 개발하다 보니 여러 번 실행할 때 이전 실행이 성공하지 않으면 재실행이 안 되는 문제도 만났는데요. 이건 RunIdIncrementer를 Job에 붙여서 해결했습니다. 매 실행마다 파라미터가 달라서 Spring Batch가 새 작업으로 인식하게 만드는 거죠.

만약 실패한 실행을 재시도하거나 특정 단계만 재실행하고 싶으면 JobExplorerJobOperator를 활용하는 방법도 있으니, 필요한 분들은 공식 문서 참고하시면 도움이 될 겁니다.

자주 물어보시는 것들

Q. 왜 null 반환이 꼭 필요한가요?

A. Spring Batch가 데이터를 계속 읽어야 하는데 null이 없으면 언제 끝나는지 모릅니다. 무한 대기 상태가 되니 꼭 데이터가 다 소진되면 null로 알려줘야 합니다.

Q. 배치 실행시 DB 설정이 꼭 필요한가요?

A. 기본적으로 Spring Batch는 실행 정보를 DB에 저장하도록 설계돼서 권장합니다. 하지만 테스트 용도로는 메모리 기반 MapJobRepositoryFactoryBean를 써도 됩니다.

Spring Batch 처음 써보면서 겪은 시행착오 관련 이미지

Spring Batch 처음 써보면서 겪은 시행착오 관련 정보

저는 이번에 Spring Batch로 간단한 배치를 구현하면서 이렇게 설정부터 실행까지 하나하나 겪어봤는데요, 생각보다 배치 프레임워크가 강력하고 유연해서 점점 더 활용할 맛 나더라고요. 다음에는 데이터베이스 기반 배치 처리와 더 복잡한 처리 흐름도 도전해볼 예정입니다.

Comments