- Java 11
- SpringBoot 2.7.7
- Gradle 7.6
- H2 database (2.1.212)
- IntelliJ IDEA
💯 Flyway를 활용하여 DB 스키마를 형상관리하고, 마이그레이션을 자동화하는 방법에 익숙해질 수 있습니다.
☑️ DB 스키마 변경을 수작업으로 하고 계시는 분
☑️ 개발 환경이 달라질 때마다 반복되는 마이그레이션 작업에 지치신 분
☑️ 반복 작업을 싫어하고 자동화를 좋아하시는 분
-
Flyway Library를 Application에 연동하고, Flyway Gradle Plugin 의존성 추가하기
-
Migration File을 작성하여, DB 스키마를 변경해보기
-
기존 데이터가 존재하는 상황에서 Baseline을 설정해보기
-
Community Version(무료 버전)에서 Rollback 해보기
-
로컬환경에서만 Seed Data가 삽입되도록 Repeatable Migration 실행해보기
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.7'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id "org.flywaydb.flyway" version "9.8.1" // flyway gradle plugin 의존성 추가
}
group = 'bong'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
/* flyway gradle plugin에서 사용하는 설정값 블록 */
flyway {
url = 'jdbc:h2:~/flyway'
user = 'sa'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.flywaydb:flyway-core' // Application 에 flyway library 의존성 추가
}
tasks.named('test') {
useJUnitPlatform()
}
spring:
h2:
console:
enabled: true // h2 웹 콘솔 사용 설정
datasource:
url: jdbc:h2:~/flyway
username: sa
password:
driver-class-name: org.h2.Driver
flyway:
enabled: true # 기본값 = true 이지만, flyway를 사용하고 있음을 명시해줍니다.
-
Migration File이 위치하는 기본 경로에 맞게, 폴더를 생성합니다.
src/main/resources/db/migration
-
Naming Convention을 준수하여 Migration File을 작성합니다.
V1__create_member_table.sql
DROP TABLE IF EXISTS member; CREATE TABLE member ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name VARCHAR(100) NOT NULL )
-
application을 구동시킵니다.
콘솔 로그를 보시면, 아래 사진과 같이, 정상적으로 Migration 작업이 수행된 것을 확인하실 수 있습니다.
- h2 웹 콘솔을 이용하여, db를 직접 조회해봅니다.
http://localhost:8080/h2-console/
애플리케이션이 실행 중인 상태에서, 웹 브라우저에서 위의 주소로 접근하면, h2 웹 콘솔을 사용할 수 있습니다.
JDBC URL 란에 jdbc:h2:~/flyway 경로를 입력하고, 연결 버튼을 눌러, DB에 접근하여 member 테이블이 생긴 것을 확인해봅니다.
지금까지 우리는 애플리케이션에 Flyway Library 의존성을 추가하여,
애플리케이션 부트스트랩 단계에서 Migration 작업을 수행시켜봤습니다.
이 방식은 배포환경에서 유용하게 사용할 수 있습니다.
하지만, Application을 재시작 시키지 않고 Migration 작업만 해야하는 상황도 존재합니다. 이를 위해서 Flyway Gradle Plugin을 이용하여 Migration 작업을 진행해보겠습니다.
-
V2에 해당하는 Migration File을 만듭니다.
V2__insert_into_member.sql
INSERT INTO member(name) VALUES ('최수봉');
💣주의사항
우리는 실습의 편의상, 로컬에 별도의 dbms 설치 없이 h2 database를 Embedded 모드로 사용 중입니다.
h2 DB를 embedded 모드(파일 모드)로 사용하는 경우, 멀티 커넥션이 허용되지않아 db접근에 문제가 발생할 수 있습니다.
그러므로, 이번 실습에서는 실행 중인 어플리케이션을 먼저 종료한 다음, Gradle plugin을 통해 Migration 작업을 하도록 합니다.
-
Gradle plugin으로 Migration을 수행합니다.
아래 사진과 같이, IntelliJ 우측 상단 Gradle 탭-프로젝트명-Tasks-flyway 패키지를 보시면
여러 가지 Flyway 명령어를 GUI 환경에서 사용할 수 있도록 제공하고 있습니다.
여기서 flywayMigrate를 더블클릭하여, 마이그레이션 작업을 실행시킵니다.이는, 프로젝트 루트 경로에서 ./gradlew flywayMigrate 명령어를 수행한 것과 같습니다.
-
기존 데이터가 존재하는 프로젝트라는 시나리오 구성을 위해, flyway_schema_history 테이블을 삭제해줍니다.
-
gradle plugin에서, flywayMigrate 명령어를 실행시킵니다.
Found non-empty schema(s) "PUBLIC" but no schema history table. Use baseline() or set baselineOnMigrate to true to initialize the schema history table.
migration 작업 실행시, 위와 같은 에러가 발생하는 것을 확인할 수 있습니다.
연동된 데이터소스에 기존 데이터는 존재하는데, 이를 관리하는 flyway_schema_history 테이블 자체가 없는 경우에는,
변경이력을 쌓을 시작점인 Baseline을 직접 정의해주어야 합니다. -
flywayBaseline 명령어로, Baseline을 잡아줍니다.
flywayBaseline을 더블클릭하여, Baseline 생성 작업을 실행시킵니다.
프로젝트 루트 경로에서 ./gradlew flywayBaseline 명령어를 수행한 것과 같습니다. -
baseline-on-migrate 설정을 이용해, 애플리케이션 부트스트랩 단계에서 Baseline 만들기
이번에는 Gradle plugin이 아닌, 애플리케이션 구동 시점에서 Baseline을 설정하는 방법을 알아보겠습니다.
application.yml
flyway: baseline-on-migrate: true # Baseline 생성이 필요한 상황에서 migration 작업 실행시, Baseline 생성부터 하겠다는 설정
baseline-on-migrate 설정 값이 true인 상태에서,
애플리케이션을 구동하면, Flyway는 Baseline 생성이 필요한 상황을 스스로 판단하여,
flyway_schema_history 테이블을 생성하고 Baseline을 생성합니다. -
application을 구동시킨 뒤, flyway_schema_history 테이블을 확인합니다.
성공적으로 적용되었다면, 아래 사진과 같이 flyway_schema_history 테이블에 Baseline(기준선)이 잡힌 것을 확인할 수 있습니다.
먼저, member 테이블에 age 컬럼을 추가하는 V3 마이그레이션을 작성합니다.
V3__add_age_to_member.sql
ALTER TABLE member ADD age INT DEFAULT 0;
정상적으로 반영된 것을 확인 할 수 있습니다.
이제 Rollback을 해보겠습니다.
Rollback을 하려면, 먼저 롤백 대상인 V3 마이그레이션을 보면서, 변경된 스키마 내용을 다시 되돌립니다.
아래 쿼리로, member table에 age 컬럼을 삭제합니다.
ALTER TABLE member DROP COLUMN age;
age 컬럼은 삭제되었지만, flyway_schema_history 테이블에는 기록이 남아있는 것을 확인할 수 있습니다.
flyway_schema_history 테이블에서, 버전 3에 해당하는 Row를 지워줍니다.
DELETE FROM "flyway_schema_history" WHERE "version" = 3;
이렇게 스키마 구조도 이전 버전의 스키마 구조로 복구되었고, 변경이력 또한 삭제되었습니다.
이렇게해서 Rollback 작업이 모두 완료되었습니다.
-
application-local.yml 파일을 생성하고, location 설정을 추가합니다.
applicaton-local.yml
spring: flyway: enabled: true locations: classpath:db/migration, classpath:db/seed # 경로가 여러 개일 경우 쉼표(,)로 구분합니다.
-
application.yml 파일에 local profile 사용을 명시하고,
배포환경에서는 flyway가 동작하지 않도록 flyway.enabled 속성을 false로 바꿔줍니다.applicaton.yml
spring: profiles: active: local # local profile 사용하기 위한 설정 flyway: enabled: false # 운영환경에서 flyway가 동작하는 것을 방지하기 위하여 false로 설정
-
resources/db 하위에 Repeatable Migration File만 따로 관리할 seed 폴더를 생성합니다.
-
아래와 같이 Repeatable Migration File을 작성해줍니다.
Repeatable Migration File은 File의 checksum값이 변경될 때마다 반영하는 특성을 가집니다.
매 회 Migration마다 data를 반복적으로 seeding하기 위해서, 주석으로 timestamp를 찍어줍니다.R__insert_seed_data_into_member_.sql
-- ${flyway:timestamp} # repeatable 파일이 매번 반영되도록 주석으로 timestamp를 찍는다. INSERT INTO member(name) VALUES ('토비');
지금까지 다양한 시나리오에서 Flyway를 이용하여 DB 스키마를 코드로 관리하는 Hands-On-Lab Session을 진행했습니다. 이번 실습만 잘 따라오셨다면, 여러분들은 이미 Flyway를 실무에 적용하여 사용하기에 충분한 수준이 된 것입니다.
2022년의 마지막 날, 마지막 발표까지, 긴 시간 Hands-On-Lab Session에 참가해주셔서 정말 감사합니다!
아래 블로그에 Flyway CLI Tool을 설치하고, 간단히 사용하는 방법을 정리해두었으니 참고해주세요. https://devcamus.tistory.com/29