1. FlywayDB

애플리케이션의 변경에 따라 DB 스키마의 변경이력을 관리하기 위한 목적으로 사용한다.

2. SpringBoot에서 제공하는 FlywayDB 관련설정

# FLYWAY (FlywayProperties)
flyway.baseline-description= #
flyway.baseline-version=1 # version to start migration
flyway.baseline-on-migrate= #
flyway.check-location=false # Check that migration scripts location exists.
flyway.clean-on-validation-error= #
flyway.enabled=true # Enable flyway.
flyway.encoding= #
flyway.ignore-failed-future-migration= #
flyway.init-sqls= # SQL statements to execute to initialize a connection immediately after obtaining it.
flyway.locations=classpath:db/migration # locations of migrations scripts
flyway.out-of-order= #
flyway.password= # JDBC password if you want Flyway to create its own DataSource
flyway.placeholder-prefix= #
flyway.placeholder-replacement= #
flyway.placeholder-suffix= #
flyway.placeholders.*= #
flyway.schemas= # schemas to update
flyway.sql-migration-prefix=V #
flyway.sql-migration-separator= #
flyway.sql-migration-suffix=.sql #
flyway.table= #
flyway.url= # JDBC url of the database to migrate. If not set, the primary configured data source is used.
flyway.user= # Login user of the database to migrate.
flyway.validate-on-migrate= #

baseline-version은 별도로 설정하지 않아도 된다.

버전을 별도로 지정하지 않는다면 설정항목을 변경하지 않아도 된다.

3. SpringBootApplication 설정

3.1. build.gradle: flywayDB 의존성 추가

/**
 * http://flywaydb.org/
 * FlywayDB: DB Schema version management tool
 */
compile "org.flywaydb:flyway-core"

3.2. application.yml: flywayDB properties 설정

spring:
  profiles: production
  profiles.include: logging-info, logging-daily
  datasource:
    initialize: false
    sql-script-encoding: UTF-8
    driver-class-name: org.h2.Driver
    url: jdbc:h2:file:~/.h2database/test-db;CACHE_SIZE=10240;DB_CLOSE_DELAY=-1;AUTO_SERVER=TRUE;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=15000;MVCC=true;
    username: tester //sa 계정을 사용하지 않으려고...
    password: tester
  jpa:
    hibernate:
      ddl-auto: validate
logging:
  file: logs/tester.log
flyway:
  enabled: true
  encoding: UTF-8
  user: sa
  password:

3.3. V1__xxxx.sql 추가

CREATE SCHEMA IF NOT EXISTS "PUBLIC";
CREATE USER IF NOT EXISTS tester PASSWORD 'tester';
ALTER USER tester ADMIN TRUE;
GRANT ALL TO tester;
 
-- DDL 스크립트
....

파일명을 기준으로 하여 버전관리

version comment 사이는 __ 2개의 언더바로 구분지어야 한다.


Evolve your Database Schema easily and reliably across all your instances

데이터베이스 스키마의 변화를 손쉽게 관리

하는 기능을 가진 기술



1. FlywayDB 소개

1.1. 왜 DB Migration 을 사용하는가?

각기 다른 데이터베이스에서 동일한 DB스키마를 유지할 수 있는 기능이 필요하다. Shiny DB migration

  • 코드쪽에서는 꽤 괜찮은 해결책들이 있다.

    • 버전관리 도구는 보편적으로 사용되고 있다
    • 재현가능한 빌드와 지속적인 통합을 실시
    • 괜찮게 정의된 릴리스 및 배포 프로세스를 가지고 있다.
  • 그런데 데이터베이스는??

    • 많은 프로젝트가 수동으로 SQL을 실행하는 방식 수행
    • 이 과정에서 많은 문제가 발생함
      • 각 데이터베이스마다 DB 스키마의 구조가 달라질 수 있는 문제가 발생
    • 관련된 많은 질문들
      • 이 시스템의 데이터베이스는 어떤 상태입니까?
      • 이 스크립트는 이미 적용 했습니까?
      • 제품에 적용한 긴급수정사항은 테스트 데이터베이스에 적용했습니까?
      • 어떻게 새로운 데이터베이스 인스턴스를 설정합니까?

        보다 많은 질문들에 대해서는: 모르겠다!!

  • 데이터베이스 마이그레이션으로 이 무제들을 해결할 수 있다!

    • 처음부터 데이터베이스를 재작성하고
    • 데이터베이스가 어떤 상태인지 확인가능하고
    • 새로운 데이터베이스에 현재 적용되어있는 방식으로 마이그레이션 가능

