본문 바로가기

Dev Log

Spring Batch 프로젝트 동작 원리 정리

728x90

프로젝트에서 배치가 어떻게 동작하는지에 대한 흐름을 정리해봤다.

1. 배포 및 실행 방법

JAR 파일로 배포

Spring Boot 프로젝트를 gradle bootJar로 빌드하면 실행 가능한 JAR 파일이 생성된다. 이 JAR에는 내장 Tomcat과 모든 의존성이 포함되어 있어 별도의 웹 서버 설치 없이 실행할 수 있다.

NCP(Naver Cloud Platform) 서버에서 다음과 같이 실행한다:

java -jar project-common-batch.jar

 

이 명령어가 실행되면 Spring Boot 애플리케이션이 시작되며, 내장 웹 서버(포트 58080)가 함께 올라간다.

실행 주체: Java 프로세스가 CommonBatchApplication.main() 메서드를 실행한다.

 

2. 전체 동작 흐름

1단계: 애플리케이션 시작

서버 시작
↓
CommonBatchApplication.main() 실행
↓
Spring Boot 컨테이너 초기화
↓
모든 Job 빈 생성 (sampleJob, employeeJob, checkupPlanJob 등)
↓
RefreshScheduler 빈 생성 (모든 Job들을 Map으로 자동 주입)

 

Spring Boot 컨테이너란?

Spring Boot 컨테이너는 객체를 생성하고 관리하는 저장소다. @Bean, @Component 등이 붙은 클래스를 찾아 객체를 생성하고, 필요한 곳에 자동으로 주입한다.

 

일반적인 Java 프로그램 vs Spring Boot 컨테이너

일반적인 Java 프로그램:
┌─────────────────┐
│  개발자가 직접  │
│  new 객체()     │  ← 매번 직접 만들어야 함
│  객체.메서드()  │
└─────────────────┘

Spring Boot 컨테이너:
┌─────────────────────────────┐
│   Spring 컨테이너 (저장소)  │
│  ┌───────────────────────┐ │
│  │ 객체1 (자동 생성)      │ │
│  │ 객체2 (자동 생성)      │ │  ← Spring이 알아서 생성/관리
│  │ 객체3 (자동 생성)      │ │
│  │ ...                    │ │
│  └───────────────────────┘ │
└─────────────────────────────┘

 

Job 빈이란?

빈(Bean) = Spring 컨테이너가 관리하는 객체

Job 빈 = 배치 작업을 실행하는 객체

실제 코드 예시:

@Configuration  // 이 클래스는 설정 클래스
public class SampleJobConfig {
    
    @Bean  // 이 메서드가 반환하는 객체가 "빈"이 됨
    public Job sampleJob() {  // 빈 이름: "sampleJob"
        return new JobBuilder("sampleJob", jobRepository)
            .start(step1())
            .build();
    }
}

 

동작 과정:

1. Spring 컨테이너 시작
   ↓
2. SampleJobConfig 클래스 발견 (@Configuration)
   ↓
3. sampleJob() 메서드 발견 (@Bean)
   ↓
4. sampleJob() 메서드 실행
   ↓
5. Job 객체 생성  ← 애플리케이션 시작 시 여기서 한 번만 생성!
   ↓
6. 컨테이너에 저장 (이름: "sampleJob")

 

컨테이너에 저장되는 모습:

Spring 컨테이너 (메모리)
├── "sampleJob" → Job 객체 (실제 배치 작업)
├── "employeeJob" → Job 객체
├── "checkupPlanJob" → Job 객체
├── "messageAtJob" → Job 객체
└── ... (다른 빈들)

 

2단계: 스케줄 등록 (30초마다 반복)

RefreshScheduler.onReady() 실행
  ↓
DB에서 활성 스케줄 조회 (hm_batch_job_schedule 테이블)
  ↓
각 스케줄마다:
  - job_name으로 Job 객체 찾기
  - cron_expr로 CronTrigger 생성
  - TaskScheduler에 등록

 

onReady() 

    @EventListener(ApplicationReadyEvent.class)
    public void onReady() {
        running.set(true);
        // 기동 직후 한 번 동기화
        refreshSchedules();
    }

 

핵심 포인트:

  • 애플리케이션이 시작되면 ApplicationReadyEvent가 발생하여 onReady() 메서드가 호출된다.
  • 이후 @Scheduled(cron = "30 * * * * *") 어노테이션으로 인해 매 분 30초마다 refreshSchedules() 메서드가 자동 실행된다.
  • DB에서 활성 스케줄을 조회하고, 각 스케줄을 ThreadPoolTaskScheduler에 등록한다.
