🔍 Flyway란? DB 마이그레이션의 필요성코드는?하지만 DB는?DB 마이그레이션은 이러한 혼란을 제어할 수 있는 좋은 방법입니다.⚙️ Flyway 동작 원리🔫 Let’s Migration🐶 프로젝트 적용 방법📌 개발 환경⚙️ 설정1. 라이브러리 추가2. application.yml 설정 추가3. 폴더 만들기🧩 파일명 관리🏃 가즈아!🍒 Seed 데이터🕹 Flyway - Command 다루기🪬 Command 종류🐶 Command 사용 2가지 방법😓 Maven PlugIn 사용🦊 Command Line Interface🐹 CLI 장점📃 Case By CaseCase.1 - 마이그레이션 오류 상황일 때!!! (ex. 파일 분실)Case.2 - R 파일(Repeatable) - Duplicate-key 오류🔖 참고 사이트Q&A
🔍 Flyway란?
DB 마이그레이션의 필요성
코드는?
- 버전 관리는 GIT, SVN 등의 버전 관리 툴을 통해서 관리하고 있습니다.
- 그리고 재현 가능한 빌드와 지속적인 통합도 있습니다.
- 잘 정의된 릴리즈 및 배포 프로세스도 가지고 있습니다.
하지만 DB는?
많은 프로젝트가 여전히 수동적으로 SQL 스크립트에 의존하게 됩니다. 그리고 여러가지 의문들이 생깁니다.
- 현 프로젝트의 DB는 어떤 상태인가?
- 이 스크립트가 잘 적용 되었는가? 적용되지 않았는가?
- 제품의 수정사항이 테스트에 적용 되었는가?
- 새 DB 인스턴스는 어떻게 설정 하는가?
DB 마이그레이션은 이러한 혼란을 제어할 수 있는 좋은 방법입니다.
- DB를 처음부터 다시 생성
- DB가 어떤 상태인지 명확하게 확인 가능
- 현재 버전의 DB에서 최신 버전으로 결정적인 마이그레이션
⚙️ Flyway 동작 원리


비어있는 DB인 경우에는 Migration 파일과 함께 정보를 생성을 하게 되는데,
이 때 flway_schema_history(변경 이력 관리 테이블)도 함께 생성 됩니다.
flway_schema_history가 바로 DB의 상태를 알 수있게 해주는 테이블 입니다.
이 테이블을 통해서 Flyway는 파일 혹은 클래스 경로를 스캔해서 마이그레이션을 시작합니다.

다음과 같은 순서로 진행이 된다면 이력 관리 테이블에서도 순서대로 정보가 업데이트 됩니다.

(이미지 더블 클릭 하시면 확대 됩니다.)
🔫 Let’s Migration
- Flyway는 마이그레이션을 위해 파일 또는 클래스 경로를 다시 스캔
- 스키마 기록 테이블에 확인

위에서 Pending은 보류 중인 마이그레이션입니다.
(사용 가능하지만 적용되지 않습니다.)
⁉️ 버전 정보가 현재 표시된 버전 중 하나보다 낮거나 같으면 무시됩니다.
- 다음으로 버전 별로 정렬되고 순서대로 실행됩니다.