2. FlywayDB 동작방식 설명

Empty Database

마이그레이션 버전 1, 버전 2가 있는 상황에서 flyway는 SCHEMA_VERSION 라는 이름의 비어있는 단독 테이블을 생성한다.

Emtpy schema version table

테이블이 생성되면 flyway는 즉시 파일시스템 혹은 애플리케이션의 마이그레이션을 위해 지정된 클래스패스에서 SQL 혹은 JAVA 파일을 탐색한다. 탐색된 파일들은 버전에 따라서 정렬하여 실행한다. execute migration

마이그레이션 버전1, 버전2가 각각 실행되면 SCHEMA_VERSION 테이블에는 다음과 같이 마이그레이션 이력이 저장된다.

Table schema_version

애플리케이션이 실행될때마다 flyway 설정에 따라서 파일과 SCHEMA_VERSION의 변동사항을 확인하나. 새로운 버전의 파일이 추가되면 실행하고 그 변경이력을SCHEMA_VERSION에 추가한다.

Pending Migration

마이그레이션 버전 2.1 후 변동사항

SCHEMA_VERSION의 변동사항

참 쉽죠??


3. 프로젝트 적용

3.1. SpringBoot 설정

스프링부트에서는 데이터베이스 마이그레이션을 지원한다. 그 중에서 Flyway를 채택했다. 이전 프로젝트에서는 Carbon5(c5-db-migration) 라고 하는 마이그레이션 도구를 사용했는데...

  • Maven만 지원했음
  • 지원이 사라짐...

그러다가 발견한 것이 Flyway. 손쉽게 사용가능함.

3.1.1. 의존성 추가

Maven

  • Flyway: maven
    <project ...>
    ...
    <build>
      <plugins>
        <plugin>
          <groupId>org.flywaydb</groupId>
          <artifactId>flyway-maven-plugin</artifactId>
          <version>3.2.1</version>
          <configuration>
            <url>jdbc:h2:file:target/foobar</url>
            <user>sa</user>
          </configuration>
          <dependencies>
            <dependency>
              <groupId>com.h2database</groupId>
              <artifactId>h2</artifactId>
              <version>1.3.170</version>
            </dependency>
          </dependencies>
        </plugin>
      </plugins>
    </build>
    </project>

Gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.h2database:h2:1.3.170'
        classpath 'org.flywaydb:flyway-gradle-plugin:3.2.1'
    }
}
 
apply plugin: 'org.flywaydb.flyway'
apply plugin: 'java'
 
flyway {
    url = 'jdbc:h2:file:target/foobar'
    user = 'sa'
}

SpringBoot

dependencies {
  /**
   * http://flywaydb.org/
   * Database Migration tool
   */
  compile "org.flywaydb:flyway-core"
}

3.1.2. application.yml(or properties) 설정

DB를 초기화하는 다양한 방법들이 있다.

  • JPA 에 의한 초기화
  • Hibernate 에 의한 초기화
  • JDBC에 의한 초기화
  • Batch를 이용한 초기화

그보다 높은 차원에서 손쉽게할 수 있는 것들이 있는데, 그중 하나가 바로 Flyway

