1. 程式人生 > 程式設計 >快速掌握和使用Flyway的詳細教程

快速掌握和使用Flyway的詳細教程

什麼是Flyway?

轉載:https://blog.waterstrong.me/flyway-in-practice/

Flyway is an open-source database migration tool. It strongly favors simplicity and convention over configuration.

Flyway是一款開源的資料庫版本管理工具,它更傾向於規約優於配置的方式。Flyway可以獨立於應用實現管理並跟蹤資料庫變更,支援資料庫版本自動升級,並且有一套預設的規約,不需要複雜的配置,Migrations可以寫成SQL指令碼,也可以寫在Java程式碼中,不僅支援Command Line和Java API,還支援Build構建工具和Spring Boot等,同時在分散式環境下能夠安全可靠地升級資料庫,同時也支援失敗恢復等。

Flyway主要基於6種基本命令:Migrate,Clean,Info,Validate,BaselineandRepair,稍候會逐一分析講解。目前支援的資料庫主要有:Oracle,SQL Server,SQL Azure,DB2,DB2 z/OS,MySQL(including Amazon RDS),MariaDB,Google Cloud SQL,PostgreSQL(including Amazon RDS and Heroku),Redshift,Vertica,H2,Hsql,Derby,SQLite,SAP HANA,solidDB,Sybase ASE and Phoenix.

關於Flyway的優勢,支援的資料庫以及與其他資料庫版本工具的對比,可以閱讀Flyway官網介紹。

為什麼使用Flyway?

通常在專案開始時會針對資料庫進行全域性設計,但在開發產品新特性過程中,難免會遇到需要更新資料庫Schema的情況,比如:新增新表,新增新欄位和約束等,這種情況在實際專案中也經常發生。那麼,當開發人員完成了對資料庫更的SQL指令碼後,如何快速地在其他開發者機器上同步?並且如何在測試伺服器上快速同步?以及如何保證整合測試能夠順利執行並通過呢?

假設以Spring Boot技術棧專案為例,可能有人會說,本地使用Hibernate自動更新資料庫Schema模式,然後讓QA或DEV到測試伺服器上手動執行SQL指令碼,同時可以寫一個Gradle任務自動執行更新。

個人覺得,對於Hibernate自動更新資料庫,感覺不靠譜,不透明,控制自由度不高,而且有時很容易就會犯錯,比如:用SQL建立的某個欄位為VARCHAR型別,而在Entity中配置的為CHAR型別,那麼在執行整合測試時,自動建立的資料庫表中的欄位為CHAR型別,而實際SQL指令碼期望的是VARCHAR型別,雖然測試通過了,但不是期望的行為,並且在本地bootRun或伺服器上執行Service時都會失敗。另外,到各測試伺服器上手動執行SQL指令碼費時費神費力的,幹嘛不自動化呢,當然,對於高級別和PROD環境,還是需要DBA手動執行的。最後,寫一段自動化程式來自動執行更新,想法是很好的,那如果已經有了一些外掛或庫可以幫助你更好地實現這樣的功能,為何不好好利用一下呢,當然,如果是為了學習目的,重複造輪子是無可厚非的。

其實,以上問題可以通過Flyway工具來解決,Flyway可以實現自動化的資料庫版本管理,並且能夠記錄資料庫版本更新記錄,Flyway官網對Why database migrations結合示例進行了詳細的闡述,有興趣可以參閱一下。

Flyway如何工作的?

Flyway對資料庫進行版本管理主要由Metadata表和6種命令完成,Metadata主要用於記錄元資料,每種命令功能和解決的問題範圍不一樣,以下分別對metadata表和這些命令進行闡述,其中的示意圖都來自Flyway的官方文件。

Metadata Table

Flyway中最核心的就是用於記錄所有版本演化和狀態的Metadata表,在Flyway首次啟動時會建立預設名為SCHEMA_VERSION的元資料表,其表結構為(以MySQL為例):

Field Type Null Key Default
version_rank int(11) NO MUL NULL
installed_rank int(11) NO MUL NULL
version varchar(50) NO PRI NULL
description varchar(200) NO NULL
type varchar(20) NO NULL
script varchar(1000) NO NULL
checksum int(11) YES NULL
installed_by varchar(100) NO NULL
installed_on timestamp NO CURRENT_TIMESTAMP
execution_time int(11) NO NULL
success tinyint(1) NO MUL NULL

