TL;DR

  • Hibernate ddl-auto의 핵심 개념을 빠르게 파악할 수 있다.
  • 배경과 이유를 통해 왜 필요한지 맥락을 이해할 수 있다.
  • 특징과 상세 내용을 통해 실무 적용 포인트를 확인할 수 있다.

1. 개념

Hibernate ddl-auto의 핵심 정의와 문제 공간을 간단히 정리한다.

2. 배경

이 주제가 등장한 기술적·조직적 배경과 기존 접근의 한계를 설명한다.

3. 이유

왜 지금 이 방식을 채택해야 하는지, 기대 효과와 트레이드오프를 함께 정리한다.

4. 특징

핵심 동작 방식, 장단점, 적용 시 주의점을 빠르게 훑을 수 있도록 요약한다.

5. 상세 내용

Hibernate ddl-auto

작성일: 2026-01-30 카테고리: Backend / Java / JPA / Hibernate 포함 내용: ddl-auto, 스키마 자동 생성, update, validate, create, Flyway, Liquibase, 마이그레이션, fail-fast, Source of Truth, additive-only, SchemaManagementException, 스키마 검증, INFORMATION_SCHEMA


1. ddl-auto란?

약자/이름

DDL = Data Definition Language
      └── CREATE, ALTER, DROP 등 테이블 구조를 정의하는 SQL

ddl-auto = DDL 자동화
           └── Hibernate가 Java 엔티티 기준으로 DB 스키마를 자동 관리

설정 위치: application.yml의 spring.jpa.hibernate.ddl-auto

2. 등장 배경

전통적인 개발 방식의 문제

┌─────────────────────────────────────────────────────────────────┐
│                 전통적인 개발 방식 (수동)                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  1단계: DBA가 SQL로 테이블 생성                                   │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  CREATE TABLE users (                                        │ │
│  │      id BIGINT PRIMARY KEY,                                 │ │
│  │      name VARCHAR(100),                                     │ │
│  │      email VARCHAR(255)                                     │ │
│  │  );                                                          │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
│  2단계: 개발자가 Java 클래스 작성                                 │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  public class User {                                         │ │
│  │      private Long id;                                        │ │
│  │      private String name;                                    │ │
│  │      private String email;                                   │ │
│  │  }                                                           │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
│  문제점:                                                          │
│  ├── 테이블과 클래스를 "두 번" 정의 (중복 작업)                   │
│  ├── 필드 추가 시 SQL도 수정, 클래스도 수정                       │
│  ├── 오타나 불일치 발생 가능                                      │
│  ├── 개발자가 SQL을 잘 모를 수 있음                               │
│  └── 환경마다 (로컬, 개발, 스테이징) 스키마 동기화 어려움          │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

Hibernate의 해결책

┌─────────────────────────────────────────────────────────────────┐
│                 Hibernate의 아이디어                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  "Java 클래스만 정의하면, DB 테이블은 자동으로 만들어주지!"       │
│                                                                   │
│  핵심 철학: "코드가 진실의 원천(Single Source of Truth)"          │
│                                                                   │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  @Entity                                                     │ │
│  │  @Table(name = "users")                                      │ │
│  │  public class User {                                         │ │
│  │                                                              │ │
│  │      @Id                                                     │ │
│  │      @GeneratedValue(strategy = GenerationType.IDENTITY)    │ │
│  │      private Long id;                                        │ │
│  │                                                              │ │
│  │      @Column(length = 100)                                   │ │
│  │      private String name;                                    │ │
│  │                                                              │ │
│  │      @Column(length = 255, unique = true)                    │ │
│  │      private String email;                                   │ │
│  │  }                                                           │ │
│  └────────────────────────────────────────────────────────────┘ │
│                          │                                        │
│                          ▼                                        │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  Hibernate가 자동 생성:                                      │ │
│  │                                                              │ │
│  │  CREATE TABLE users (                                        │ │
│  │      id BIGINT AUTO_INCREMENT PRIMARY KEY,                  │ │
│  │      name VARCHAR(100),                                     │ │
│  │      email VARCHAR(255) UNIQUE                              │ │
│  │  );                                                          │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
│  장점:                                                            │
│  ├── SQL 몰라도 테이블 생성 가능                                  │
│  ├── 한 곳만 수정하면 됨 (Java 클래스)                            │
│  ├── 개발 속도 향상                                               │
│  └── 오타/불일치 방지                                             │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