flyway:
  enabled: true
  check-location: true # 마이그레이션 스크립트 파일이 존재하는지 여부를 확인
  locations: classpath:db/migration # 마이그레이션 스크립트 위치
  baseline-version: LATEST # 마이그레이션을 시작할 번호
  sql-migration-prefix: V
  sql-migration-suffix: .sql
  url: jdbc:h2:file:~/.database/flywaydb;DB_CLOSE_DELAY=-1;AUTO_SERVER=TRUE;DB_CLOSE_ON_EXIT=FALSE;  # Flyway 소유의 데이터소스를 생성하려고 하는 경우 사용
  user: sa # Flyway 소유의 데이터소스를 생성하려고 하는 경우 사용
  password: # Flyway 소유의 데이터소스를 생성하려고 하는 경우 사용

Flyway에 지정할 수 있는 속성은 다음과 같음

# FLYWAY (FlywayProperties)
flyway.*= # Any public property available on the auto-configured `Flyway` object
flyway.check-location=false # check that migration scripts location exists
flyway.locations=classpath:db/migration # locations of migrations scripts
flyway.schemas= # schemas to update
flyway.init-version= 1 # version to start migration
flyway.init-sqls= # SQL statements to execute to initialize a connection immediately after obtaining it
flyway.sql-migration-prefix=V
flyway.sql-migration-suffix=.sql
flyway.enabled=true
flyway.url= # JDBC url if you want Flyway to create its own DataSource
flyway.user= # JDBC username if you want Flyway to create its own DataSource
flyway.password= # JDBC password if you want Flyway to create its own DataSource

3.1.2. 디렉토리 설명

3.2. 스키마 관리방법 설명

  • 프로젝트의 소스코드와 함께 관리한다.
  • 로컬, 테스트와 출시 버전을 분리한다.
    • 로컬, 개발은 update로도 충분...
    • 테스트, 운영 등 서버는 validate 모드로 관리
  • 프로젝트의 버전관리시스템에 기능을 활용하여 버전관리가 가능

3.2.1. DB 스키마 추출방법

어디까지나 수동.... 처음에는 수동처리가 필요함...

  1. 로컬 테스트에서 Hibernate에서 DDL 쿼리를 생성하도록 정의 생성하도록 설정

    jpa:
    hibernate:
     ddl-auto: create-drop
  2. 테스트를 실행

    사용할려는 DB에 따라 생성하는 SQL 스크립트가 달라질 수 있는데, 이는H2Database 에서 제공하는 기능을 활용하여 MySQL, Oracle 등으로 변경하면 하이버네이트의 방언Dialect 에 의해 적절한 쿼리가 생성됨

  3. 쿼리를 복사하여 손질


  4. 처음 쿼리는 V1__init_db_schema.sql 로 생성

3.2.2. 변경이력 관리

프로젝트 내에 포함되어 있으니 모든 것을 형상관리시스템에 맡긴다.

3.3. 활용방안

  • 제품 출시 후 DB 스키마를 초기화하는 것이 아니라, 출시 이후 변경된 사항들에 대한 형상을 관리할 수 있다.
  • 위에서 설명한 동작방식을 상기하기 바란다.


○ SpringBoot 레퍼런스 가이드 설정부분

# FLYWAY (FlywayProperties)
flyway.check-location=false # check that migration scripts location exists
flyway.locations=classpath:db/migration # locations of migrations scripts
flyway.schemas= # schemas to update
flyway.init-version= 1 # version to start migration
flyway.init-sqls= # SQL statements to execute to initialize a connection immediately after obtaining it
flyway.sql-migration-prefix=V
flyway.sql-migration-suffix=.sql
flyway.enabled=true
flyway.url= # JDBC url if you want Flyway to create its own DataSource
flyway.user= # JDBC username if you want Flyway to create its own DataSource
flyway.password= # JDBC password if you want Flyway to create its own DataSource

예제에 보면 1 이 등록된 것을 볼 수 있다. 하지만!!

