Hibernate ddl-auto
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