3. ddl-auto 옵션 종류

설정 방법

# application.yml (Spring Boot)
spring:
  jpa:
    hibernate:
      ddl-auto: update   # ← 여기서 설정

옵션별 동작

┌─────────────────────────────────────────────────────────────────┐
│                    ddl-auto 옵션 종류                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ┌──────────┬────────────────────────────────────────────────┐  │
│  │  옵션    │  동작                                           │  │
│  ├──────────┼────────────────────────────────────────────────┤  │
│  │  none    │  아무것도 안 함 (수동 관리)                      │  │
│  │          │  → 프로덕션 권장                                 │  │
│  │          │  → Hibernate가 스키마에 손대지 않음              │  │
│  ├──────────┼────────────────────────────────────────────────┤  │
│  │  validate│  엔티티와 테이블이 맞는지 검증만                 │  │
│  │          │  → 불일치 시 앱 시작 실패 (에러)                 │  │
│  │          │  → 스키마 수정 안 함                             │  │
│  │          │  → 프로덕션에서 안전하게 검증용                  │  │
│  ├──────────┼────────────────────────────────────────────────┤  │
│  │  update  │  엔티티 기준으로 테이블 변경 ⭐                  │  │
│  │          │  → 새 컬럼 추가 O                                │  │
│  │          │  → 컬럼 삭제 X (안전장치)                        │  │
│  │          │  → 타입 변경 X (안전장치)                        │  │
│  │          │  → 개발 환경에서 편리                            │  │
│  ├──────────┼────────────────────────────────────────────────┤  │
│  │  create  │  앱 시작 시 테이블 삭제(DROP) 후 새로 생성       │  │
│  │          │  → ⚠️ 기존 데이터 모두 삭제됨!                   │  │
│  │          │  → 테스트용                                      │  │
│  ├──────────┼────────────────────────────────────────────────┤  │
│  │create-drop│ create + 앱 종료 시 테이블 삭제                 │  │
│  │          │  → 앱 끄면 테이블도 사라짐                       │  │
│  │          │  → 인메모리 DB 테스트용 (H2)                     │  │
│  └──────────┴────────────────────────────────────────────────┘  │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

옵션 비교 다이어그램

┌─────────────────────────────────────────────────────────────────┐
│                   옵션별 동작 비교                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  앱 시작 시:                                                      │
│                                                                   │
│  none       ─────────────────────────────────────→ 아무것도 안함 │
│                                                                   │
│  validate   ───[엔티티↔DB 비교]───┬─ 일치 ─→ 정상 시작          │
│                                   └─ 불일치 → 에러! 앱 종료      │
│                                                                   │
│  update     ───[엔티티↔DB 비교]───┬─ 일치 ─→ 정상 시작          │
│                                   └─ 차이 → ALTER TABLE 실행     │
│                                                                   │
│  create     ───[DROP TABLE]───[CREATE TABLE]───→ 새 테이블      │
│                  ⚠️ 데이터 삭제!                                  │
│                                                                   │
│  create-drop ──[DROP]──[CREATE]─── 앱 실행 ───[앱 종료시 DROP]   │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

4. update 상세 동작

필드 추가 시나리오

