우당탕탕

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

Tech/Spring

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

모찌모찝 2026. 5. 26. 19:39

처음에 Spring Batch를 도입해서 배치 작업을 만들어보면서 생각보다 삽질이 많았어요. 공식 문서만 읽으면 쉬워 보이는데, 실제로 토이 프로젝트에 적용해보니 의외로 설정 문제부터 실행 오류까지 여기저기서 막혀서 시간을 많이 잡아먹었거든요.

이 글에서는 제가 Spring Batch를 처음 써보면서 겪은 대표적인 시행착오와 그 해결법을 정리해봤어요. 배치 Job 설계부터 Step 구현, 그리고 흔히 발생하는 에러 해결까지 한 번에 다뤄서, 직접 써보려는 분들이 삽질을 줄일 수 있도록 도와드릴게요.

개발 환경 / 버전 정보

제가 사용한 주요 버전은 Java 17, Spring Boot 3.1.0, 그리고 Spring Batch 4.3.7입니다. 이 조합에서 경험한 내용을 중심으로 설명할게요.

Spring Batch Job 구조 이렇게 잡으면 됩니다

처음에는 Job과 Step, Tasklet, ItemReader/ItemProcessor/ItemWriter 개념이 헷갈렸는데요, 간단한 예제로 직접 구현해보니 이해가 쏙 되더라고요. 저는 CSV 파일을 읽어서 DB에 저장하는 배치를 만들었는데, 핵심 부분은 아래 코드예요.

@Bean
public Job csvToDatabaseJob(JobBuilderFactory jobBuilderFactory, Step step) {
    return jobBuilderFactory.get("csvToDatabaseJob")
        .start(step)
        .build();
}

@Bean
public Step step(StepBuilderFactory stepBuilderFactory,
                 ItemReader<User> reader,
                 ItemProcessor<User, User> processor,
                 ItemWriter<User> writer) {
    return stepBuilderFactory.get("step")
        .<User, User>chunk(100)
        .reader(reader)
        .processor(processor)
        .writer(writer)
        .build();
}

이렇게 Job이 Step을 포함하고, Step은 chunk 단위로 처리한다는 구조가 머릿속에 잘 들어왔어요. 그리고 각각의 reader, processor, writer는 Bean으로 등록해서 주입해주는 식이에요.

여기서 많이 틀렸던 설정과 해결법

실제로 막힌 부분 중 가장 흔한 게 DataSource 설정이었는데요, Spring Batch는 job 실행 기록을 DB 테이블에 저장합니다. 그래서 스키마를 안 만들거나 DataSource 설정이 잘못되면 "org.springframework.jdbc.BadSqlGrammarException" 같은 에러가 나더라고요.

Caused by: org.springframework.jdbc.BadSqlGrammarException: 
... Table "BATCH_JOB_INSTANCE" not found ...

이럴 땐 꼭 Spring Batch 기본 스키마를 만들어줘야 해요. 저는 MySQL 8 기준으로 schema-mysql.sql을 batch 테이블용 스키마로 사용했는데, resources 폴더에 넣고 실행하거나 직접 DB에 붙여서 만들어줬어요.

코드 작성하며 겪은 실행 문제와 해결 과정

처음에는 ItemReader가 null을 반환해서 배치가 제대로 안 끝나길래 "왜 안 끝나지?" 난감했는데요, 여기서 "null을 반환하면 처리 종료"라는 개념을 몰랐던 거였어요. 그래서 코드를 좀 더 명확히 써야 했습니다.

@Override
public User read() throws Exception {
    if (!hasNextRecord()) {
        return null; // 이걸 꼭 해줘야 batch 종료됨
    }
    return nextUser();
}

이 부분에서 null 반환 안 하면 배치가 무한 대기하는 문제가 생겨서 많이 당황했어요.

심화: Batch 성능 튜닝해봤더니

chunk 사이즈 조정, 병렬 처리, 그리고 트랜잭션 범위 관리가 성능에 큰 영향을 미치는 걸 알게 됐어요. 저는 기본 chunk 사이즈 100으로 시작했다가, 데이터 크기에 따라 1000까지 올려봤는데 처리 속도가 꽤 차이나더라고요.

그리고 Spring Batch TaskExecutor를 써서 Step 병렬 실행도 시도했는데, 이 과정에서 트랜잭션 충돌 문제도 있어서 살짝 까다로웠어요.

자주 물어보시는 것들

Q. Job과 Step을 나누는 기준이 뭔가요?

A. 일반적으로 독립적인 트랜잭션 단위 기준으로 Step을 나눕니다. 예를 들어 파일 읽기와 DB 저장은 하나 Step에 묶고, 후처리 같은 것은 별도의 Step으로 두기도 하죠.

Q. 배치 실행 시점은 어떻게 관리하면 좋나요?

A. 저는 스프링 스케줄러 @Scheduled 어노테이션을 써서 일정 주기로 호출하는 방식을 썼어요. 또는 OS 크론과 연동해도 무방합니다.

배치를 직접 구현해보면서 Job, Step 개념부터 실행 문제까지 하나씩 해결하다 보니 이제야 배치가 뭔지 좀 감이 옵니다. 다음에는 더 복잡한 테이블 조인이나 멀티 스텝 흐름 구현도 도전해보려 해요.

Comments