- 그리고 해당 이력이 flway_schema_history에 업데이트 됩니다.
이렇게 버전을 높이면서 새 마이그레이션을 생성하면 됩니다.
그럼 Flyway가 시작되면 이를 찾아 DB에 반영을 하게 될것입니다.
🐶 프로젝트 적용 방법
📌 개발 환경
- Java 17
- Spring Boot 2.7.0
- Flyway 8.5.12
- MySQL 8.0
⚙️ 설정
1. 라이브러리 추가
DB - MySQL 사용하는 경우에는 flyway-mysql을 추가해야 합니다.
<maven>
<dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> <version>${flyway.version}</version> </dependency> <!-- Mysql 사용 하는 경우, 추가--> <dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-mysql</artifactId> <version>${flyway.version}</version> </dependency>
<gradle>
plugins{ id "org.flywaydb.flyway" version "6.2.4" } dependencies{ compileOnly "org.flywaydb:flyway-mysql" implementation 'org.flywaydb:flyway-mysql:8.5.13' testImplementation 'org.flywaydb.flyway-test-extensions:flyway-spring-test:7.0.0' }
2. application.yml 설정 추가
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: DB_URL username: DB_ID password: DB_PASSWORD jpa: generate-ddl: false # ✨ ddl 기능 false 처리 hibernate: ddl-auto: validate # ✨ validate 사용하기 flyway: enabled: true baseline-on-migrate: true # Spring boot 2 이상인 경우 자동으로 생성되지 않는 경우가 있었다...? 🤔 locations: classpath:db/migration, classpath:db/seed # Flyway 파일 경로
- JPA 설정
- generate-ddl
- hibernate.ddl-auto
- validate : 변경된 스키마가 있다면 변경 사항을 출력하고 App 종료???
🤔 DB에는 적용되어 있고, Entity에는 적용 안되있어도 그냥 실행 되던디…?
- Flyway 설정
- enabled : Flyway 사용여부!
- baseline-on-migrate : flway_schema_history 자동 생성 옵션
- true : flway_schema_history 테이블이 없는 경우 생성
- false : 기존에 히스토리 테이블이 있는 경우에 사용
- locations : 마이그레이션 파일 경로
3. 폴더 만들기
resrouces ⌙ db ⌙ migration # 테이블 변경 사항 적용을 위한 마이그레이션 파일 보관용 ⌙ V1__Init.sql ⌙ V2__Alter_member_add_phone_number.sql ⌙ ... ⌙ seed # 데이터 등록을 위해 사용되는 시드 파일 보관용 ⌙ R__001_Seed_member.sql ⌙ ... # Flway 명령어 실행을 위해 로컬 설정 파일 resrouces-local ⌙ flyway_main.conf # 마이그레이션용 설정 파일 ⌙ flyway_seed.conf # 시드용 설정 파일
실제로 만들면, 아래와 같은 모양새가 됩니다.

But! 일단은 resources-local 폴더의 configuration 파일은 지금 당장 사용하지는 않습니다.
(문제가 발생 했을 때 사용하게 됩니다.)
본격적으로 가기 전에 파일명 정리 먼저 보고 가시죠!
🧩 파일명 관리


- Prefix : 항상 대문자로 해야 합니다!!
- V : Versioned
- R : Repeatable
- U : Undo
- Version : X.X로 이루어진 버전
📌 Repeatable 파일에는 버전 명시하면 안됩니다!
- Separator : __(언더바 2개)
- Description : 파일 설명
- Suffix : 확장자 (.sql)
⁉️ Prefix는 항상 대문자!!!! & __(언더바 2개) 잊지 맙시다!
🏃 가즈아!
테스트를 위한 Member, Post 엔티티 생성
@Entity public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String email; private String password; // ... } @Entity public class Post { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String content; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") private Member member; // ... }
- V1__Initial_Setup.sql 생성

DROP TABLE IF EXISTS post; DROP TABLE IF EXISTS member; CREATE TABLE member ( id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, email VARCHAR(255), password VARCHAR(255) ); CREATE TABLE post ( id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, content VARCHAR(255), member_id BIGINT NOT NULL, FOREIGN KEY (member_id) REFERENCES member(id) );
- 실행을 해보면?

로그를 보시면 마이그레이션이 성공적으로 적용됬다고 나오게 됩니다.

변경 이력 테이블과 함께 테이블들이 생성된 것을 확인할 수 있습니다.

(이미지 더블 클릭 하시면 확대 됩니다.)
그리고 변경 이력 테이블에서 해당 이력도 확인 가능 합니다.
- 다음은…?
변경 사항이 있을 때마다 V2__Alter_member_add_name.sql 과 같이 버전을 높인 마이그레이션 파일을 생성 후에 반영을 하면서 진행을 하면 됩니다.
🍒 Seed 데이터
- 한 번만 실행되는 대신 CheckSum이 변경 될 때마다 (재)적용 됩니다.
(파일이 수정 된다면, 실행되게 됩니다.)
- 대게 다음과 같은 상황에서 사용 됩니다.
- 뷰 (재)생성 / 프로시저 / 함수…
- 벌크 Insert 데이터
- 한번 해보겠습니다. → 현 상태 : V1 - 테이블이 생성된 상태

INSERT INTO member(email, password) VALUES ('test00', '1234');
다음과 같이 R__001_Seed_member.sql 파일을 생성 후 마이그레이션 실행하게 되면…?

데이터가 들어간 것을 확인 할 수 있구요.

이력 정보도 함께 확인 가능 합니다.
🕹 Flyway - Command 다루기
Flyway에서 제공하는 Command 종류부터 보고 가실까요?
🪬 Command 종류
- Migrate : 스키마를 최신 버전으로 마이그레이션
- Flyway 이력 테이블이 없는 경우 자동 생성
- Flyway 워크 플로우의 핵심
- 사용 가능한 마이그레이션에 대해 파일 시스템 또는 클래스 경로 스캔 → 히스토리 테이블 확인 후 업데이트
- DB와 코드의 비호환성을 피하기 위해 마이그레이션은 응용 프로그램 시작 시 실행하는 것을 추천