┌─────────────────────────────────────────────────────────────────┐
│                    ddl-auto: update 동작                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  시나리오: 엔티티에 phone 필드 추가                               │
│                                                                   │
│  [Before]                                                         │
│  ┌────────────────────┐     ┌────────────────────┐              │
│  │  User.java         │     │  users 테이블       │              │
│  │  - id              │     │  - id              │              │
│  │  - name            │     │  - name            │              │
│  │  - email           │     │  - email           │              │
│  └────────────────────┘     └────────────────────┘              │
│                                                                   │
│  [개발자 작업] 필드 추가                                          │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  @Entity                                                     │ │
│  │  public class User {                                         │ │
│  │      // 기존 필드들...                                       │ │
│  │                                                              │ │
│  │      @Column(length = 20)                                    │ │
│  │      private String phone;  // ← 새 필드 추가                │ │
│  │  }                                                           │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
│  [앱 재시작 시 Hibernate 동작]                                    │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  1. DB 메타데이터 조회 (현재 테이블 구조 확인)               │ │
│  │  2. 엔티티 클래스 분석                                       │ │
│  │  3. 차이점 발견: phone 컬럼 없음                             │ │
│  │  4. DDL 생성 및 실행:                                        │ │
│  │                                                              │ │
│  │     ALTER TABLE users ADD COLUMN phone VARCHAR(20);          │ │
│  │                                                              │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
│  [After]                                                          │
│  ┌────────────────────┐     ┌────────────────────┐              │
│  │  User.java         │     │  users 테이블       │              │
│  │  - id              │     │  - id              │              │
│  │  - name            │     │  - name            │              │
│  │  - email           │     │  - email           │              │
│  │  - phone ✅        │     │  - phone ✅        │              │
│  └────────────────────┘     └────────────────────┘              │
│                                                                   │
│  개발자 입장:                                                     │
│  → Java 코드만 수정하면 끝!                                       │
│  → SQL 작성 불필요                                                │
│  → 앱 재시작만 하면 자동 반영                                     │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

update의 한계 (중요!)

┌─────────────────────────────────────────────────────────────────┐
│                    update가 안 되는 것들                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ❌ 1. 컬럼 삭제                                                  │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  // 엔티티에서 필드를 삭제해도                               │ │
│  │  // DB 컬럼은 그대로 남아있음                                │ │
│  │                                                              │ │
│  │  이유: 데이터 손실 방지를 위한 안전장치                      │ │
│  │  해결: 수동으로 ALTER TABLE DROP COLUMN 실행                 │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
│  ❌ 2. 컬럼 타입 변경                                             │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  // String → Integer 변경해도                                │ │
│  │  // DB는 VARCHAR 그대로                                      │ │
│  │                                                              │ │
│  │  이유: 기존 데이터 변환 실패 가능                            │ │
│  │  예: "hello" → Integer로 변환 불가                          │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
│  ❌ 3. 컬럼 이름 변경                                             │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  // name → fullName 으로 필드명 변경하면                     │ │
│  │  // Hibernate는 "새 컬럼 추가"로 인식                        │ │
│  │                                                              │ │
│  │  결과:                                                       │ │
│  │  - name 컬럼 그대로 (삭제 안 됨)                             │ │
│  │  - fullName 컬럼 추가                                        │ │
│  │  - 기존 데이터는 name에 남아있음                             │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
│  ❌ 4. 제약조건 변경                                              │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  // @Column(nullable = false) 추가해도                       │ │
│  │  // 기존에 NULL 데이터가 있으면 실패                         │ │
│  │                                                              │ │
│  │  // @Column(length = 50) → length = 100 변경                 │ │
│  │  // 적용 안 될 수 있음 (DB 종류에 따라 다름)                 │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  결론:                                                       │ │
│  │  update는 "추가"만 잘 됨                                     │ │
│  │  "변경/삭제"는 안전하게 막혀있음                             │ │
│  │  → 복잡한 변경은 수동 마이그레이션 필요                      │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

5. 환경별 권장 설정

┌─────────────────────────────────────────────────────────────────┐
│                   환경별 권장 ddl-auto 설정                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ┌─────────────┬─────────────┬──────────────────────────────┐   │
│  │  환경       │  ddl-auto   │  이유                         │   │
│  ├─────────────┼─────────────┼──────────────────────────────┤   │
│  │  로컬 개발  │  update     │  빠른 개발, 자동 스키마 변경  │   │
│  │             │  또는 create│  (매번 초기화해도 OK)         │   │
│  ├─────────────┼─────────────┼──────────────────────────────┤   │
│  │  테스트     │  create-drop│  매 테스트마다 깨끗한 DB      │   │
│  │  (CI/CD)    │             │  H2 인메모리 DB와 함께       │   │
│  ├─────────────┼─────────────┼──────────────────────────────┤   │
│  │  스테이징   │  validate   │  스키마 검증만               │   │
│  │             │             │  수동 마이그레이션 사용       │   │
│  ├─────────────┼─────────────┼──────────────────────────────┤   │
│  │  프로덕션   │  none       │  절대 자동 변경 금지!        │   │
│  │             │  또는       │  Flyway/Liquibase 사용       │   │
│  │             │  validate   │                              │   │
│  └─────────────┴─────────────┴──────────────────────────────┘   │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

