Maven學習總結(33)——開發人員如何使用 Flyway 插件管理數據庫版本

一、前言、

想到要管理數據庫的版本,是在實際產品中遇到問題後想到的一種解決方案,當時各個環境的數據庫亂作一團,沒有任何一個人(開發、測試、維護人員)能夠講清楚當前環境下的數據庫是哪個版本,與哪個版本的應用相匹配,如何升級到與新版本的應用相匹配。想到管理數據庫版本時,先是心底形成了一個初步的解決方案,大致是通過數據庫中的某張表來記錄數據庫表結構的歷次更新與對應版本,在每次數據庫表結構調整時除了提供庫表更新sql ,還必須提供更新記錄與對應版本的sql,以幫助維護數據庫版本信息,並在遇到問題時提供相關的排查依據。後來據此思路在網絡上搜索了一把,結果搜到Liquibase (另一款開源數據庫版本管理工具)。在學習瞭解Liquibase 的時候,經高手介紹又瞭解到了Flyway 這個項目的存在。經過一番瞭解,最後我們選擇了Flyway ,主要原因是Flyway 支持sql 腳本,而Liquibase 只支持XML 方式的數據庫表結構定義,雖然在新的版本中號稱在XML 的數據庫表結構定義方式中支持了sql 腳本。雖然Flyway 的中文文檔近乎爲零,英文文檔也鳳毛麟角,但它卻是我們最理想的數據庫版本管理工具,它不但支持sql 腳本,還支持Java 代碼直接操作數據庫(在版本升級時做數據遷移相當有用),有Maven 插件,支持命令行(我們的平臺數據庫有部分由C 語言項目管理),而且在spring 框架的配合下,很容易就能實現應用啓動時自動檢查並升級數據庫的功能。

數據庫開發遇到的問題:

  • 不同的開發人員在開發產品特性時,都有可能更新數據庫(添加新表,新的約束等)。當開發人員完成工作並提交代碼時,代碼會被合併到主分支並在測試服務器上執行單元測試與集成測試。我們在哪個環節來執行數據庫的更新操作呢?由QA 部門手工執行sql 腳本?或者我們開發一斷程序自動執行數據庫更新?以什麼順序來執行這些更新腳本?這些問題同樣存在於生產環境。 
  • 我們的產品部署在不同的客戶服務器上,以及很多的測試、聯調、實驗局、銷售環境上。不同的客戶和測試環境上都部署着不同版本的產品。當他們需要升級他們的產品到新的版本時,我們不僅需要讓他們的管理員可以升級產品到新的版本,同時需要保留他們的已有數據。在升級產品的步驟中,我們清楚地知道客戶數據庫的當前版本,以及需要在該數據庫上執行哪些數據庫更新腳本,來更新數據庫表結構與數據庫中已存在的數據。當升級完成時,數據庫表結構及數據應當與升級後的產品版本保持一致。
  • 有的時候,我們需要通過代碼(Java )來維護一些已存在的數據,如通過代碼來維護blob 類型的用戶頭像數據。
  • 當升級失敗時(比如在升級過程中出現網絡連接失敗),我們應當支持對失敗進行修復。

Flyway的特性

  • 自動升級(自動發現更新項):Flyway 會將任意版本的數據庫升級到最新版本。Flyway 可以脫離JVM 環境通過命令行執行,可以通過Ant 腳本執行,通過Maven 腳本執行(這樣就可以在集成環境自動執行),並且可以在應用中執行(比如在應用啓動時執行)。
  • 規約優於配置:Flyway 有一套默認的規約,所以不需要修改任何配置就可以正常使用。
  • 既支持SQL 腳本,又支持Java 代碼:可以使用SQL 腳本執行數據庫更新,也可以使用Java 代碼來進行一些高級數據升級操作。
  • 高可靠性:在集羣環境下進行數據庫升級是安全可靠的。
  • 支持清除已存在的庫表結構:Flyway 可以清除已存在的庫表結構,可以從零開始搭建您的庫表結構,並管理您的數據庫版本升級工作。
  • 支持失敗修復。新的2.0 版本提供了repair 功能,用於解決數據庫更新操作失敗問題。

二、什麼是Flyway

Flyway 是獨立於數據庫的應用、管理並跟蹤數據庫變更的數據庫版本管理工具。Flyway 的項目主頁是 http://flywaydb.org/ 。Flyway 和 Liquibase 都是 Java 項目中常用的 DB migration 工具, 從使用簡便性看,Flyway 比 Liquibase 更簡單, 從 github 的 star 數量看, flyway 更受歡迎。對於 SpringBoot 項目開發, 其實不需要專門安裝 flyway 命令行工具和 maven 插件, SpringBoot 啓動就會自動執行 DB migrate 操作. 對於其他的 flyway 操作, 就需要使用命令行工具或 maven 插件了。

  • 版本:對數據庫的每一次變更可稱爲一個版本。
  • 遷移:Flyway把數據庫結構從一個版本更新到另一個版本叫做遷移。
  • 可用的遷移:Flyway的文件系統識別出來的遷移版本。
  • 已經應用的遷移:Flyway已經對數據庫執行過的遷移。