public interface BatchJobMapper {
    @Select("""
        SELECT 
            job_schedule_seq, job_name, cron_expr, job_params_json, start_dtm, end_dtm, use_yn
        FROM hm_batch_job_schedule
        WHERE use_yn='Y'
        AND (start_dtm is null OR start_dtm <= now())
        AND (end_dtm is null OR end_dtm >= now())
    """)
    List<BatchSchedule> findActive();
}

 

ApplicationReadyEvent란?

Spring Boot 애플리케이션이 완전히 시작되어 요청을 받을 준비가 되었을 때 발생하는 이벤트다.

Spring Boot는 시작 과정에서 여러 이벤트를 순차적으로 발생시킨다.

1. ApplicationStartingEvent - 애플리케이션 시작 직후
2. ApplicationEnvironmentPreparedEvent - Environment 준비 완료
3. ApplicationContextInitializedEvent - ApplicationContext 초기화
4. ApplicationPreparedEvent - ApplicationContext 준비 완료
5. ContextRefreshedEvent - ApplicationContext 리프레시 완료
6. ApplicationStartedEvent - 애플리케이션 시작 완료 (아직 준비 안됨)
7. ApplicationReadyEvent - 애플리케이션 완전히 준비됨 ✅

 

ApplicationReadyEvent를 왜 사용할까?

모든 Bean이 생성되고 초기화된 후 실행되기 때문에 외부 서비스 연결, 캐시 초기화, 스케줄러 시작 등 초기화 작업에 적합하다.

 

3단계: 시간이 되면 자동 실행

CronTrigger가 계산한 시간 도달
  ↓
TaskScheduler 스레드 풀에서 자동 실행
  ↓
runJob() 호출
  ↓
JobLauncher가 Spring Batch Job 실행
  ↓
Job → Step → Tasklet 순차 실행
  ↓
실제 비즈니스 로직 수행 (DB 조회, API 호출 등)

 

실행 메커니즘:

  1. ThreadPoolTaskScheduler가 등록된 CronTrigger를 모니터링한다.
  2. 설정된 시간(예: 매일 새벽 2시)이 되면 스레드 풀의 스레드가 자동으로 람다 함수를 실행한다.
  3. runJob() 메서드가 호출되어:
  • 동일 Job이 이미 실행 중인지 확인 (중복 실행 방지)
  • DB에서 읽은 job_params_json을 파싱하여 Job 파라미터로 변환
  • JobLauncher.run()을 통해 Spring Batch Job을 실행한다.

실제 코드:

private void runJob(Job job, BatchSchedule s) {
    // 동일 JobName이 이미 실행 중이면 스킵
    if (isJobRunning(job)) {
        log.info("[scheduler] job={} 이미 실행중 → skip", job.getName());
        return;
    }
    
    // JSON → Map 변환
    Map<String, Object> paramMap = parseParams(s.getJobParamsJson());
    
    // JobParameters 구성
    JobParametersBuilder builder = new JobParametersBuilder();
    paramMap.forEach((k, v) -> builder.addString(k, String.valueOf(v)));
    builder.addLong("run.ts", System.currentTimeMillis());
    
    // 실행
    jobLauncher.run(job, builder.toJobParameters());
}

 

3. 핵심 메커니즘

데이터베이스 기반 동적 스케줄링

  • 스케줄 정보는 hm_batch_job_schedule 테이블에 저장된다.
  • 30초마다 DB를 조회하여 스케줄을 갱신하므로, 재시작 없이 스케줄 변경이 반영된다.

DB 스케줄 예시:

INSERT INTO hm_batch_job_schedule 
(job_name, cron_expr, job_params_json, use_yn)
VALUES 
('employeeJob', '0 0 2 * * ?', '{"companySeq": 3}', 'Y');
-- 매일 새벽 2시에 employeeJob 실행

 

자체 스케줄링 시스템

  • Spring의 @Scheduled와 ThreadPoolTaskScheduler를 사용한다.
  • 외부 cron 데몬이나 스케줄러가 필요 없다.

중복 실행 방지

  • 동일한 Job이 이미 실행 중이면 스킵한다.
  • JobExplorer를 통해 실행 상태를 확인한다.

 

4. 요약

  1. 배포: JAR 파일로 NCP 서버에 배포
  2. 실행: java -jar 명령어로 시작
  3. 스케줄링: DB 기반으로 30초마다 갱신
  4. 실행 주체: Spring의 TaskScheduler가 자동 실행
  5. 특징: 외부 스케줄러 불필요, 재시작 없이 스케줄 변경 가능

이 구조로 서버가 실행 중이면 DB에 등록된 스케줄에 따라 배치 작업이 자동으로 실행된다.

728x90