프로덕션에서 update 쓰면 안 되는 이유

┌─────────────────────────────────────────────────────────────────┐
│              ⚠️ 프로덕션에서 update 금지 이유                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  1. 예측 불가능한 스키마 변경                                     │
│     └── 개발자가 실수로 필드 추가하면 즉시 ALTER 실행             │
│     └── 코드 리뷰 없이 DB 변경됨                                  │
│                                                                   │
│  2. 롤백 불가능                                                   │
│     └── 배포 실패 시 코드는 롤백해도                              │
│     └── 이미 추가된 컬럼은 그대로                                 │
│                                                                   │
│  3. 다운타임 발생 가능                                            │
│     └── 대용량 테이블 ALTER는 락(Lock) 발생                       │
│     └── 수백만 행 테이블 → 수 분~수 시간 락                       │
│     └── 서비스 장애로 이어짐                                      │
│                                                                   │
│  4. 감사(Audit) 추적 불가                                         │
│     └── 누가 언제 무슨 변경을 했는지 기록 없음                    │
│     └── 보안/컴플라이언스 문제                                    │
│                                                                   │
│  5. 동시 배포 문제                                                │
│     └── 여러 서버가 동시에 시작하면                               │
│     └── 같은 ALTER 여러 번 실행 시도                              │
│     └── 충돌/에러 발생 가능                                       │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

6. 프로덕션: 마이그레이션 도구 사용

Flyway 소개

┌─────────────────────────────────────────────────────────────────┐
│                    Flyway - DB 마이그레이션 도구                   │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  개념: SQL 파일로 스키마 변경을 버전 관리                         │
│                                                                   │
│  resources/db/migration/                                          │
│  ├── V1__create_users_table.sql       # 버전 1                   │
│  ├── V2__add_phone_column.sql         # 버전 2                   │
│  ├── V3__add_index_on_email.sql       # 버전 3                   │
│  └── V4__create_orders_table.sql      # 버전 4                   │
│                                                                   │
│  파일명 규칙:                                                      │
│  V{버전}__{설명}.sql                                              │
│  └── V는 대문자                                                   │
│  └── 버전은 숫자                                                  │
│  └── __ (언더스코어 2개)                                          │
│  └── 설명은 snake_case                                            │
│                                                                   │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  -- V2__add_phone_column.sql                                 │ │
│  │                                                              │ │
│  │  ALTER TABLE users ADD COLUMN phone VARCHAR(20);             │ │
│  │  CREATE INDEX idx_users_phone ON users(phone);               │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
│  동작:                                                            │
│  1. 앱 시작 시 Flyway가 실행됨                                    │
│  2. flyway_schema_history 테이블에서 적용된 버전 확인             │
│  3. 아직 적용 안 된 SQL 파일만 순서대로 실행                      │
│  4. 실행 기록 저장                                                │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

Flyway vs ddl-auto 비교

┌─────────────────────────────────────────────────────────────────┐
│                   Flyway vs ddl-auto 비교                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ┌─────────────────┬─────────────────┬─────────────────────┐    │
│  │  항목           │  ddl-auto       │  Flyway             │    │
│  ├─────────────────┼─────────────────┼─────────────────────┤    │
│  │  버전 관리      │  ❌ 없음        │  ✅ Git에 저장      │    │
│  │  코드 리뷰      │  ❌ 불가        │  ✅ PR로 리뷰      │    │
│  │  롤백           │  ❌ 불가        │  ✅ 가능           │    │
│  │  변경 이력      │  ❌ 없음        │  ✅ 테이블에 기록  │    │
│  │  복잡한 변경    │  ❌ 제한적      │  ✅ SQL 직접 작성  │    │
│  │  데이터 마이그  │  ❌ 불가        │  ✅ SQL로 가능     │    │
│  │  개발 편의성    │  ✅ 매우 편함   │  ⚠️ SQL 작성 필요  │    │
│  │  프로덕션 안정성│  ❌ 위험        │  ✅ 안전           │    │
│  └─────────────────┴─────────────────┴─────────────────────┘    │
│                                                                   │
│  결론:                                                            │
│  ├── 개발: ddl-auto: update (편리함)                             │
│  └── 프로덕션: ddl-auto: none + Flyway (안전함)                  │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