flyway 提供命令行工具, 常用的命令包括:

  • Clean: 刪除所有創建的數據庫對象, 包括用戶、表、視圖等。(注意不要在生產庫上執行 clean 操作) 
  • Migrate: 對數據庫依次應用版本更改. 
  • Info: 獲取目前數據庫的狀態. 那些遷移已經完成, 那些遷移待完成. 所有遷移的執行時間以及結果. 
  • Validate: 驗證已 Apply 的腳本是否有變更, Flyway 的 Migration 默認先做 Validate. 
  • Baseline: 根據現有的數據庫結構生成一個基準遷移腳本. 
  • Repair: 修復命令儘量不要使用, 修復場景有: 1. 移除失敗的 migration 記錄. 2.已經應用的 SQL 腳本被修改, 我們想重新應用該 SQL 腳本.

三、Maven插件使用

maven 插件, 最新 maven 插件見 https://mvnrepository.com/artifact/org.flywaydb/flyway-maven-plugin。maven插件命令, mvn flyway:migrate

<plugin>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-maven-plugin</artifactId>
    <version>4.0.3</version> 
</plugin>

注意:編寫數據庫的版本的腳本文件,放到src/main/resources的flyway裏面:(flyway找腳本的時候默認去src/mian/resources下面的db/migration,如果要放在別的位置,後面的地方要配置一下)

四、Flyway 的工作原理

flyway 需要在 DB 中先創建一個 metdata 表 (缺省表名爲 flyway_schema_history), 在該表中保存着每次 migration 的記錄, 記錄包含 migration 腳本的版本號和 SQL 腳本的 checksum 值. 當一個新的 SQL 腳本被掃描到後, Flyway 解析該 SQL 腳本的版本號, 並和 metadata 表已 apply 的的 migration 對比, 如果該 SQL 腳本版本更新的話, 將在指定的 DB 上執行該 SQL 文件, 否則跳過該 SQL 文件。兩個 flyway 版本號的比較, 採用左對齊原則, 缺位用 0 代替. 舉例如下: 

1.2.9.4 比 1.2.9 版本高. 
1.2.10 比 1.2.9.4 版本高. 
1.2.10 和 1.2.010 版本號一樣高, 每個版本號部分的前導 0 會被忽略.

Flyway SQL 文件可以分爲兩類: Versioned 和 Repeatable. Versioned migration 用於版本升級, 每個版本有唯一的版本號並只能 apply 一次. Repeatable migration 是指可重複加載的 migration, 一旦 SQL 腳本的 checksum 有變動, flyway 就會重新應用該腳本. 它並不用於版本更新, 這類的 migration 總是在 versioned migration 執行之後才被執行.默認情況下, Migration SQL的命名規則如下圖:

其中的文件名由以下部分組成,除了使用默認配置外,某些部分還可自定義規則.

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

flyway 的 metadata 表結果如下:   

CREATE TABLE  flyway_schema_history(
        installed_rank INT NOT NULL,
        version VARCHAR(50),
        description VARCHAR(200) NOT NULL,
        type VARCHAR(20) NOT NULL,
        script VARCHAR(1000) NOT NULL,
        checksum INT,
        installed_by VARCHAR(100) NOT NULL,
        installed_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        execution_time INT NOT NULL,
        success TINYINT(1) NOT NULL,
        PRIMARY KEY (installed_rank),
        INDEX flyway_schema_history_s_idx (success)
    )
    ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

pom.xml配置如下(spring-boot-starter-parent 包沒有使用最新的 2.0.5, 最新版總是導致 HikariPool 無法初始化, 所以選擇的版本是 2.0.4)

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <!-- lookup parent from repository -->
        <relativePath /> 
    </parent>

flyway 其實僅依賴 spring-boot-starter-jdbc 包,

    <dependency>
        <groupId>org.flywaydb</groupId>
        <artifactId>flyway-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

 

加上 spring-boot-maven-plugin , 可生成 fat jar. 

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>      

application.properties 參數

## 設定 db source 屬性
spring.datasource.url=jdbc:mysql://localhost:3306/world
spring.datasource.username=root
spring.datasource.password=toor

## 設定 flyway 屬性 
spring.flyway.cleanDisabled = true 
  # flyway 的 clean 命令會刪除指定 schema 下的所有 table, 殺傷力太大了, 應該禁掉. 
spring.flyway.enabled = true
  # 啓用或禁用 flyway 
spring.flyway.locations =classpath:db/migration
  # 設定 SQL 腳本的目錄,多個路徑使用逗號分隔, 比如取值爲 classpath:db/migration,filesystem:/sql-migrations