Caused by: org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'flyway' on field 'initVersion': rejected value [1]; codes [typeMismatch.flyway.initVersion,typeMismatch.initVersion,typeMismatch.org.flywaydb.core.api.MigrationVersion,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [flyway.initVersion,initVersion]; arguments []; default message [initVersion]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'org.flywaydb.core.api.MigrationVersion' for property 'initVersion'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [org.flywaydb.core.api.MigrationVersion] for property 'initVersion': no matching editors or conversion strategy found]
    at org.springframework.boot.bind.PropertiesConfigurationFactory.validate(PropertiesConfigurationFactory.java:296)
    at org.springframework.boot.bind.PropertiesConfigurationFactory.doBindPropertiesToTarget(PropertiesConfigurationFactory.java:255)
    at org.springframework.boot.bind.PropertiesConfigurationFactory.bindPropertiesToTarget(PropertiesConfigurationFactory.java:227)
    at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:296)
    ... 26 more

ㅡ_-);; 그대로 따라해보면 저렇게 오류를 뱉는다.

스프링부트 1.2.3 에서 Flyway와 관련된 ... 부분에서 initVersion 항목이 Integer 가 아니라 MigrationVersion으로 변경이 되었다. 스프링부트 1.2.3 에서 제공하는 flywayDB는 3.1 버전으로, Flyway.class의 코드를 살펴보면

/**
 * Sets the version to tag an existing schema with when executing baseline.
 *
 * @param initVersion The version to tag an existing schema with when executing baseline. (default: 1)
 * @deprecated Use setBaselineVersion() instead. Will be removed in Flyway 4.0.
 */
@Deprecated
public void setInitVersion(MigrationVersion initVersion) {
    LOG.warn("Flyway.setInitVersion() is deprecated. Use setBaselineVersion() instead. Will be removed in Flyway 4.0.");
    this.baselineVersion = initVersion;
}
 
/**
 * Sets the version to tag an existing schema with when executing baseline.
 *
 * @param initVersion The version to tag an existing schema with when executing baseline. (default: 1)
 * @deprecated Use setBaselineVersion() instead. Will be removed in Flyway 4.0.
 */
@Deprecated
public void setInitVersion(String initVersion) {
    LOG.warn("Flyway.setInitVersion() is deprecated. Use setBaselineVersion() instead. Will be removed in Flyway 4.0.");
    this.baselineVersion = MigrationVersion.fromVersion(initVersion);
}

에서 보는 것처럼, 1, 2, 3으로 정의하던 initVersion 관련 항목이 @Depercated 처리되었다.

initVersion 으로 설정하는 부분

String initVersionProp = properties.getProperty("flyway.initVersion");
if (initVersionProp != null) {
    LOG.warn("flyway.initVersion is deprecated and will be removed in Flyway 4.0. Use flyway.baselineVersion instead.");
    setBaselineVersion(MigrationVersion.fromVersion(initVersionProp));
}

baselineVersion

String baselineVersionProp = properties.getProperty("flyway.baselineVersion");
if (baselineVersionProp != null) {
    setBaselineVersion(MigrationVersion.fromVersion(baselineVersionProp));
}

flyway의 릴리즈 노트를 보면 init 관련항목들이 baseline으로 변경된 것을 확인할 수 있었다. 두둥.

Issue 860 Deprecated init(), use baseline() instead.
Issue 860 Deprecated initVersion, use baselineVersion instead.
Issue 860 Deprecated initDescription, use baselineDescription instead.
Issue 860 Deprecated initOnMigrate, use baselineOnMigrate instead.
Issue 860 Deprecated FlywayCallback.beforeInit(), use FlywayCallback.beforeBaseline() instead.
Issue 860 Deprecated FlywayCallback.afterInit(), use FlywayCallback.afterBaseline() instead.
Issue 860 Deprecated MigrationState.PREINIT, use MigrationState.BELOW_BASELINE instead.
Issue 860 Deprecated MigrationType.INIT, use MigrationType.BASELINE instead.

◎ 정리

  • initVersion 보다는 baselineVersion으로 옮겨가자.
  • MigrationVersion 은 LATEST와 EMPTY 로 변환가능...
  • 스프링부트에서 사용한 외부 라이브러리에서 오류가 발생한다면, 외부라이브러리와 관련된 가이드를 살펴보자.
    • 스프링부트 레퍼런스 가이드에 반영이 안되어 있을수도 있다.


+ Recent posts