application.yml
빠른 답
- 운영 환경에서는 보통 none 또는 validate만 두고 스키마 변경은 Flyway나 Liquibase로 관리합니다.
- update는 개발 속도를 높이지만 운영 DB에는 예측하기 어려운 변경을 만들 수 있어 신중해야 합니다.
- create와 create-drop은 기존 데이터를 지우므로 로컬 실험이나 테스트 환경 전용으로 보는 편이 안전합니다.
- 프로필별 설정을 분리하지 않으면 개발용 ddl-auto가 운영까지 들어가는 실수가 생길 수 있습니다.
spring: jpa: open-in-view: false hibernate: ddl-auto: validate
application-local.yml
spring: jpa: hibernate: ddl-auto: update
application-test.yml
spring: jpa: hibernate: ddl-auto: create-drop
application-prod.yml
spring: jpa: hibernate: ddl-auto: validate
이렇게 두면 프로필이 잘못 붙더라도 기본값이 최소한 안전한 쪽에 가깝습니다. 팀에 따라 운영은 `none`, 스테이징은 `validate`로 더 분리하기도 합니다. 중요한 것은 “명시적으로 적는다”는 점입니다. 기본값 추정에 기대면 임베디드 DB, Flyway 감지 여부, 테스트 환경 구성에 따라 생각과 다른 동작이 나올 수 있습니다.
엔티티 변경이 실제로 어떻게 보이는지는 짧은 예제가 가장 빠릅니다.
java @Entity @Table(name = "member") public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
private String nickname;
}
이 상태에서 DB에 `nickname` 컬럼이 없다면 각 옵션의 반응은 분명합니다.
- `validate`: 시작 중 오류를 내고 애플리케이션이 올라오지 않습니다.
- `update`: 컬럼 추가를 시도할 수 있습니다.
- `create`: `member` 테이블을 다시 만들며 기존 데이터가 사라집니다.
- `create-drop`: 시작 시 만들고 종료 시 다시 지웁니다.
- `none`: 아무 일도 하지 않으므로 런타임 쿼리에서 다른 형태의 오류가 뒤늦게 터질 수 있습니다.
## 로그와 오류는 이렇게 읽는다
운영과 배포에서는 설정값보다 로그가 더 솔직합니다. 아래는 `prod` 프로필에서 `validate`가 실패하는 전형적인 예시입니다.
text 2026-04-05 10:12:40.981 INFO 18231 --- [main] c.example.ApiApplication : The following 1 profile is active: "prod" 2026-04-05 10:12:41.287 INFO 18231 --- [main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.6.44.Final 2026-04-05 10:12:42.014 INFO 18231 --- [main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 2026-04-05 10:12:42.551 ERROR 18231 --- [main] j.LocalContainerEntityManagerFactoryBean : Failed to initialize JPA EntityManagerFactory org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing column [nickname] in table [member]
2026-04-05 10:15:08.401 DEBUG 18402 --- [main] org.hibernate.SQL : drop table if exists member cascade 2026-04-05 10:15:08.579 DEBUG 18402 --- [main] org.hibernate.SQL : create table member (id bigint generated by default as identity, email varchar(255) not null, nickname varchar(255), primary key (id))
첫 번째 오류는 “코드는 새 컬럼을 기대하지만 DB는 아직 구버전”이라는 뜻입니다. 이때 운영에서 할 일은 `ddl-auto=update`로 바꾸는 것이 아니라, 마이그레이션을 먼저 반영하는 것입니다. 두 번째 로그처럼 `drop table`이 보이면 더 심각합니다. 운영에서 이런 로그가 나왔다면 잘못된 프로필, 잘못 주입된 환경 변수, 혹은 `create` 계열 설정 유입을 먼저 의심해야 합니다.
배포 중 로그를 볼 때는 세 가지만 먼저 확인하면 됩니다.
- 현재 활성 프로필이 무엇인지
- 최종적으로 적용된 `ddl-auto` 값이 무엇인지
- `drop table`, `create table`, `Schema-validation`, `import.sql` 관련 로그가 있는지
## Spring Boot 3, 4와 Hibernate 6 기준으로 다시 볼 점
2026년 4월 5일 기준으로 이 주제를 볼 때 참고할 기준점은 [Spring Boot Database Initialization](https://docs.spring.io/spring-boot/4.1/how-to/data-initialization.html), [Spring Boot 3.0 Migration Guide](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide), [Hibernate ORM 6.6 User Guide](https://docs.hibernate.org/orm/6.6/userguide/html_single/)입니다. 현재 안정 릴리스는 Spring Boot 4.0.x 라인이고, 현업에서는 여전히 3.x 라인도 많이 운영합니다. 중요한 것은 버전 숫자보다 “Boot 3 이상은 Jakarta Persistence와 Hibernate 6 계열을 전제로 읽어야 한다”는 점입니다.
오래된 글을 읽을 때 헷갈리는 지점은 두 가지입니다.
- 옵션 이름 자체는 예전과 지금이 거의 같습니다.
- 하지만 Boot 3 이상에서는 `javax.persistence`가 아니라 `jakarta.persistence`이고, Hibernate 6으로 오면서 DDL 생성 세부 동작, 타입 매핑 감각, 식별자 전략 주변 경험치가 예전 Hibernate 5 시절과 완전히 같지 않습니다.
그래서 “예전에도 `update` 잘 썼는데요?”라는 감각만 믿고 가면 위험합니다. 옵션 목록은 그대로여도, 실제 생성 SQL과 매핑 해석은 버전 영향을 받습니다. 또 하나 자주 놓치는 점은 기본값입니다. 현재 공식 문서 기준으로도 임베디드 DB이고 Flyway나 Liquibase 같은 스키마 관리자가 감지되지 않으면 기본값은 `create-drop`, 그 밖의 경우는 `none`입니다. 이 규칙은 기억보다 명시가 더 안전하다는 뜻이기도 합니다.
추가로 기억할 만한 현재 기준 포인트도 있습니다.
- `spring.jpa.hibernate.ddl-auto`는 Hibernate의 `hibernate.hbm2ddl.auto`에 대한 Spring Boot 단축 키입니다.
- `create`와 `create-drop`일 때만 클래스패스 루트의 `import.sql`이 실행될 수 있습니다.
- 공식 문서도 스키마 생성 방식은 하나로 통일하라고 권장합니다. `ddl-auto`, `schema.sql`, Flyway를 한꺼번에 섞으면 원인 추적이 급격히 어려워집니다.
## 실무에서는 왜 Flyway나 Liquibase로 마무리해야 하나
운영에서 중요한 것은 “바뀌었다”가 아니라 “무엇이, 언제, 어떤 순서로, 누가 리뷰한 상태로 바뀌었는가”입니다. `update`는 이력을 남기지 않습니다. 반면 Flyway나 Liquibase는 변경 파일 자체가 기록이고, 리뷰 대상이고, 배포 순서의 기준점입니다.
예를 들어 `nickname` 컬럼 추가는 운영에서 보통 이런 식으로 가져갑니다.
sql -- V3_addmember_nickname.sql alter table member add column nickname varchar(255);
-- 배포 후 확인 select version, description, success from flywayschemahistory order by installed_rank desc; ```
이 방식의 장점은 명확합니다.
- 변경 이력이 파일로 남습니다.
- 코드 리뷰와 배포 승인 흐름에 넣기 쉽습니다.
- 어떤 스크립트가 언제 적용됐는지 추적할 수 있습니다.
- 장애 시 원인 범위를 코드와 스키마로 나눠서 볼 수 있습니다.
여기서 validate의 역할도 분명해집니다. validate는 마이그레이션을 대체하지 않습니다. 마이그레이션이 제대로 반영됐는지 확인하는 마지막 안전장치에 가깝습니다. 특히 무중단 배포가 필요한 서비스라면 컬럼 추가, 데이터 백필, 코드 전환, 구컬럼 제거를 한 번에 처리하지 않고 expand-then-contract 방식으로 나누는 편이 훨씬 안전합니다.
- 1차 배포: nullable 컬럼 추가
- 2차 배포: 애플리케이션이 새 컬럼도 함께 쓰기 시작
- 데이터 백필 수행
- 3차 배포:
not null제약, 인덱스 보강, 구컬럼 제거
흔한 실수
- 기본값을 믿고
ddl-auto를 명시하지 않는 것 - 로컬의
update설정이 환경 변수로 운영까지 흘러가는 것 schema.sql,data.sql,ddl-auto, Flyway를 동시에 켜는 것create나create-drop를 테스트 편의 때문에 공용 환경에 남겨두는 것import.sql파일을 데모용으로 넣어두고 운영 클래스패스에서 제거하지 않는 것
한 문장으로 기준을 잡으면 이렇습니다. 개발 편의가 목적이면 update, 시작 검증이 목적이면 validate, 운영 통제가 목적이면 none 또는 validate와 마이그레이션 도구 조합입니다. 팀 개발과 운영 배포가 시작되는 순간부터는 ddl-auto를 주인공이 아니라 보조 수단으로 보는 편이 맞습니다.
원문 참고
https://www.maeil-mail.kr/question/28
댓글
댓글 쓰기