Flyway官網上提供了一個很清晰的示例How Flyway works,可以參閱一下。

Migrate

Migrate是指把資料庫Schema遷移到最新版本,是Flyway工作流的核心功能,Flyway在Migrate時會檢查Metadata(元資料)表,如果不存在會建立Metadata表,Metadata表主要用於記錄版本變更歷史以及Checksum之類的。

快速掌握和使用Flyway的詳細教程

Migrate時會掃描指定檔案系統或Classpath下的Migrations(可以理解為資料庫的版本指令碼),並且會逐一比對Metadata表中的已存在的版本記錄,如果有未應用的Migrations,Flyway會獲取這些Migrations並按次序Apply到資料庫中,否則不需要做任何事情。另外,通常在應用程式啟動時應預設執行Migrate操作,從而避免程式和資料庫的不一致性。

Clean

Clean相對比較容易理解,即清除掉對應資料庫Schema中的所有物件,包括表結構,檢視,儲存過程,函式以及所有的資料等都會被清除。

快速掌握和使用Flyway的詳細教程

Clean操作在開發和測試階段是非常有用的,它能夠幫助快速有效地更新和重新生成資料庫表結構,但特別注意的是:不應在Production的資料庫上使用!

Info

Info用於列印所有Migrations的詳細和狀態資訊,其實也是通過Metadata表和Migrations完成的,下圖很好地示意了Info打印出來的資訊。

快速掌握和使用Flyway的詳細教程

Info能夠幫助快速定位當前的資料庫版本,以及檢視執行成功和失敗的Migrations。

Validate

Validate是指驗證已經Apply的Migrations是否有變更,Flyway是預設是開啟驗證的。

快速掌握和使用Flyway的詳細教程

Validate原理是對比Metadata表與本地Migrations的Checksum值,如果值相同則驗證通過,否則驗證失敗,從而可以防止對已經Apply到資料庫的本地Migrations的無意修改。

Baseline

Baseline針對已經存在Schema結構的資料庫的一種解決方案,即實現在非空資料庫中新建Metadata表,並把Migrations應用到該資料庫。

快速掌握和使用Flyway的詳細教程

Baseline可以應用到特定的版本,這樣在已有表結構的資料庫中也可以實現新增Metadata表,從而利用Flyway進行新Migrations的管理了。

Repair

Repair操作能夠修復Metadata表,該操作在Metadata表出現錯誤時是非常有用的。

快速掌握和使用Flyway的詳細教程

Repair會修復Metadata表的錯誤,通常有兩種用途:

  • 移除失敗的Migration記錄,該問題只是針對不支援DDL事務的資料庫。
  • 重新調整已經應用的Migratons的Checksums值,比如:某個Migratinon已經被應用,但本地進行了修改,又期望重新應用並調整Checksum值,不過儘量不要這樣操作,否則可能造成其它環境失敗。

如何使用Flyway?

這裡將主要關注在Gradle和Spring Boot中整合並使用Flyway,資料庫通常會採用MySQL、PostgreSQL、H2或Hsql等。

正確建立Migrations

Migrations是指Flyway在更新資料庫時是使用的版本指令碼,比如:一個基於Sql的Migration命名為V1__init_tables.sql,內容即是建立所有表的sql語句,另外,Flyway也支援基於Java的Migration。Flyway載入Migrations的預設Locations為classpath:db/migration,也可以指定filesystem:/project/folder,其載入是在Runtime自動遞迴地執行的。

快速掌握和使用Flyway的詳細教程

除了需要指定Location外,Flyway對Migrations的掃描還必須遵從一定的命名模式,Migration主要分為兩類:Versioned和Repeatable。

Versioned migrations

一般常用的是Versioned型別,用於版本升級,每一個版本都有一個唯一的標識並且只能被應用一次,並且不能再修改已經載入過的Migrations,因為Metadata表會記錄其Checksum值。其中的version標識版本號,由一個或多個數字構成,數字之間的分隔符可以採用點或下劃線,在執行時下劃線其實也是被替換成點了,每一部分的前導零會被自動忽略。

Repeatable migrations

Repeatable是指可重複載入的Migrations,其每一次的更新會影響Checksum值,然後都會被重新載入,並不用於版本升級。對於管理不穩定的資料庫物件的更新時非常有用。Repeatable的Migrations總是在Versioned之後按順序執行,但開發者必須自己維護指令碼並且確保可以重複執行,通常會在sql語句中使用CREATE OR REPLACE來保證可重複執行。

