TL;DR

  • Hibernate ddl-auto의 핵심 개념과 사용 범위를 한눈에 정리
  • 등장 배경과 필요한 이유를 짚고 실무 적용 포인트를 연결
  • 주요 특징과 체크리스트를 빠르게 확인

1. 개념

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

2. 배경

Hibernate ddl-auto이(가) 등장한 배경과 기존 한계를 정리한다.

3. 이유

이 주제를 이해하고 적용해야 하는 이유를 정리한다.

4. 특징

  • 약자/이름
  • 전통적인 개발 방식의 문제
  • Hibernate의 해결책
  • 설정 방법
  • 옵션별 동작

5. 상세 내용

작성일: 2026-01-30 카테고리: Backend / Java / JPA / Hibernate 포함 내용: ddl-auto, 스키마 자동 생성, update, validate, create, Flyway, Liquibase, 마이그레이션


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. 정리

┌─────────────────────────────────────────────────────────────────┐
│                         요약                                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  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