Spring Boot에서 함께 사용

# application.yml (로컬 개발)
spring:
  jpa:
    hibernate:
      ddl-auto: update
  flyway:
    enabled: false   # 로컬에서는 Flyway 비활성화

---
# application-prod.yml (프로덕션)
spring:
  jpa:
    hibernate:
      ddl-auto: none    # 자동 변경 금지
  flyway:
    enabled: true       # Flyway로 마이그레이션
    locations: classpath:db/migration

7. 실제 설정 예시

개발 환경

# application-local.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb_dev
    username: root
    password: password

  jpa:
    hibernate:
      ddl-auto: update        # 자동 스키마 업데이트
    show-sql: true            # SQL 로그 출력
    properties:
      hibernate:
        format_sql: true      # SQL 보기 좋게

테스트 환경

# application-test.yml
spring:
  datasource:
    url: jdbc:h2:mem:testdb   # 인메모리 DB
    driver-class-name: org.h2.Driver

  jpa:
    hibernate:
      ddl-auto: create-drop   # 매번 새로 생성, 종료 시 삭제

프로덕션 환경

# application-prod.yml
spring:
  datasource:
    url: jdbc:mysql://${DB_HOST}:3306/${DB_NAME}
    username: ${DB_USER}
    password: ${DB_PASS}

  jpa:
    hibernate:
      ddl-auto: none          # 절대 자동 변경 금지!

  flyway:
    enabled: true
    baseline-on-migrate: true

8. validate vs update 근본적 분석

핵심 철학부터 다르다

┌─────────────────────────────────────────────────────────────────┐
│              validate와 update의 근본적 철학 차이                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  validate의 철학:                                                 │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  "DB가 진실의 원천(Source of Truth)이다"                     │ │
│  │  "코드는 DB에 맞춰야 한다"                                  │ │
│  │  "불일치가 있으면 앱을 아예 시작하지 마라"                   │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
│  update의 철학:                                                   │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  "코드가 진실의 원천(Source of Truth)이다"                   │ │
│  │  "DB가 코드에 맞춰야 한다"                                  │ │
│  │  "불일치가 있으면 DB를 고쳐라"                               │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
│  이 철학적 차이가 모든 동작 차이를 만든다.                        │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

validate 내부 동작 메커니즘

┌─────────────────────────────────────────────────────────────────┐
│              validate: 앱 시작 시 Hibernate가 하는 일              │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  1. @Entity 어노테이션이 붙은 모든 클래스 스캔                    │
│  2. 각 엔티티의 필드 → 기대하는 컬럼 목록 생성                    │
│  3. JDBC로 DB 메타데이터 조회 (INFORMATION_SCHEMA)                │
│  4. 비교:                                                         │
│                                                                   │
│     엔티티 필드        DB 컬럼                                    │
│     ─────────       ─────────                                    │
│     id              id          ✅ 일치                           │
│     name            name        ✅ 일치                           │
│     email           email       ✅ 일치                           │
│     phone           ???         ❌ 없음 → SchemaManagementException│
│                                                                   │
│  5. 하나라도 불일치 → 즉시 앱 종료 (시작 자체가 안 됨)            │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

validate가 검증하는 것 vs 검증하지 않는 것

┌─────────────────────────────────────────────────────────────────┐
│              validate의 검증 범위 (의외로 중요!)                   │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ✅ 검증하는 것:                                                  │
│  ├── 엔티티 필드에 해당하는 컬럼이 DB에 존재하는가?               │
│  └── 컬럼의 타입이 대략적으로 호환되는가?                         │
│                                                                   │
│  ❌ 검증하지 않는 것:                                              │
│  ├── DB에 엔티티에 없는 "여분의 컬럼"이 있는지 → 무시             │
│  ├── 컬럼 길이 (VARCHAR(100) vs VARCHAR(255)) → DB마다 다름       │
│  └── 인덱스, 제약조건의 정확한 일치 → 검증 안 함                  │
│                                                                   │
│  핵심: validate는 "코드 → DB" 방향만 검증한다                     │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  엔티티에 있는 필드가 DB에 없으면? → 에러!  (코드 → DB)    │ │
│  │  DB에 있는 컬럼이 엔티티에 없으면? → OK!    (DB → 코드)    │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

update 내부 동작 메커니즘

