Hibernate ddl-auto
TL;DR
- ddl-auto는 Hibernate가 엔티티를 기준으로 스키마를 자동 생성/검증하는 기능이다.
- update는 개발 편의성이 높지만 변경/삭제는 못 하고 프로덕션에는 위험하다.
- 프로덕션은 none/validate + Flyway 같은 마이그레이션 도구 조합이 안전하다.
1. 개념
Hibernate ddl-auto는 엔티티 정의를 기준으로 DB 스키마를 자동 생성/검증/수정하는 설정으로, 개발 환경에서 스키마 동기화 작업을 줄여준다.
2. 배경
테이블과 엔티티를 중복 정의하던 전통적 방식은 불일치와 작업 중복을 낳았고, Hibernate가 이를 코드 기반 스키마 자동화로 해결하려 했다.
3. 이유
엔티티만 수정하면 스키마를 자동으로 맞춰 개발 속도를 높이고, 스키마 불일치를 줄이는 것이 목적이다.
4. 특징
- none/validate/update/create/create-drop 등 다양한 전략 제공
- update는 additive-only로 변경/삭제는 차단해 안전장치를 둔다
- 프로덕션에서는 validate 또는 none과 마이그레이션 도구 병행이 권장된다
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