====== Flyway Java Database Migration ======
* http://flywaydb.org/
* [[https://flywaydb.org/documentation/configfiles|Config Files - Config Files - Flyway by Boxfuse • Database Migrations Made Easy.]]
===== Flyway Gradle Plugin =====
* http://flywaydb.org/documentation/gradle/
==== config 파일 이용 ====
* [[https://flywaydb.org/documentation/configfiles|Flyway Config Files]]
* 설정 파일을 만들고, 해당 파일을 다음 처럼 호출
gradle -Dflyway.configFiles=path/to/myAlternativeConfig.conf flywayMigrate
gradle -Dflyway.configFiles=path/to/myAlternativeConfig.conf,other.conf flywayMigrate
# 환경 설정 이용
export FLYWAY_CONFIG_FILES=path/to/myAlternativeConfig.conf,other.conf
* 혹은 ''flyway'' 설정에 설정파일도 지정가능
flyway {
configFiles = ['/path/to/file']
}
// 이 얘기는 공통 설정 프로필은 gradle에 지정하고 프로필별 접속 정보 설정파일을 따로 두고 아래 처럼 할 수도 있다는 얘기
flyway {
driver = 'org.mariadb.jdbc.Driver'
locations = ["filesystem:${file('src/migrations').absolutePath}"]
encoding = 'UTF-8'
// ....
configFiles = ['/path/to/profile.config']
}
// 혹은 -Pprofile=local 형태로 프로필을 지정하고
flyway {
driver = 'org.mariadb.jdbc.Driver'
// ...
configFiles = ["${file('src/migrations/confs/flyway-' + profile + '.conf').absolutePath}"]
}
==== gradle 에 직접 설정 ====
* [[https://github.com/flyway/flyway/blob/master/flyway-gradle-plugin/src/main/java/org/flywaydb/gradle/FlywayExtension.java|FlywayExtension]] 기본 뼈대
// locations 에서 filesystem 사용시 file() 을 사용하지 않으면 상황에 따라 상대경로가 서로 다른 경로로 매핑되므로 주의필요.
flyway {
url = 'jdbc:....'
user = 'dbusername'
password = 'dbpassword'
locations = ["filesystem:${file('src/migration/mydb').absolutePath}"]
schemas = ['dbo']
encoding = 'UTF-8'
outOfOrder = true // 여러브랜치에서 서로 다른 날짜로 만들어도 작동하도록
validateOnMigrate = true
}
==== Task 직접 생성 ====
* [[https://github.com/flyway/flyway/issues/830| Allow gradle plugin to create multiple migrate tasks for different (database) schemas #830]] : 다중 flyway task 만드는 힌트
* Flyway Gradle 3.1 이상 버전에서 작동
* ''apply plugin: 'org.flywaydb.flyway'''을 실행한 상태에서 생성된 ''project.flyway'' extension이 존재할 경우 커스텀 Task가 생성이 올바로 온됐다. 따라서 커스텀 태스크를 사용할 때는 플러그인을 사용하지 말고 커스텀으로만 사용한다.
ext.flyway = null // custom 사용시 flyway extionsion null로 처리.
tasks.create(name: 'myFlywayMigrate', type: org.flywaydb.gradle.task.FlywayMigrateTask) {
extension = new org.flywaydb.gradle.FlywayExtension()
// extension configurations...
}
// FlywayBaselineTask, FlywayCleanTask, FlywayInfoTask, FlywayInitTask(@deprecated)
// FlywayMigrateTask, FlywayRepairTask, FlywayValidateTask
===== callback =====
* [[http://www.baeldung.com/flyway-callbacks|A Guide to Flyway Callbacks | Baeldung]]
* 마이그레이션 하나하나 실행될 때마다 콜백실행하는 듯.
===== SpringBoot 연동 =====
* [[springframework:springboot|SpringBoot]] 와 연동하여 서버가 뜰 때 자동으로 flyway 가 실행되게 할 수 있다.
* [[https://flywaydb.org/documentation/plugins/springboot|Spring Boot - Community Plugins and Integrations: Spring Boot - Flyway]]
* [[https://www.callicoder.com/spring-boot-flyway-database-migration-example/|Spring Boot Database Migrations with Flyway | CalliCoder]]
* [[http://java.ihoney.pe.kr/404|[스프링부트] 1.3.0: FlywayDB 설정 :: 허니몬(Honeymon)의 자바guru]]
* 단, 실 운영에서 마이그레이션이 발생하지 않도록 절대로 주의한다.
* **실 운영은 접속시 아예 DDL 권한이 없어야 한다**
* 의존성만 추가하면 됨.
compile "org.flywaydb:flyway-core:5.0.7"
* ''application.yml'' 설정. [[https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html|Appendix A. Common application properties]] 에서 flyway 부분 참조. Spring Boot 2.0 부터는 ''spring.flyway''
flyway:
enabled: true
encoding: UTF-8
....
* DataSource 관련하여 지정하지 않으면 ''@Primary'' DataSource를 사용한다.
* 기본 마이그레이션 디렉토리는 ''src/main/resources/db/migration'' 인듯?
* flyway 프로필별로 끄기
# SpringBoot 1.x
flyway:
enabled: false
# SpringBoot 2.0
spring:
flyway:
enabled: false
* 가급적 **''spring.flyway.enabled: false''**로 항상 끄자.
===== Version Number =====
* Flyway 버전 번호는 날짜 포맷으로 하는게 좋아보인다.''yyyy.MM.dd.HH.mm.ss''
* 연속된 숫자로 할 경우 둘 이상의 개발자가 동일 숫자를 지정하여 오류가 발생하기 쉽다.
* ''outOfOrder = true'' Migration 시에 숫자 증분을 하지 않아도 되게 한다.
* [[https://plugins.jetbrains.com/plugin/8597-flyway-migration-creation|Flyway Migration Creation]] IntelliJ IDEA 플러그인으로 쉽게 마이그레이션을 생성할 수 있다.
===== SpringBoot & Flyway =====
* [[springframework:springboot|SpringBoot]]의 ''spring.flyway.enabled=true''가 기본값이다. 이 때문에 실수로 ''spring.flyway.enabled'' 값을 지정을 안해도 무조건 작동하게 된다. production DB에서도 작동할 경우 DB 날리는 일이 된다.
* 따라서 SpringBoot application에서는 아예 flyway 에 대한 의존성을 걸지 않는 것을 추천한다.
===== 특정 migration 실패 대응 =====
migration 을 실행했으나, 이미 누군가가 수동으로 마이그레이션을 진행한 상황이라서 migration history 가 실패했거나 존재하지않는 상황일 때 대응하는 방법
==== migration history는 생성됐으나 success = 0 ====
-- 먼저 데이터 확인
select * from flyway_schema_history WHERE version = <실패한MIGRATION버전>;
-- 성공으로 강제 update
update flyway_schema_history SET success = 1 WHERE version = <실패한MIGRATION버전>;
==== migration history 자체가 생성이 안 됨 ====
* migration history 가 생성이 안 됐다면, local DB 등에서 migration 을 전체 실행하고 실패한 version 관련 정보를 강제로 넣어준다.
-- MySQL 기준임.
-- local db 에서
select concat(
'SET @max_rank = (select MAX(installed_rank) from flyway_schema_history);\n',
'INSERT INTO flyway_schema_history ',
'SET',
' installed_rank = @max_rank + 1',
', version = ', fsh.version,
', description = ''', fsh.description, '''',
', type = ''', fsh.type, '''',
', script = ''', fsh.script, '''',
', checksum = ''', fsh.checksum, '''',
', installed_by = CURRENT_USER()',
', installed_on = now()',
', execution_time = ', fsh.execution_time,
', success = ', fsh.success,
';')
from flyway_schema_history fsh where version = <실패한MIGRATION버전>;
-- 실제 flyway를 적용할 DB에서 위 쿼리를 실행해서 나온 SQL 문을 실행해주면 된다.
==== migration history 자체가 생성이 안 됨 - skipExecutingMigrations ====
* [[https://flywaydb.org/documentation/configuration/parameters/skipExecutingMigrations#configuration-file|flyway.skipExecutingMigrations - Skip Executing Migrations - Flyway by Redgate • Database Migrations Made Easy.]]
* [[https://flywaydb.org/blog/skipexecutingmigrations|Skip Executing Migrations Examples - Flyway]]
* 마이그레이션을 누군가 수동으로 진행했을 때 마이그레이션을 실행하지 않고, 빠져있는 migration history 만 채워넣는다.
* 유료버전 기능.
flyway {
skipExecutingMigrations = true
}
* 이 기능을 사용할 수 없으면, local 에서 실행한 history 를 그대로 복사해서 넣거나 해야한다.