┌─────────────────────────────────────────────────────────────────┐
│              update: 앱 시작 시 Hibernate가 하는 일                │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  1. validate와 동일하게 엔티티 스캔 + DB 메타데이터 조회          │
│  2. 차이점 발견 시 → DDL 자동 생성 및 실행                        │
│                                                                   │
│     엔티티에 phone 필드가 있는데 DB에 없으면:                     │
│     → ALTER TABLE users ADD COLUMN phone VARCHAR(20);             │
│                                                                   │
│  3. "추가"만 한다. 절대 삭제하지 않는다.                          │
│                                                                   │
│     엔티티에서 phone 필드를 제거해도:                              │
│     → DB의 phone 컬럼은 그대로 남아있음                           │
│     → Hibernate는 모른 척 함 (데이터 손실 방지)                   │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

update의 Additive-Only 정책

┌─────────────────────────────────────────────────────────────────┐
│              update는 오직 "더하기"만 한다                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ┌──────────────────────┬──────────────────────────────────┐    │
│  │  할 수 있는 것       │  할 수 없는 것                    │    │
│  ├──────────────────────┼──────────────────────────────────┤    │
│  │  컬럼 추가 ✅        │  컬럼 삭제 ❌                     │    │
│  │  테이블 생성 ✅      │  테이블 삭제 ❌                   │    │
│  │  인덱스 추가 ✅      │  컬럼 타입 변경 ❌               │    │
│  │                      │  컬럼 이름 변경 ❌               │    │
│  │                      │  제약조건 변경 ❌                │    │
│  └──────────────────────┴──────────────────────────────────┘    │
│                                                                   │
│  이것이 "additive-only" 정책이다.                                 │
│  안전을 위해 파괴적 변경을 원천 차단한다.                         │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

언제 validate가 필요한가?

1. Flyway/Liquibase와 함께 쓸 때 (가장 핵심적 용도)

┌─────────────────────────────────────────────────────────────────┐
│              validate + Flyway = 최강 조합                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  Flyway가 스키마를 관리하는 환경에서:                             │
│                                                                   │
│  Flyway    → DB 스키마 변경 담당                                  │
│               (V1__create.sql, V2__alter.sql...)                   │
│  Hibernate → ORM 매핑만 담당                                      │
│                                                                   │
│  문제 상황:                                                       │
│  Flyway가 컬럼을 추가/삭제했는데                                  │
│  개발자가 엔티티를 업데이트 안 했으면?                             │
│                                                                   │
│  → validate가 앱 시작 시 잡아준다!                                │
│  → "야, 코드랑 DB 안 맞아" 라고 즉시 알려줌                      │
│                                                                   │
│  이것이 validate의 존재 이유:                                      │
│  마이그레이션 도구를 쓸 때 "코드와 DB의 동기화 검증"              │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

2. 가상 시나리오: 마이그레이션 후 코드 정리 누락

┌─────────────────────────────────────────────────────────────────┐
│              validate가 잡아주는 전형적인 사고 시나리오             │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  상황: 주문 시스템 리팩토링                                       │
│                                                                   │
│  1. 기존 Order 엔티티에 레거시 FK 컬럼들이 있었음                 │
│     (legacy_payment_id, old_shipping_id, temp_coupon_id)          │
│                                                                   │
│  2. Flyway V3 마이그레이션으로 새 테이블 구조 도입                │
│     V4 마이그레이션으로 레거시 FK 컬럼 3개를 DROP                 │
│                                                                   │
│  3. 그런데 Order.java 엔티티에는                                  │
│     @Deprecated 필드들이 그대로 남아있었음                         │
│     (코드 정리를 깜빡함)                                          │
│                                                                   │
│  4. ddl-auto: validate 설정                                       │
│                                                                   │
│  결과:                                                            │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  앱 시작                                                    │ │
│  │  → Hibernate가 엔티티와 DB 비교                             │ │
│  │  → "Schema-validation: missing column                       │ │
│  │     [legacy_payment_id]"                                     │ │
│  │  → 앱 즉시 종료!                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
│  근본 원인:                                                       │
│  DB 마이그레이션(컬럼 삭제)은 완료했지만                          │
│  코드 정리(엔티티 필드 삭제)를 안 했음                             │
│                                                                   │
│  교훈:                                                            │
│  validate가 없었다면? (none 모드였다면?)                           │
│  → 앱은 시작됐겠지만                                              │
│  → 존재하지 않는 컬럼을 쿼리하는 런타임 에러 발생                 │
│  → 사용자가 주문할 때 500 에러 → 더 큰 장애                      │
│  → validate가 "빠르게 실패(fail-fast)"하게 해준 것                │
│                                                                   │
│  해결:                                                            │
│  Order.java에서 @Deprecated 필드 3개 제거 → 재배포 → 정상 시작   │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