- Undo : 가장 최근에 적용된 버전이 지정된 마이그레이션을 실행 취소 합니다.
- 유료
- 지정한 타겟 보다 낮은 마이그레이션에 도달할 때까지 적용된 순서대로 버저닝 지정된 마이그레이션 실행 취소
- 실행 취소할 버전이 지정된 마이그레이션이 없는 경우 요청해도 효과 X
- R (반복 가능한 마이그레이션)은 실행 취소 기능 없음


- Info : 모든 마이그레이션 상세 정보를 출력한다.

- Validate : DB 적용된 마이그레이션 정보 유효성 검증
- DB 적용된 마이그레이션과 로컬에서 사용 가능한 마이그레이션이 일치하는지 확인
- Validate 마이그레이션 실행 될 때, 체크섬을 저장하여 작동
- 로컬 마이그레이션이 DB에서 이미 실행된 마이그레이션과 여전히 동일 한 체크섬을 가지고 있는지 확인

- Baseline : Flyway 관리 이전에 DB 존재할 경우, 해당 DB를 Flyway baseline으로 설정
- 이렇게 하면 Migrate가 기준 버전까지 모든 마이그레이션을 무시

- Repair : 메타 데이터 테이블 문제를 해결하기 위해 사용
- 실패한 마이그레이션 항목 제거 (DDL 트랜잭션을 지원하지 않는 DB만 해당)
- 적용된 마이그레이션의 체크섬을 사용 가능한 마이그레이션의 체크섬으로 재정렬
- 누락된 모든 마이그레이션을 삭제된 것으로 표시

- Clean : 구성된 스키마의 모든 객체를 삭제
- 초기화 할 수 있어, 개발 및 테스트에 큰 도움이 됩니다.
- 🚫 운영중인 본 DB에는 사용하지 말라고 경고하고 있음!!

(테이블 정보까지 삭제되기 때문에, 사용은 지양할것!!!!!!)
- Check : 현재 베타 버전
- 마이그레이션에 대한 신뢰도를 높이기 위해 보고서를 생성
- 임시 DB에 대해 마이그레이션 후 보고서를 생성하기 위해 대상 DB와 비교
- 임시 DB를 사용하기 전에 정리되므로, 중요한 내용이 포함되어 있지 않은지 확인 필요!
🐶 Command 사용 2가지 방법
여러가지 방법이 있지만 2가지만 일단 소개 드리겠습니다.
😓 Maven PlugIn 사용
- pom.xml에 다음과 같이 flyway-maven-plugin을 추가합니다.
<plugin> <groupId>org.flywaydb</groupId> <artifactId>flyway-maven-plugin</artifactId> <version>${flyway.version}</version> <configuration> <url>jdbc:mysql://localhost:3306</url> <schemas>flyway</schemas> <user>flyway</user> <password>password</password> <locations>filesystem:src/main/resources/db/migration</locations> </configuration> </plugin>

이제 Maven Plugins 에서 플라이웨이 명령어를 사용할 수 있습니다!
👾 단점 (추가 사항) feat. 혜빈님 Feedback
🦊 Command Line Interface
여기서 위에서 나왔었던, configuration 파일을 사용하게 됩니다.
# Flway 명령어 실행을 위해 로컬 설정 파일 resources-local ⌙ flyway_main.conf # 마이그레이션용 설정 파일 ⌙ flyway_seed.conf # 시드용 설정 파일
- resources-local 폴더를 만들고
- flyway_main.conf 파일을 생성합니다.
flyway.url=jdbc:mysql://localhost:3306/ # 접속 주소 flyway.schemas=flyway # DB 명 flyway.user=flyway # 회원ID flyway.password=password # 비밀번호 flyway.locations=filesystem:src/main/resources/db/migration # migration 폴더명
- 이제 다음과 같이 명령어를 사용할 수 있습니다. 해당 Configuration 파일을 이용해서 migrate info validate baseline repair clean 을 사용 가능!
# migration flyway -configFiles=./src/main/resources-local/flyway_main.conf migrate # repeatable flyway -configFiles=./src/main/resources-local/flyway_seed.conf migrate

