Spring Boot 초기화 순서: @PostConstruct vs @EventListener

Yong Lee·2025년 12월 18일

문제 상황

LanguageCache에서 @PostConstruct를 사용하여 언어 데이터를 캐싱하려 했으나,
languageRepository.findAll()이 빈 결과를 반환하는 문제 발생.

@PostConstruct
public void init() {
    languageRepository.findAll().forEach(language -> {
        languageCodeToIdMap.put(language.getLanguageCode(), language.getId());
    });
    log.info("Loaded {} languages into cache", languageCodeToIdMap.size());  // 0개 출력
}

원인 분석

Spring Boot 초기화 순서

┌─────────────────────────────────────────────────────────────────┐
│  1. Spring Container 시작                                        │
├─────────────────────────────────────────────────────────────────┤
│  2. Bean 생성 및 의존성 주입 (DI)                                  │
├─────────────────────────────────────────────────────────────────┤
│  3. @PostConstruct 실행  ◀── 이 시점에 DB 데이터 없음!            │
├─────────────────────────────────────────────────────────────────┤
│  4. Hibernate DDL 실행 (테이블 생성)                              │
├─────────────────────────────────────────────────────────────────┤
│  5. SQL 스크립트 실행 (데이터 INSERT)                             │
│     └── defer-datasource-initialization: true 설정 필요          │
├─────────────────────────────────────────────────────────────────┤
│  6. ApplicationReadyEvent 발생  ◀── 이 시점에 DB 데이터 있음!     │
└─────────────────────────────────────────────────────────────────┘

관련 설정 (application-local.yml)

jpa:
  hibernate:
    ddl-auto: create-drop
  defer-datasource-initialization: true  # DDL 이후에 SQL 스크립트 실행

sql:
  init:
    mode: always
    data-locations:
      - classpath:local-init/seed/01_languages.sql
      - classpath:local-init/seed/02_currencies.sql
      # ...

defer-datasource-initialization: true의 역할

설정실행 순서
false (기본값)SQL 스크립트 → Hibernate DDL (실패: 테이블 없음)
trueHibernate DDL → SQL 스크립트 (성공: 테이블 있음)

주의: 이 설정과 관계없이 @PostConstruct는 DDL/SQL 스크립트보다 먼저 실행됨.

Spring Boot 이벤트 순서

ApplicationStartingEvent
        ↓
ApplicationEnvironmentPreparedEvent
        ↓
ApplicationContextInitializedEvent
        ↓
ApplicationPreparedEvent
        ↓
ContextRefreshedEvent  ←  @PostConstruct 실행 시점
        ↓
ApplicationStartedEvent
        ↓
ApplicationReadyEvent  ←  @EventListener(ApplicationReadyEvent.class) 실행 시점
        ↓
(애플리케이션 실행 중...)

해결 방법

변경 전

import jakarta.annotation.PostConstruct;

@PostConstruct
public void init() {
    // DB 데이터 없음 - 실패
}

변경 후

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;

@EventListener(ApplicationReadyEvent.class)
public void init() {
    // 모든 초기화 완료 후 실행 - 성공
}

초기화 방법 비교

방법실행 시점SQL 스크립트 이후 실행?사용 사례
@PostConstructBean 초기화 직후외부 의존성 없는 초기화
CommandLineRunner애플리케이션 시작 후CLI 인자 처리
ApplicationRunner애플리케이션 시작 후ApplicationArguments 필요 시
@EventListener(ApplicationReadyEvent.class)완전히 준비된 후DB 데이터 의존 초기화

결론

  • @PostConstruct는 Bean 생성 직후 실행되므로 DB 초기 데이터에 의존하는 로직에는 부적합
  • defer-datasource-initialization: true 설정은 DDL과 SQL 스크립트 순서만 조정할 뿐, @PostConstruct 실행 시점에는 영향 없음
  • DB 데이터에 의존하는 캐시 초기화는 @EventListener(ApplicationReadyEvent.class) 사용 권장
profile
오늘은 어떤 새로운 것이 나를 즐겁게 할까?

1개의 댓글

comment-user-thumbnail
2025년 12월 19일

감동적인 글이네요

답글 달기