預設情況下基於Sql的Migration檔案的命令規則如下圖所示:

快速掌握和使用Flyway的詳細教程

其中的檔名由以下部分組成,除了使用預設配置外,某些部分還可自定義規則。

  • prefix: 可配置,字首標識,預設值V表示Versioned,R表示Repeatable
  • version: 標識版本號,由一個或多個數字構成,數字之間的分隔符可用點.或下劃線_
  • separator: 可配置,用於分隔版本標識與描述資訊,預設為兩個下劃線__
  • description: 描述資訊,文字之間可以用下劃線或空格分隔
  • suffix: 可配置,後續標識,預設為.sql

另外,關於如何使用基於Java的Migrations,有興趣可以參考Java-based migrations。

支援的資料庫
目前Flyway支援的資料庫還是挺多的,包括:Oracle,Sybase ASE and Phoenix。
目前來說,個人用得比較多的資料庫是PostgreSQL、MySQL、H2和Hsql,針對每種資料庫的flyway.url示例配置為:

另外,關於如何使用基於Java的Migrations,有興趣可以參考Java-based migrations。

支援的資料庫

目前Flyway支援的資料庫還是挺多的,包括:Oracle,Sybase ASE and Phoenix。
目前來說,個人用得比較多的資料庫是PostgreSQL、MySQL、H2和Hsql,針對每種資料庫的flyway.url示例配置為:

# PostgreSQL
flyway.url = jdbc:postgresql://localhost:5432/postgres?currentSchema=myschema
# MySQL
flyway.url = jdbc:mysql://localhost:3306/testdb?serverTimezone=UTC&useSSL=true
# H2
flyway.url = jdbc:h2:./.tmp/testdb
# Hsql
flyway.url = jdbc:hsqldb:hsql//localhost:1476/testdb

Flyway命令列

Flyway的命令列工具支援直接在命令列中執行Migrate,BaselineRepair6種命令,不需要藉助其他Build工具,不需要應用程式執行在JVM中,只需要單純的命令列即可,但需要根據不同的作業系統下載並安裝該命令列工具。Flyway會依次搜尋以下配置檔案,越靠後的配置會覆蓋靠前的配置:

  • /conf/flyway.conf
  • /flyway.conf
  • /flyway.conf

一個典型Flyway專案示例目錄結構如下:

快速掌握和使用Flyway的詳細教程

更多關於Flyway命令列使用可以參考Flyway Command-line。

在Gradle中的應用

首先需要在Gradle中引入Flyway外掛,通常有兩種方式:

方式一:採用buildscript依賴方式。

buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.flywaydb:flyway-gradle-plugin:4.0.3")
}
}
apply plugin: 'org.flywaydb.flyway'

方式二(推薦):採用DSL方式引用Plugins。

plugins {
id "org.flywaydb.flyway" version "4.0.3"
}

而在Gradle中配置Flyway Properties有兩種方式:

方式一:在build.gradle中配置Flyway Properties。

flyway {
url = jdbc:h2:./.tmp/testdb
user = sa
password =
}
# 或者寫成:
project.ext['flyway.url'] = 'jdbc:h2:./.tmp/testdb'
project.ext['flyway.user'] = 'sa'
project.ext['flyway.password'] = ''

方式二:在gradle.properties中配置Flyway Properties。

flyway.url = jdbc:h2:./.tmp/testdb
flyway.user = sa
flyway.password =

如果期望在執行Gradle Clean/Build Tasks時自動執行Flyway的某些任務,可以設定dependsOn,若不期望隱式執行Flyway任務,可以不配置。

clean.dependsOn flywayRepair # To repair the Flyway metadata table
build.dependsOn flywayMigrate # To migrate the schema to the latest version

另外,其它Tasks:flywayInfo,flywayValidate,flywayBaseline分別對應到Flyway的命令。在使用Spring Boot時,執行./gradlew bootRun會自動檢查並載入最新的db.migration指令碼。

特別注意:在Production環境中不應執行./gradlew flywayClean,除非你知道自己的行為和目的,因為該命令會清除所有的資料庫物件,相當危險。

更多關於Flyway在Gradle中的使用請參閱Flyway Gradle Plugin。

與Spring Boot整合

在Spring Boot中,如果加入Flyway的依賴,則會自動引用Flyway並使用預設值,但可以修改並配置FlywayProperties。