(이미지 더블 클릭 하시면 확대 됩니다.)
🐹 CLI 장점
- .conf 파일만 준비해서 명령어만 실행하면 간단하게 사용할 수 있습니다. (Maven, Gradle의 경우에는 의존성 추가를 해줘야 되는 등 불필요한 설정을 해줘야 합니다.)
📃 Case By Case
Case.1 - 마이그레이션 오류 상황일 때!!! (ex. 파일 분실)
- 현 상황 (2개의 버전 적용 상태)


- V3 파일 생성 → V2 파일 삭제

이 상태에서 migrate를 하면 어떻게 될까요?

다음 과 같이 validate 실패 오류가 발생하게 됩니다.
그리고 info 명령어를 실행하게 되면…?

V2 파일이 Missing 되었다는 상태를 확인할 수 있구요, 그래서 V3가 대기 중입니다.
- 해결 방법은 REPAIR → REPAIR 명령어를 실행해주세요.

실행하게 되면…? → Info로 확인해보기


info 명령어를 통해서 V2가 삭제되고 이제 마이그레이션을 할 수 있게 됩니다!

Case.2 - R 파일(Repeatable) - Duplicate-key 오류
Repeatable 파일은 checksum이 변경 되었을 때 재실행 된다고 합니다.
(일단 해당 파일에 변경사항이 있을 때 checksum이 수정되고 이를 Flyway가 재실행하는 것으로 추정…)
하지만 저희 프로젝트에서는 member table에서는 usename, email이
UNIQUE KEY
이기 때문에,
기존에 데이터가 있는데 재실행이 되면 오류가 발생되게 됩니다.
이를 해결하기 위해서 Mysql의
INSERT IGNORE
를 적용하였습니다.INSERT 문장의 IGNORE 옵션은 저장하는 레코드의 프라이머리 키나 유니크 인덱스 컬럼 값이 이미 테이블에 존재하는 레코드와 중복되는 경우, 그리고 저장하는 레코드의 컬럼이 테이블의 컬럼과 호환되지 않는 경우 모두 무시 하고 다음 레코드를 처리할 수 있게 해준다.
<Real MySQL 8.0 2권>
중복 되는 레코드가 있다면 오류를 발생시키지만 MySQL Server는
IGNORE
속성이 있는 경우,
에러를 경고 수준의 메시지로 바꾸고 다음 쿼리가 계속 실행되게 됩니다.⛔️ 이야기만 들었을 때는 굉장히 편리해 보이는 옵션이지만 주의할 점이 있습니다.
INSERT IGNORE
옵션은 중복되는 키 뿐만 아니라 데이터 타입이 일치하지 않아서 INSERT를 할 수 없는 경우에도, 컬럼의 기본값으로 INSERT를 하도록 만듭니다.INSERT IGNORE INTO member (username, name, password, phone_number, email, introduction, created_at, created_by, updated_at, updated_by) VALUES (null, null, null, null, null, null, null, null, null, null)
위의 구문을 실행하면 어떻게 될까요? 🤔

(이미지 더블 클릭 하시면 확대 됩니다.)
실행 로그는 아래와 같습니다.
testdb> INSERT IGNORE INTO member (username, name, password, phone_number, email, introduction, created_at, created_by, updated_at, updated_by) VALUES (null, null, null, null, null, null, null, null, null, null) [2022-06-25 20:54:26] [23000][1048] Column 'name' cannot be null [2022-06-25 20:54:26] [23000][1048] Column 'password' cannot be null [2022-06-25 20:54:26] [23000][1048] Column 'phone_number' cannot be null [2022-06-25 20:54:26] [23000][1048] Column 'created_at' cannot be null [2022-06-25 20:54:26] [23000][1048] Column 'updated_at' cannot be null
위의 결과를 보면 null 허용인 컬럼에는 null을 그 외의 컬럼은 기본값을 등록하는 것을 볼 수 있습니다.
✨ 이런 상황을 피하기 위하기 위해서는
INSERT IGNORE
사용할 때 중복 이외에 에러가 발생할 여지가 없는지 면밀히 확인 후에 적용하는 것이 좋습니다.🔖 참고 사이트
Q&A
진형 Q :
인텔리제이에서 사용할 때에는 프로젝트 실행 시 자동으로 마이그레이션 해주는 것을 볼 수 있었습니다.

하지만 리눅스에 JAR로 배포해 실행 시켜보니 마이그레이션이 안되는 것을 볼 수 있는데요 어떻게 해결하는지 궁금합니다…

자체 해결 A :
flyway-mysql이 complieOnly로 되어있어서 build시 jar에 의존성이 추가되지 않았던 문제였습니다.
compileOnly를 implementation으로 변경해주니 마이그레이션이 잘 되는것을 볼 수 있습니다.
- 변경 전

- 변경 후