3. CI/CD 배포 파이프라인에서 “안전망”

┌─────────────────────────────────────────────────────────────────┐
│              CI/CD에서 validate의 역할                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  배포 흐름:                                                       │
│                                                                   │
│  개발자 코드 push                                                 │
│       │                                                           │
│       ▼                                                           │
│  CI 빌드                                                          │
│       │                                                           │
│       ▼                                                           │
│  스테이징 배포 ← validate가 여기서 불일치 잡아냄                  │
│       │           프로덕션 가기 전에 발견!                         │
│       ▼                                                           │
│  프로덕션 배포 (안전하게 도달)                                    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

언제 update가 필요한가?

1. 로컬 개발에서 빠른 반복 (가장 핵심적 용도)

┌─────────────────────────────────────────────────────────────────┐
│              update가 빛나는 순간: 로컬 개발                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  update가 있을 때 (개발자의 일상):                                │
│  1. User 엔티티에 phone 필드 추가                                 │
│  2. 앱 재시작                                                     │
│  3. 끝! DB에 phone 컬럼 자동 생성됨                               │
│                                                                   │
│  update가 없다면:                                                 │
│  1. User 엔티티에 phone 필드 추가                                 │
│  2. SQL 작성: ALTER TABLE users ADD COLUMN phone VARCHAR(20);     │
│  3. SQL 실행                                                      │
│  4. 앱 재시작                                                     │
│  → 개발 속도가 2배 느려짐                                         │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

2. 프로토타이핑 / PoC (개념 증명)

┌─────────────────────────────────────────────────────────────────┐
│              아이디어 검증 단계에서의 update                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  프로토타이핑:                                                    │
│  → 스키마가 계속 바뀜                                             │
│  → 매번 SQL 쓰는 건 시간 낭비                                     │
│  → update로 엔티티만 수정하면서 빠르게 진행                       │
│                                                                   │
│  학습/교육 환경:                                                  │
│  → JPA/Hibernate 배우는 중                                        │
│  → SQL보다 Java 코드에 집중하고 싶음                               │
│  → update가 DB 걱정을 없애줌                                      │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

효과적인 때 vs 위험한 때

┌─────────────────────────────────────────────────────────────────┐
│              validate와 update의 효과/위험 비교                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ┌────────────────┬──────────────────┬──────────────────────┐   │
│  │                │  validate        │  update              │   │
│  ├────────────────┼──────────────────┼──────────────────────┤   │
│  │  효과적        │  프로덕션 배포   │  로컬 개발           │   │
│  │                │  Flyway와 조합   │  프로토타이핑        │   │
│  │                │  CI/CD 안전망    │  빠른 반복           │   │
│  │                │  스테이징 검증   │  개인 프로젝트       │   │
│  ├────────────────┼──────────────────┼──────────────────────┤   │
│  │  위험한 때     │  마이그레이션    │  프로덕션 배포       │   │
│  │                │  없이 단독 사용  │  다중 서버 환경      │   │
│  │                │  → 코드 수정만   │  대용량 테이블       │   │
│  │                │  으로 영원히     │  팀 협업 환경        │   │
│  │                │  시작 불가       │                      │   │
│  └────────────────┴──────────────────┴──────────────────────┘   │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

validate의 위험한 순간

┌─────────────────────────────────────────────────────────────────┐
│              validate 단독 사용의 함정                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  validate만 쓰고 Flyway도 안 쓰면?                                │
│                                                                   │
│  엔티티 수정                                                      │
│     │                                                             │
│     ▼                                                             │
│  앱 시작 불가 (validate 에러)                                     │
│     │                                                             │
│     ▼                                                             │
│  DB 직접 수정해야 함 (수동 ALTER TABLE)                           │
│     │                                                             │
│     ▼                                                             │
│  실수 가능 → 악순환                                               │
│                                                                   │
│  결론:                                                            │
│  validate는 반드시 마이그레이션 도구와 세트로 써야 의미가 있다.   │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