flyway.baseline-description= # The description to tag an existing schema with when executing baseline.
flyway.baseline-version=1 # Version to start migration.
flyway.baseline-on-migrate=false # Whether to execute migration against a non-empty schema with no metadata table
flyway.check-location=false # Check that migration scripts location exists.
flyway.clean-on-validation-error=false # will clean all objects. Warning! Do NOT enable in production!
flyway.enabled=true # Enable flyway.
flyway.encoding=UTF-8 # The encoding of migrations.
flyway.ignore-failed-future-migration=true # Ignore future migrations when reading the metadata table.
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=false # Allows migrations to be run "out of order".
flyway.placeholder-prefix= # The prefix of every placeholder.
flyway.placeholder-replacement=true # Whether placeholders should be replaced.
flyway.placeholder-suffix=} # The suffix of every placeholder.
flyway.placeholders.*= # Placeholders to replace in Sql migrations.
flyway.schemas= # Default schema of the connection and updating
flyway.sql-migration-prefix=V # The file name prefix for Sql migrations
flyway.sql-migration-separator=__ # The file name separator for Sql migrations
flyway.sql-migration-suffix=.sql # The file name suffix for Sql migrations
flyway.table=schema_version # The name of Flyway's metadata 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. If not set,use spring.datasource.username value.
flyway.password= # JDBC password if you want Flyway to create its own DataSource.
flyway.validate-on-migrate=true # Validate sql migration CRC32 checksum in classpath.

若使用Gradle,通常在build.gradle引入org.flywaydb:flyway-core:4.0.3依賴後即可使用。可能會有以下幾種需求:

  • 在本地Run和Tests都會使用記憶體資料庫,其中的spring.jpa.hibernate.ddl-auto都設定為validate,Schema不需要Hibernate自動生成,並期望使用Flyway,而在線上環境會使用真實資料庫,並不期望使用Flyway,如何實現呢?
  • 解決方案:可以在common.properties中配置flyway.enabled=false,然後在local或dev的配置中啟用Flyway即可。通常推薦使用此模式,畢竟可以對不同的環境進行控制,另外本地Run不會依賴真實資料庫,又能保證資料庫Schema是按指令碼建立的。
  • 在執行Tests會使用記憶體資料庫,有單獨的配置檔案,不使用Flyway,而在本地bootRun時會使用真實資料庫,使用Flyway,畢竟不想每次Schema改後都在本地手動去執行指令碼,如何實現?

解決方案:設定bootRun.dependsOn動態新增Flyway的依賴即可:

addFlywayDenpendency {
doLast {
dependencies {
compile('org.flywaydb:flyway-core:4.0.3')
}
}
}
bootRun.dependsOn=addFlywayDenpendency

若專案有多個團隊同時開發不同的功能,需要新建多個分支,並且都會涉及到資料庫Schema更改,當後期Merge時,Migration的版本如何控制並且不會產生資料庫更改的衝突呢?
解決方案:如果兩個分支的資料庫更改有衝突,要麼最初資料庫設計不合理,要麼目前資料庫更改不合理,所以需要團隊進行全域性考慮和協調。而針對資料庫在同一段時間有修改,但不會造成衝突的情況,通常實際專案中主要存在這樣的情況,那可以設定flyway.out-of-order=true,這樣允許當v1和v3已經被應用後,v2出現時同樣也可以被應用。其實在本地使用記憶體資料庫不會存在該問題,因為資料庫所有物件會自動清除掉,而在local或dev中使用真實資料庫時可遇到這樣的問題,因此需要注意一下了。
另外,值得一提的是Flyway的引數ignore-failed-future-migration預設為true,使用情形為:當Rollback資料庫更改到舊版本,而metadata表中已存在了新版本時,Flyway會忽略此錯誤,只會顯示警告資訊。

結束語

總得來說,Flyway可以有效改善資料庫版本管理方式,如果專案中還未使用,不防嘗試一下。如果有興趣,也可以關注MyBatis Migration,功能支援沒有Flyway多,屬於更輕量級的資料庫版本管理工具。如果在使用過程中遇到了問題或坑,歡迎留言一起交流討論。

References

Flyway Documentation

Gradle Plugin: Flyway

Spring Common application properties

Execute Flyway database migrations on startup

到此這篇關於快速掌握和使用Flyway的詳細教程的文章就介紹到這了,更多相關使用Flyway的技巧內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!