Flyway란?
Flyway는 데이터베이스 스키마 버전 관리 도구입니다. 애플리케이션 코드와 마찬가지로 DB 스키마 변경 이력(마이그레이션)을 버전으로 관리할 수 있게 해줍니다.
Spring Boot + JPA 환경에서 Flyway를 사용하면 다음과 같은 이점을 얻을 수 있습니다.
- 팀 협업 시 DB 스키마 변경 이력을 코드로 공유
- 배포 환경(dev/staging/prod)마다 스키마 일관성 보장
- 롤백 전략 수립 가능
spring.jpa.hibernate.ddl-auto=create 대신 안전한 마이그레이션 적용
의존성 추가
Gradle
dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-mysql'
runtimeOnly 'com.mysql:mysql-connector-j' }
|
Maven
<dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> </dependency> <dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-mysql</artifactId> </dependency>
|
application.yml 설정
spring: datasource: url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC username: root password: password driver-class-name: com.mysql.cj.jdbc.Driver
jpa: hibernate: ddl-auto: validate show-sql: true properties: hibernate: format_sql: true
flyway: enabled: true baseline-on-migrate: true locations: classpath:db/migration
|
ddl-auto: validate 를 사용하면 JPA 엔티티와 실제 DB 스키마가 일치하는지 검증만 하고, 스키마 변경은 Flyway에게 위임합니다.
마이그레이션 파일 네이밍 규칙
Flyway는 resources/db/migration 디렉토리에서 마이그레이션 파일을 자동으로 탐색합니다.
src/ └── main/ └── resources/ └── db/ └── migration/ ├── V1__init_schema.sql ├── V2__add_member_table.sql ├── V3__add_column_email.sql └── R__insert_default_data.sql
|
파일명 형식
| 접두사 |
설명 |
예시 |
V |
버전 마이그레이션 (한 번만 실행) |
V1__create_table.sql |
U |
실행 취소 마이그레이션 (Pro 기능) |
U1__undo_create_table.sql |
R |
반복 마이그레이션 (변경 시마다 재실행) |
R__insert_seed_data.sql |
버전 형식: V{버전}__{설명}.sql
- 버전 구분자:
. 또는 _ 사용 가능 (예: V1.1__, V1_1__)
- 설명 구분자:
__ (언더스코어 2개)
- 설명 내 공백은
_ 로 표현
마이그레이션 파일 예제
V1__init_schema.sql - 초기 스키마 생성
CREATE TABLE member ( id BIGINT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_member_email (email) );
CREATE TABLE orders ( id BIGINT NOT NULL AUTO_INCREMENT, member_id BIGINT NOT NULL, total_price INT NOT NULL DEFAULT 0, status VARCHAR(20) NOT NULL DEFAULT 'PENDING', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), CONSTRAINT fk_orders_member FOREIGN KEY (member_id) REFERENCES member (id) );
|
V2__add_phone_column.sql - 컬럼 추가
ALTER TABLE member ADD COLUMN phone VARCHAR(20) NULL AFTER email;
|
V3__create_product_table.sql - 테이블 추가
CREATE TABLE product ( id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price INT NOT NULL, stock INT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) );
|
R__insert_default_roles.sql - 반복 마이그레이션 (기초 데이터)
INSERT IGNORE INTO role (name) VALUES ('ROLE_USER'), ('ROLE_ADMIN');
|
JPA 엔티티 예제
마이그레이션 파일의 스키마와 일치하는 JPA 엔티티를 작성합니다.
@Entity @Table(name = "member") @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
@Column(nullable = false, length = 50) private String username;
@Column(nullable = false, length = 100, unique = true) private String email;
@Column(length = 20) private String phone;
@Column(nullable = false, updatable = false) private LocalDateTime createdAt;
@Column(nullable = false) private LocalDateTime updatedAt;
@Builder public Member(String username, String email, String phone) { this.username = username; this.email = email; this.phone = phone; this.createdAt = LocalDateTime.now(); this.updatedAt = LocalDateTime.now(); } }
|
flyway_schema_history 테이블
Flyway는 마이그레이션 이력을 flyway_schema_history 테이블에 저장합니다. 애플리케이션 시작 시 이 테이블을 조회하여 적용되지 않은 마이그레이션만 순서대로 실행합니다.
+-----------+-------+---------------------------+---------+------------------+----------+---------+ | installed | version | description | type | checksum | success | +-----------+-------+---------------------------+---------+------------------+----------+---------+ | 2026-04-26| 1 | init schema | SQL | 1234567890 | true | | 2026-04-26| 2 | add phone column | SQL | 9876543210 | true | | 2026-04-26| 3 | create product table | SQL | 1122334455 | true | +-----------+-------+---------------------------+---------+------------------+----------+---------+
|
테스트 환경 설정
테스트 환경에서는 H2 인메모리 DB와 함께 Flyway를 사용할 수 있습니다.
build.gradle
testImplementation 'com.h2database:h2'
|
src/test/resources/application.yml
spring: datasource: url: jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1 driver-class-name: org.h2.Driver username: sa password:
jpa: hibernate: ddl-auto: validate
flyway: enabled: true locations: classpath:db/migration
|
MODE=MySQL 옵션으로 H2가 MySQL 문법을 해석할 수 있도록 합니다.
주요 설정 옵션 정리
| 설정 |
설명 |
기본값 |
flyway.enabled |
Flyway 활성화 여부 |
true |
flyway.locations |
마이그레이션 파일 경로 |
classpath:db/migration |
flyway.baseline-on-migrate |
기존 DB에 baseline 자동 설정 |
false |
flyway.baseline-version |
baseline 버전 번호 |
1 |
flyway.validate-on-migrate |
적용 전 체크섬 검증 |
true |
flyway.clean-on-validation-error |
검증 실패 시 DB 초기화 (운영 사용 금지) |
false |
flyway.out-of-order |
순서가 맞지 않는 마이그레이션 허용 |
false |
flyway.repair |
실패한 마이그레이션 기록 삭제 후 재실행 가능 상태로 복구 |
- |
주의사항
- 이미 적용된 마이그레이션 파일은 수정 금지: 체크섬이 변경되면
FlywayException이 발생합니다.
- 운영 환경에서
clean 명령어 사용 금지: flyway.clean-disabled=true 설정을 권장합니다.
ddl-auto: create 또는 update와 혼용 금지: Flyway와 Hibernate DDL이 충돌합니다. validate 또는 none만 사용하세요.
- 마이그레이션 파일은 반드시 버전 순서대로 작성:
out-of-order: false(기본값)이면 낮은 버전이 뒤늦게 추가될 경우 오류가 발생합니다.