새발블로그
[Spring] Spring Batch + Scheduler 본문
1. 서론
Banklab에서 Spring Batch와 Scheduler를 붙이면서 정말 많은 삽질을 했다.ㅠㅠ
당시에 하루면 끝날 줄 알았는데 ...3일은 걸렸었던 거 같다.
그냥 레퍼런스대로 하면될 줄 알았는데 아예 모르는 상태로 하니까 어려워서 하루는 거의 공부하는 데 쓴 거 같다


Spring Batch는 금융 API 데이터를 주기적으로 수집·적재하는 데 필수였고, 스케줄러는 그 배치를 매일 새벽에 돌리는 역할을 맡았다.
나중에 upsert로 변경하였지만 초기에는 delete + insert 구조여서 다음과 같이 설계하고 시작하였다
- 환경: Spring Framework 5.3.37
- Batch 버전: 4.3.9 (5.x 시도했다가 호환성 문제로 실패 → 결국 4.x 선택)
이 글은 내가 겪은 버전 지옥, JobBuilderFactory 에러, Tasklet/Chunk 선택, 스케줄러와 배치 혼동을 정리한 기록이다~~
2. JobBuilderFactory/StepBuilderFactory 삽질
처음엔 이런 코드가 잘 돌았다.
@Bean
public Job savingsRefreshJob() {
return jobBuilderFactory.get("savingsRefreshJob")
.start(deleteSavingsStep())
.next(fetchAndInsertSavingsStep())
.build();
}
그런데 Batch를 5.x로 올리자마자 JobBuilderFactory와 StepBuilderFactory 주입 실패로 애플리케이션이 부팅조차 안 됐다.
알고 보니 Batch 5.x에서 이 팩토리들은 더 이상 빈으로 제공되지 않고, JobRepository와 TransactionManager를 직접 주입해야 한다.
결국 Spring Framework 5.3과 Batch 5.x 조합은 호환성 문제가 계속 생겨서, 다시 4.3.9로 내리면서 해결했다.
정리 1: Batch 4 vs 5 차이
- 4.x: JobBuilderFactory, StepBuilderFactory → @Autowired 가능, @EnableBatchProcessing 자동 설정 OK
- 5.x: Factory 제거, new JobBuilder("name", jobRepository) 직접 생성 필요, PlatformTransactionManager 필수 인자 전달 필요
3. Tasklet vs Chunk: 언제 뭘 쓸까?
- Tasklet: 단순한 단발성 작업 (예: 테이블 삭제)
- Chunk: 대용량 데이터, Reader → Processor → Writer 반복 처리 (예: API 데이터 적재)
내가 실제로 적용한 패턴은
1. Tasklet으로 기존 데이터 삭제 후 재적재였다.
상품이 데이터적으로 많은 건 아니었기때문에 Tasklet을 적용했다
정리 2: Tasklet vs Chunk
- Tasklet은 단일/원샷 작업, 제어 흐름에 적합
- Chunk는 데이터 처리 중심, 트랜잭션 단위 관리에 적합
- 기준: 반복적이면 Chunk, 한 번이면 Tasklet
4. 스케줄러 vs 배치
처음엔 “스케줄러 = 배치”라고 착각했는데, 사실은 전혀 다르다.
- 배치: 무엇을 실행할지 정의 (Job/Step)
- 스케줄러: 언제 실행할지 제어 (Cron 등)
정리 3: 배치 vs 스케줄러
- Spring Batch = 실행 로직 정의
- Scheduler(@Scheduled, Quartz) = 실행 타이밍 지정
- 스케줄러는 배치를 JobLauncher로 기동하는 트리거일 뿐
5. 실제 스케줄러 코드
Banklab 프로젝트에서는 금융상품 배치를 새벽 시간대에 나눠 실행했다.
@Component
public class ProductScheduler {
@Autowired
private JobLauncher jobLauncher;
@Autowired @Qualifier("depositRefreshJob")
private Job depositRefreshJob;
@Autowired @Qualifier("savingsRefreshJob")
private Job savingsRefreshJob;
// 예금 상품 배치 - 매일 오전 2시 실행
@Scheduled(cron = "0 0 2 * * *")
public void runDepositBatch() {
try {
JobParameters jobParameters = new JobParametersBuilder()
.addLong("timestamp", System.currentTimeMillis())
.toJobParameters();
jobLauncher.run(depositRefreshJob, jobParameters);
log.info("=== 예금 상품 배치 완료 ===");
} catch (Exception e) {
log.error("예금 상품 배치 실행 중 오류 발생", e);
}
}
// 적금 상품 배치 - 매일 오전 2시 5분 실행
@Scheduled(cron = "0 5 2 * * *")
public void runSavingsBatch() {
try {
JobParameters jobParameters = new JobParametersBuilder()
.addLong("timestamp", System.currentTimeMillis())
.toJobParameters();
jobLauncher.run(savingsRefreshJob, jobParameters);
log.info("=== 적금 상품 배치 완료 ===");
} catch (Exception e) {
log.error("적금 상품 배치 실행 중 오류 발생", e);
}
}
}
- @Scheduled로 실행 시간 지정
- JobLauncher.run(job, params)로 배치 기동
- JobParameters에 timestamp 추가해 매번 실행 구분
6. Config와 스키마 문제
- Batch 설정 클래스가 컴포넌트 스캔에 안 잡히면 배치 실행 자체가 안 됨
- Boot에서는 자동 구성되지만, 멀티 모듈에서는 반드시 @Configuration + @EnableBatchProcessing 포함해야 함
- 스케줄러를 쓰려면 @EnableScheduling도 루트 설정에 추가 필요
여기서 또 하나 크게 헤맸던 게 바로 스키마 문제였다.
Spring Batch는 실행 정보를 관리하기 위해 자체 메타데이터 테이블을 사용한다(BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION 등).
이걸 DB에 미리 생성하지 않으면, 애플리케이션 부팅 단계에서 오류가 발생하거나 Job 실행 시 “Table not found” 에러가 터진다.
나는 처음에 이걸 몰랐다가, 공식 SQL 스크립트(schema-mysql.sql)를 DB에 적용한 뒤에야 정상적으로 Job이 실행됐다.
- 데이터베이스 스키마 및 테이블 관리
- Spring Batch는 배치 작업을 수행하는 동안 작업의 상태, 메타데이터, 실행 정보 등을 저장
- MySQL을 데이터베이스로 사용할 때 이 테이블들을 명확하게 정의하고 관리할 필요 존재
- 테이블
- BATCH_JOB_INSTANCE: 배치 작업 인스턴스 정보를 저장 (어떤 작업이 실행됐는지)
- BATCH_JOB_EXECUTION: 배치 작업 실행의 상태 정보 저장 (성공/실패, 시작/종료 시간 등)
- BATCH_STEP_EXECUTION: 각 Step의 실행 상태 저장
- BATCH_JOB_EXECUTION_CONTEXT: 배치 작업의 실행 시 사용된 데이터 저장
- BATCH_STEP_EXECUTION_CONTEXT: 각 Step에서 사용된 임시 데이터 저장
이렇게 정리해놨다
메타테이블 생성은 프로젝트에서 할 수 있다.


우리 서비스는 mysql 을 사용했기 때문에 mysql 스키마를 사용했다
7. 교훈 & 회고
- 버전 호환성은 미리 확인하자 (Spring 5.x → Batch 4.x, Spring 6.x → Batch 5.x)
- Factory 자동주입은 언젠가 사라진다. 빌더 직접 생성법도 익혀야 한다.
- 스케줄러와 배치는 엄연히 다르다. 역할을 분리해 생각하자.
- Config와 스키마 설정을 소홀히 하지 말자.
- 제대로 알고 쓰자..
'Server > Spring' 카테고리의 다른 글
| [Spring] MyBatis에서 Enum 타입 안전하게 매핑하기 (0) | 2025.10.07 |
|---|---|
| [Spring] WebSocket + STOMP (0) | 2025.09.22 |
| [Spring] AOP (Aspect Oriented Programming) (0) | 2025.09.22 |
| [Spring] 직렬화와 역직렬화 (0) | 2025.09.22 |
| [Spring] 파일 업로드 & 다운로드 (0) | 2025.09.22 |