-
Notifications
You must be signed in to change notification settings - Fork 0
Flyway
- 오픈소스 Database 마이그레이션 툴
- Local 개발 환경의 DB 변경사항을 다른 배포단계의 DB에 적용 하려면 배포하기 전에 변경사항을 수동으로 처리한 뒤 배포해야한다. 이러한 과정 속에서 변경해야할 사항을 하나라도 빠뜨리게 될 경우 문제를 일으키게 되는데 이러한 수동 작업 + 수동 작업으로 인한 문제점을 해결해줄 수 있는 것이 바로 flyway다.
- DB schema의 변경 이력이 생긴다.
- 변경 후 DB schema 에서 어떤 문제가 발견되었다면, 이전 이력을 통해서 문제를 해결하기 훨씬 쉬워진다.
- DB schema 변경 작업이 더 안전해진다.
- 수동으로 하나씩 DB를 변경하지 않기 때문에 빼먹거나 실수할 여지가 줄어들고, 배포 시에 자동화도 가능해진다.
- 협업 하기에 수월해진다.
- 문제 발생 시 문제를 파악하기 좋고, 실제 환경과 동일한 local 환경을 구축하기 쉬워져서 local 에서 개발하기가 훨씬 수월해진다.
두 개의 마이그레이션을 가지고 있고 DB는 비워져있다고 가정
-
flyway를 통해서 마이그레이션을 할 때, 마이그레이션 대상
Shiny DB에SCHEMA_VERSIONtable이 존재하는지 판단하고 없다면 flyway가 자동으로 신규로 생성한다.- 이용 가능한 마이그레이션을 application classpath에서 스캔한다.
-
SCHEMA_VERSION이 이미 존재해있거나 새로 생성되었다면 flyway는 마이그레이션을 위해 지정된 파일(sql or Java)을 지정된 classpath에서 탐색하여 버전 순서대로 실행한다.-
SCHEMA_VERSION은 마이그레이션 체크섬을 추척하여, 마이그레이션이 성공적으로 완료되었는지도 확인한다. -
SCHEMA_VERSION을 확인하여 마이그레이션 버전이 현재 버전과 같거나 더 낮을 경우, 무시한다. - 버전을 통해서 정렬 되어있고 순차적으로 실행한다.
-
-
마이그레이션이 실행되고 나면
SCHEMA_VERSIONtable에 실행이력을 저장하게 된다.
- flyway [option] 명령어
- ex)
flyway -url="db주소" -user="user" clean
- ex)
-
DB 스키마를 현재 버전으로 마이그레이션한다.
- 이용가능한 마이그레이션을 classpath에서 스캔한 뒤 pending된 마이그레이션들을 적용한다
-
모든 마이그레이션 상세 정보 출력한다.
- DB 스키마의 현재 status 또는 version을 출력한다.
- 어떤 마이그레이션이 적용되었는지, pending된 상태인지도 출력한다.
-
DB에 적용된 마이그레이션 정보의 유효성을 검증한다.
- 현재 DB 스키마를 이용가능한 마이그레이션을 통해 검증한다.
-
flyway로 관리하기 이전에 DB가 존재시 해당 DB를 flyway baseline 으로 설정할 수 있다.
- 기존 존재하는 DB에서 Flyway를 시작하는데 도움을 주는 명령어이다.
- 기존 존재하는 DB의 현재 상태를 기준으로
flyway_schema_history을 table 생성한다.
- 기존 존재하는 DB의 현재 상태를 기준으로
- 기존 존재하는 DB에서 Flyway를 시작하는데 도움을 주는 명령어이다.
- 메타 데이터 테이블 문제를 해결하기 위해 사용하는데 두가지 용법이 존재한다.
-
실패한 마이그레이션 항목 제거( DDL 트랜잭션을 지원하지 않는 DB에만 해당)
-
적용된 마이그레이션의 체크섬을 이용 가능한 마이그레이션의 체크섬으로 재정렬한다.
-
-
DB의
SCHEMA_VERSION테이블 포함한 모든 objects(tables, views, procedures, ...)를 drop시킨다.- 운영 DB에서는 절대 사용하지 말아야한다.
-
SCHEMA_VERSION 파일 이름 컨벤션
-
<Prefix><Version>__<Description>.sql-
<Prefix>– default prefixV(Version) 는 버전 마이그레이션이다.- 만약
V외의 prefix로 변경하고 싶다면 설정파일에서flyway.sqlMigrationPrefix속성을 정의해주면 된다. -
R(Repeatable)은 반복 마이그레이션을 뜻한다. 버전 상관없이 항상 실행되는 스크립트를 의미한다.
- 만약
-
<Version>– 버전 마이그레이션 번호를 뜻하며,_로 주 버전, 부 버전을 나눌 수 있다. 마이그레이션 버전은 항상 1로 시작해야한다. -
<Description>– 해당 마이그레이션 설명을 뜻하며, 버전 마이그레이션 번호 사이에서 반드시__로 분리되어야한다.
-
-
ex)
V1_1_0__my_first_migration.sql-
V: prefix -
1_1_0: version -
__my_first_migration.sql: description
-
-
-
Flyaway가 관리하는
SCHEMA_VERSION역할을 하는 테이블이 된다. -
적용된 스크립트는 절대로 변경하면 안된다.
-
gradle에 flyway 라이브러리 추가 :
implementation "org.flywaydb:flyway-core:7.14.0" -
application 설정 파일에 flyway 설정 추가
application.yml spring: jpa: hibernate: ddl-auto: validate flyway: baseline-on-migrate: true baseline-version: 0 locations: classpath:db/migration/{vendor}
-
ddl-auto: validate: JPA entity 정보를 바탕으로 생성된 스키마가 실제 관계형 DB 스키마와 일치하는지 검증해준다. -
baseline-on-migrate: true:SCHEMA_VERSION이 존재하지 않는 DB 스키마에 대해서 마이그레이션이 실행될 때, baseline을 호출하게 함으로써SCHEMA_VERSION을 생성해준다.- 기존 DB 스키마가 존재하지 않을 경우,
SCHEMA_VERSION이 생성되지 않는다.
- 기존 DB 스키마가 존재하지 않을 경우,
-
baseline-version: 0: 일반적으로 V1__ 형태로 버전 1부터 시작하기 때문에 baseline을 버전 0으로 지정하여 버전 1부터 마이그레이션들을 적용하게 한다.- default = 1
-
locations: classpath:db/migration/{vendor}: db 종류를 기준으로 마이그레이션을 따로 적용한다.- h2를 사용할 경우에는
db/migration/h2, mysql을 사용할 경우에는db/migration/mysql경로를 참조하게 된다. - Spring boot default scan은
classpath:db/migration임으로 경로 변경을 원할시에는spring.flyway.locations: 경로를 해주면 된다.
- h2를 사용할 경우에는
-
Spring boot의 문서를 보면 default로 컨텍스트에 존재하는 @Primary
DataSource를 autowired하기 때문에spring.datasource속성이 설정이 되어있다면 flyway를 위한 속성 설정을 하지 않아도 된다.-
다른 Datasource를 사용하고 싶다면
@FlywayDataSource으로 빈을 생성해주거나 아래와 같이 설정 파일에서 설정을 해주면 된다.application.yml spring: flyway: url: jdbc:h2:mem:kodesalon user: sa password: schemas: kodesalon
-
운영, 개발 환경 등에 다른 마이그레이션을 적용하고 싶을 경우
application-dev.yml spring: flyway: locations: classpath:/db/migration,classpath:/dev/db/migration
- 이와 같이 설정을 하여
devprofile이 활성화될 때는dev/db/migration만 실행하도록 할 수 있다.
- 이와 같이 설정을 하여
-
- Flyway 설정 오버라이딩 적용 우선순위
- System properties
- Environment variables
- Custom config files
- Gradle properties
- Flyway configuration section in
build.gradle <user-home>/flyway.conf- Flyway Gradle plugin defaults
- 설정 방법은 Flyway Documentation 참조
-
-
${project_root}/src/main/resources/db/migration/mysql디렉터리 추가 -
추가한 경로에 빈 스크립트 파일 생성
-
V1__init.sql파일 생성- baseline을 사용할 경우 반드시 빈 script 파일이 필요하다.
-
-
flyway 적용 확인
-
DB에 스키마 변경 이력을 확인할 경우
select * from flyway_schema_history;
- 제대로 적용된 것을 확인할 수 있다.
-
-
새로운 데이터 갱신
- Flyway를 실행하여 생성된 DB 스키마와 JPA entity 정보를 바탕으로 생성된 스키마와 비교
-
일치하지 않을 경우 오류가 발생한다.
-
IntelliJ의
JPA Buddy플러그인을 이용하면 더 쉽게 Flyway를 다룰 수 있다.- flyway 파일 생성 시
V1,V2외V202108300911처럼 타임스탬프 형식으로도 자동으로 생성하도록 할 수 있다.- 타임스탬프 패턴의 이점
- V1 → V2 → V3처럼 연속된 숫자로 할 경우 같은 프로젝트를 진행하는 둘 이상의 개발자가 동일 숫자를 지정할 수도 있다.
-
outOfOrder = trueMigration을 해도 이미 초 단위로 나누어져 있기 때문에 따로 버저닝을 위한 숫자를 추가하지 않아도 된다.
- 타임스탬프 패턴의 이점
- flyway 파일 생성 시
-
- Flyway를 실행하여 생성된 DB 스키마와 JPA entity 정보를 바탕으로 생성된 스키마와 비교
-
기존 테이블에 새로운 컬럼 추가할 경우
-
ex)
memberentity에address칼럼을 추가할 경우@Entity @Where(clause = "deleted = 'false'") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "member", uniqueConstraints = { @UniqueConstraint(name = "member_unique_constraint", columnNames = {"alias"})}) public class Member extends BaseEntity { ..... @Column(name = "address", nullable = false) private String address;
-
추가한
address컬럼에 대한 스크립트 작성하지 않고 실행할 경우 아래와 같은 예외가 발생한다.-
member테이블에서address칼럼을 찾지 못한다.
-
-
mysql 폴더에서 마우스 오른쪽 버튼 클릭하여
JPA Buddy를 사용한 스크립트 생성-
New - Flyway - Diff Versioned Migration: Entity 스키마와 실제 DB를 비교하여 다른 점을 모두 자동으로 찾아주고 SQL문까지 모두 작성해준다.
-
-
버전 마이그레이트 적용할 DB 선택
- Target(URL)에서 어느 DB와 비교할 지 선택한다.
- 현재는 테스트이기 때문에 로컬용 DB(kodesalon-mysql)에 연결했다.
- Target(URL)에서 어느 DB와 비교할 지 선택한다.
-
마이그레이션해야 할 목록 확인
-
member테이블에address칼럼 추가 SQL문이 자동적으로 작성되어 있다.
-
member테이블에address칼럼을nullable하지 않게 수정하는 SQL문이 자동적으로 작성되어 있다.
-
-
목록에서 확인한 SQL문이 작성된 스크립트 파일 생성 및 이름 변경
-
V2__.sql이 생성되었고 목록에서 확인한 SQL문들이 작성되어 있다.
-
V2__.sql→V2_member_add_address.sql로 이름 변경 및 SQL문도 하나로 합쳐주었다.
-
-
실행 및
flyway_schema_history확인- 정상적으로 작동하며 마이그레이션이 성공했다는 것을 확인할 수 있다.
-
-
-
새로운 테이블이 추가될 경우
-
ex)
imageentity를 생성할 경우@Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "image", uniqueConstraints = { @UniqueConstraint(name = "image_unique_constraint", columnNames = {"url"})}) public class Image { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "url", nullable = false) private String url; }
-
추가한
imageentity에 대한 스크립트 작성하지 않고 실행할 경우 아래와 같은 예외가 발생한다.-
image테이블을 찾지 못한다.
-
-
마이그레이션해야 할 목록 확인
-
imagetable 생성 및unique constraintSQL문이 작성되어 있는 것을 확인할 수 있다.- 오른쪽 상단
File name에서 이름을 변경해줄 수 있다.
- 오른쪽 상단
-
-
목록에서 확인한 SQL문이 작성된 스크립트 파일 생성 및 이름 변경
-
V3__.sql이 생성되었고 목록에서 확인한 SQL문들이 작성되어 있다.
-
V3__.sql→V3_image_create.sql로 이름 변경해주었다.
-
-
실행 및
flyway_schema_history확인- 정상적으로 작동하며 마이그레이션이 성공했다는 것을 확인할 수 있다.
-
-
-
flyway 적용 중 BOOLEAN 타입 관련 validation 오류가 발생했다.
[PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: wrong column type encountered in column [deleted] in table [board]; found [bit (Types#BIT)], but expecting [boolean default false (Types#BOOLEAN)
public class Board extends BaseEntity { ... @Column(name = "deleted", nullable = false, columnDefinition = "boolean default false") private boolean deleted;
-
columnDefinition = "boolean default false"로 설정 되어있는데 실제로 동작하는 과정에서는 문제가 없으나,ddl-auto = validate를 함으로써 JPA가 생성한 스키마와 실제 DB 스키마와 차이가 있어 생기는 문제였다.
-
-
org.hibernate.tool.schema.internal.AbstractSchemaValidator#validateColumnTypeprotected void validateColumnType( Table table, Column column, ColumnInformation columnInformation, Metadata metadata, ExecutionOptions options, Dialect dialect) { boolean typesMatch = dialect.equivalentTypes( column.getSqlTypeCode( metadata ), columnInformation.getTypeCode() ) || column.getSqlType( dialect, metadata ).toLowerCase(Locale.ROOT).startsWith( columnInformation.getTypeName().toLowerCase(Locale.ROOT) ); if ( !typesMatch ) { throw new SchemaManagementException( String.format(
-
그래서 해당 함수에 breakpoint를 찍어 놓고
board.deleted가 들어왔을 때 어떻게 매칭이 되는 지 확인을 해봤다.- column
sqlType = "boolean default false"의sqlTypeCode = null- JPA로 생성된 column
- columnInformation
typeName = "BIT"의typeCode = -7이라는 것을 볼 수 있다.- 실제 DB column
public int getSqlTypeCode(Mapping mapping) throws MappingException { org.hibernate.type.Type type = getValue().getType(); try { int sqlTypeCode = type.sqlTypes( mapping )[getTypeIndex()]; if ( getSqlTypeCode() != null && getSqlTypeCode() != sqlTypeCode ) { throw new MappingException( "SQLType code's does not match. mapped as " + sqlTypeCode + " but is " + getSqlTypeCode() ); } return sqlTypeCode; ... } public boolean equivalentTypes(int typeCode1, int typeCode2) { return typeCode1==typeCode2 || isNumericOrDecimal(typeCode1) && isNumericOrDecimal(typeCode2) || isFloatOrRealOrDouble(typeCode1) && isFloatOrRealOrDouble(typeCode2); }
- column은
column.getSqlTypeCode(metadata)에서BooleanType(16)을 받아와sqlTypeCode = 16이 된다. - 그렇기 때문에
dialect.equivalentTypes(column.getSqlTypeCode(metadata), columnInformation.getTypeCode())에서 column16, columnInformation-7이 일치한지 비교한 결과인 false를 리턴한다. - 그리고
column.getSqlType( dialect, metadata ).toLowerCase(Locale.ROOT).startsWith( columnInformation.getTypeName().toLowerCase(Locale.ROOT)에서 column의sqlType = "boolean default false"이 columnInformation의typeName = "BIT"의bit로 시작하지 않기 때문에 false를 반환하여 에러가 발생한다.
- column
- columnDefinition을
boolean default false→bit defalut 0- 에러가 발생했던 앞의 과정과 모두 똑같았지만
column.getSqlType(dialect, metadata ).toLowerCase(Locale.ROOT).startsWith(columnInformation.getTypeName().toLowerCase(Locale.ROOT)에서 column의sqlType = "bit default 0"이 columnInformation의typeName = "BIT"의bit로 시작하기 때문에 true를 반환하여 에러가 발생하지 않는다.
- 에러가 발생했던 앞의 과정과 모두 똑같았지만
- columnDefinition 적용 X;
- 에러가 발생했던 앞의 과정과 모두 똑같았지만
column.getSqlType(dialect, metadata ).toLowerCase(Locale.ROOT).startsWith(columnInformation.getTypeName().toLowerCase(Locale.ROOT)에서 column의sqlType = "bit"이 columnInformation의typeName = "BIT"의bit로 시작하기 때문에 true를 반환하여 에러가 발생하지 않는다.
- 에러가 발생했던 앞의 과정과 모두 똑같았지만
-
Flyway Documentation - Community Plugins and Integrations: Spring Boot
Database Migrations with Flyway
나만 모르고 있던 - Flyway (DB 마이그레이션 Tool)
Flyway 로 Java 에서 DB schema, seed 관리하기
Docker Compose 로 local 개발 환경 쉽게 관리하기
Execute Flyway Database Migrations on Startup
flyway - Java 용 Database migration framework