update의 위험한 순간

┌─────────────────────────────────────────────────────────────────┐
│              프로덕션에서 update 사용 시 발생하는 문제              │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  1. 예기치 못한 테이블 락                                         │
│     개발자가 실수로 String bigField 추가                          │
│     → ALTER TABLE (수천만 행) → 테이블 락 → 서비스 장애           │
│                                                                   │
│  2. 스키마 오염 (쓰레기 컬럼 누적)                                │
│     필드 이름 변경 (name → fullName)                              │
│     → name 컬럼 남아있음 + fullName 컬럼 추가됨                   │
│     → 안 쓰는 컬럼이 계속 쌓임                                    │
│                                                                   │
│  3. 동시 배포 충돌                                                │
│     서버 A: ALTER TABLE 실행 중                                   │
│     서버 B: 같은 ALTER TABLE 시도                                 │
│     → 충돌/에러 발생                                              │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

근본적 비유

┌─────────────────────────────────────────────────────────────────┐
│              validate vs update 한 줄 요약                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  validate = "감시자(Watchdog)"                                    │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  → 스스로 아무것도 안 함                                    │ │
│  │  → 문제가 있으면 즉시 알려줌 (fail-fast)                    │ │
│  │  → "예방"의 도구                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
│  update = "자동 수리공(Auto-fixer)"                               │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  → 문제를 발견하면 스스로 고침                              │ │
│  │  → 편리하지만 통제가 안 됨                                  │ │
│  │  → "편의"의 도구                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

프로덕션 베스트 프랙티스

# 프로덕션 환경의 정석
spring:
  jpa:
    hibernate:
      ddl-auto: validate    # ← 코드↔DB 일치 검증
  flyway:
    enabled: true            # ← 스키마 변경은 Flyway가 담당
┌─────────────────────────────────────────────────────────────────┐
│              역할 분담                                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  Flyway    → "스키마를 어떻게 변경할지" 결정                      │
│               (명시적, 버전 관리됨, 코드 리뷰 가능)               │
│                                                                   │
│  validate  → "변경이 제대로 됐는지" 검증                          │
│               (자동, 앱 시작 시, fail-fast)                        │
│                                                                   │
│  이 조합이 프로덕션 스키마 관리의 정석이다.                       │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

9. 정리

┌─────────────────────────────────────────────────────────────────┐
│                         요약                                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ddl-auto란?                                                      │
│  └── Hibernate가 Java 엔티티 기준으로 DB 스키마 자동 관리         │
│                                                                   │
│  등장 배경:                                                        │
│  ├── 테이블/클래스 중복 정의 문제 해결                            │
│  ├── SQL 몰라도 개발 가능                                         │
│  └── 빠른 프로토타이핑                                            │
│                                                                   │
│  옵션별 동작:                                                      │
│  ├── none      : 아무것도 안 함 (프로덕션 권장)                   │
│  ├── validate  : 검증만, 수정 안 함                               │
│  ├── update    : 추가만 가능, 삭제/변경 불가 (개발 편리)          │
│  ├── create    : 시작 시 DROP + CREATE (데이터 삭제!)             │
│  └── create-drop: create + 종료 시 DROP                          │
│                                                                   │
│  환경별 권장:                                                      │
│  ├── 로컬 개발  : update (편리함)                                 │
│  ├── 테스트     : create-drop (깨끗한 상태)                       │
│  ├── 스테이징   : validate (검증)                                 │
│  └── 프로덕션   : none + Flyway/Liquibase (안전)                  │
│                                                                   │
│  핵심 원칙:                                                        │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  "개발할 땐 update로 편하게,                                 │ │
│  │   프로덕션은 none + 마이그레이션 도구로 안전하게"            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

관련 키워드

Hibernate, JPA, ddl-auto, update, validate, create, create-drop, Flyway, Liquibase, 스키마 마이그레이션, Spring Boot, ORM, fail-fast, Source of Truth, additive-only, SchemaManagementException, INFORMATION_SCHEMA, CI/CD 안전망, 스키마 검증, 감시자, Watchdog