spring.flyway.baselineOnMigrate=true
  # 如果指定 schema 包含了其他表,但沒有 flyway schema history 表的話, 在執行 flyway migrate 命令之前, 必須先執行 flyway baseline 命令.
  # 設置 spring.flyway.baseline-on-migrate 爲 true 後, flyway 將在需要 baseline 的時候, 自動執行一次 baseline. 
spring.flyway.baselineVersion=1 
  # 指定 baseline 的版本號,缺省值爲 1, 低於該版本號的 SQL 文件, migrate 的時候被忽略. 
#spring.flyway.encoding=
  # Encoding of SQL migrations (default: UTF-8)
spring.flyway.table=flyway_schema_history_myapp
  # 設定 flyway 的 metadata 表名, 缺省爲 flyway_schema_history
spring.flyway.outOfOrder=true
  # 開發環境最好開啓 outOfOrder, 生產環境關閉 outOfOrder . 
#spring.flyway.schemas=
  # 需要 flyway 管控的 schema list, 缺省的話, 使用的時 dbsource.connection直連上的那個 schema, 可以指定多個schema, 但僅會在第一個schema下建立 metadata 表, 也僅在第一個schema應用migration sql 腳本. 但flyway Clean 命令會依次在這些schema下都執行一遍. 

更多參數見 https://flywaydb.org/documentation/configfiles,這些參數配到springboot2 項目中, 需要加上 spring. 前綴. 

flyway.baseline-description對執行遷移時基準版本的描述.
flyway.baseline-on-migrate當遷移時發現目標schema非空,而且帶有沒有元數據的表時,是否自動執行基準遷移,默認false.
flyway.baseline-version開始執行基準遷移時對現有的schema的版本打標籤,默認值爲1.
flyway.check-location檢查遷移腳本的位置是否存在,默認false.
flyway.clean-on-validation-error當發現校驗錯誤時是否自動調用clean,默認false.
flyway.enabled是否開啓flywary,默認true.
flyway.encoding設置遷移時的編碼,默認UTF-8.
flyway.ignore-failed-future-migration當讀取元數據表時是否忽略錯誤的遷移,默認false.
flyway.init-sqls當初始化好連接時要執行的SQL.
flyway.locations遷移腳本的位置,默認db/migration.
flyway.out-of-order是否允許無序的遷移,默認false.
flyway.password目標數據庫的密碼.
flyway.placeholder-prefix設置每個placeholder的前綴,默認${.
flyway.placeholder-replacementplaceholders是否要被替換,默認true.
flyway.placeholder-suffix設置每個placeholder的後綴,默認}.
flyway.placeholders.[placeholder name]設置placeholder的value
flyway.schemas設定需要flywary遷移的schema,大小寫敏感,默認爲連接默認的schema.
flyway.sql-migration-prefix遷移文件的前綴,默認爲V.
flyway.sql-migration-separator遷移腳本的文件名分隔符,默認__
flyway.sql-migration-suffix遷移腳本的後綴,默認爲.sql
flyway.tableflyway使用的元數據表名,默認爲schema_version
flyway.target遷移時使用的目標版本,默認爲latest version
flyway.url遷移時使用的JDBC URL,如果沒有指定的話,將使用配置的主數據源
flyway.user遷移數據庫的用戶名
flyway.validate-on-migrate遷移時是否校驗,默認爲true.

五、Flyway 最佳實踐

1. SQL 的文件名

開發環境和生產環境的 migration SQL 不共用. 開發過程往往是多人協作開發, DB migration 也相對比較頻繁, 所以 SQL 腳本會很多. 而生產環境 DB migration 往往由 DBA 完成, 每次升級通常需要提交一個 SQL 腳本.

(1). 開發環境 SQL 文件建議採用時間戳作爲版本號. 

開發環境 SQL 文件建議採用時間戳作爲版本號, 多人一起開發不會導致版本號爭用, 同時再加上生產環境的版本號, 這樣的話, 將來手工 merge 成生產環境 V1.2d migration 腳本也比較方便, SQL 文件示例:
V20180317.10.59__V1.2_Unique_User_Names.sql
V20180317.14.59__V1.2_Add_SomeTables.sql

(2). 生產環境 SQL 文件, 應該是手動 merge 開發環境的 SQL 腳本, 版本號按照正常的版本, 比如

V2.1.5_001__Unique_User_Names.sql

2. migration 後的SQL 腳本不應該再被修改.

3. spring.flyway.outOfOrder 取值 true /false

對於開發環境, 可能是多人協作開發, 很可能先 apply 了自己本地的最新 SQL 代碼, 然後發現其他同事早先時候提交的 SQL 代碼還沒有 apply, 所以 開發環境應該設置 spring.flyway.outOfOrder=true, 這樣 flyway 將能加載漏掉的老版本 SQL 文件; 而生產環境應該設置 spring.flyway.outOfOrder=false

4. 多個系統公用要 DB schema 

很多時候多個系統公用一個 DB schema , 這時候使用 spring.flyway.table 爲不同的系統設置不同的 metadata 表, 缺省爲 flyway_schema_history

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章