====== 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 를 그대로 복사해서 넣거나 해야한다.