Flyway 實戰(zhàn)

原文地址:https://alphahinex.github.io/2021/08/08/flyway-in-action/

cover

description: "Everything as code"
date: 2021.08.08 10:26
categories:
- Java
tags: [Spring Boot, Database]
keywords: Database migration, Flyway, Liquibase, Spring Boot, Gradle


Using Liquibase with Gradle in Spring Project 中,介紹了數(shù)據(jù)庫版本控制工具 Liquibase,并且總結(jié)到

面向 SQL,選擇 Flyway

不面向 SQL,選擇 Liquibase

如果你還在單獨分發(fā)數(shù)據(jù)庫變動腳本,甚至簡單粗暴的將開發(fā)庫直接導(dǎo)出并導(dǎo)入生產(chǎn)環(huán)境,建議一起來了解一下 Flyway 的用法。

Flyway 中的常用概念

Flyway 中的概念可查閱 官方文檔,這里挑選一些重要的進(jìn)行簡單介紹。

Schema History Table

Flyway 對數(shù)據(jù)庫進(jìn)行版本控制的方式,是在指定數(shù)據(jù)庫中創(chuàng)建一張表,即 Schema History Table(默認(rèn)為 flyway_schema_history),記錄由 Flyway 所執(zhí)行的 sql 腳本狀態(tài)。

Migration

在 Flyway 中,所有對數(shù)據(jù)庫的變動,均稱為 migration,migration 可以是 SQL 文件,也可以是 Java 類。

默認(rèn)的查找 migration 的路徑為 classpath:db/migration,對應(yīng) SQL 文件可放置在 src/main/resources/db/migration 下,Java 類可放置在 src/main/java/db/migration 下。

Migration 可以是僅執(zhí)行一次的(versioned),也可是重復(fù)執(zhí)行的(repeatable)。

Versioned Migration

Versioned migration 包括版本號、描述和校驗值(checksum,自動計算),命名方式如下:

Versioned Migration

其中版本號必須全局(一個 Schema History Table 里)唯一,且默認(rèn)情況下(可通過參數(shù)調(diào)整)版本號只能增加,不能在已經(jīng)執(zhí)行了高版本的 migration 之后再執(zhí)行低版本的 migration。

版本號可以是數(shù)字加 . 的形式,例如下列都是合法的版本號:

1
001
5.2
1.2.3.4.5.6.7.8.9
205.68
20130115113556
2013.1.15.11.35.56
2013.01.15.11.35.56

checksum 用來檢查 migration 在執(zhí)行過后是否發(fā)生了變化,如果發(fā)生了變化,會導(dǎo)致這個版本以及后續(xù)版本的 migration 無法執(zhí)行。

Undo Migration

Versioned migration 中,還有一種特殊的類型 undo migration,可為對應(yīng)版本的常規(guī) versioned migration 進(jìn)行回退操作,命名方式如下:

Undo Migration

此功能為收費功能,在社區(qū)版 Flyway 中無法使用。

Repeatable Migration

Repeatable migration 包括描述和校驗值,沒有版本號信息,會在每次校驗值(migration 內(nèi)容變化會導(dǎo)致校驗值變化)發(fā)生變化時重復(fù)執(zhí)行,命名方式如下:

Repeatable Migration

Repeatable migration 會在所有的 versioned migration 都執(zhí)行過后再進(jìn)行執(zhí)行,Repeatable migration 內(nèi)部按照其文件名中描述(description)部分的順序執(zhí)行。

在 repeatable migration 中需保證其中內(nèi)容可重復(fù)執(zhí)行,比如在 DDL 中使用 CREATE OR REPLACE

Baseline

當(dāng)數(shù)據(jù)中已經(jīng)存在內(nèi)容時,再引入 Flyway,可通過 Baseline 設(shè)定一條基線。

Baselineg

如果想將基線之前的數(shù)據(jù)庫中表結(jié)構(gòu)和數(shù)據(jù)納入 Flyway 一同管理,可以將基線前的狀態(tài)導(dǎo)出成數(shù)據(jù)庫腳本,通過 versioned migration 添加至 flyway 中,并設(shè)定 baselin version,F(xiàn)lyway 會忽略基線版本號(包括)之前版本的所有 migration。

如果不想管理基線之前的數(shù)據(jù)庫狀態(tài)(比如多模塊或應(yīng)用操作同一數(shù)據(jù)庫,互相之間不受影響),可以只告訴 Flyway 執(zhí)行 migration 的時候是存在基線的,這樣就不會報出數(shù)據(jù)庫非空的異常。

如何在一個 Spring Boot 項目中引入 Flyway

在 Spring Boot 項目中,引入 Flyway 非常簡單,因為在 Spring Boot 的 spring-boot-autoconfigure 中包含了 Flyway 的自動配置,只要添加 flyway 的依賴即可。

演示工程

以在 spring initializr 中新建一個演示工程為例,添加 Spring Web、Spring Data JPAMySQL Driver 依賴:

spring initializr

設(shè)定數(shù)據(jù)庫連接相關(guān)參數(shù) spring.datasource.url、spring.datasource.usernamespring.datasource.password,即可完成演示工程的準(zhǔn)備。

引入 Flyway

添加依賴

Gradle 可以通過 implementation 'org.flywaydb:flyway-core' 添加 Flyway 依賴。

Maven 可加入:

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>

版本號推薦使用 Spring Boot 依賴管理中定義好的版本(即無需額外指定)。

添加 Flyway 依賴后,無需其他配置,F(xiàn)lyway 的功能是自動啟用的,如果想停用 Flyway,需設(shè)置 spring.flyway.enabled=false

添加 sql 腳本

引入 Flyway 之后,再啟動應(yīng)用時,會在默認(rèn)路徑 classpath:db/migration 查找 sql 腳本,如果沒找到會報錯,影響應(yīng)用啟動。

可在 src/main/resources/db/migration 路徑下創(chuàng)建 sql 文件,如 src/main/resources/db/migration/V20210808__test.sql,使應(yīng)用可正常啟動。

啟動成功后,可在所指定的數(shù)據(jù)庫中的 flyway_schema_history 表中查看初始化腳本執(zhí)行狀態(tài)。

可通過 spring.flyway.table=another_table 修改默認(rèn)表名。

修改默認(rèn)表名,可實現(xiàn)多個模塊分別通過 Flyway 進(jìn)行數(shù)據(jù)庫版本控制,并連接到相同數(shù)據(jù)庫的效果。

非空數(shù)據(jù)庫處理

當(dāng)在一個已有項目中引入 Flyway 時,數(shù)據(jù)庫中可能已經(jīng)存在了一些表和初始化數(shù)據(jù),此時按照上述方式引入 Flyway 時會提示如下異常:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'flywayInitializer' defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]: Invocation of init method failed; nested exception is org.flywaydb.core.api.FlywayException: Found non-empty schema(s) `demo` but no schema history table. Use baseline() or set baselineOnMigrate to true to initialize the schema history table.

此時需要設(shè)定 spring.flyway.baseline-on-migrate=true 參數(shù),告訴 Flyway 在執(zhí)行 sql 腳本之前,數(shù)據(jù)庫是非空狀態(tài)。

如果想指定 baseline version,可通過設(shè)定 spring.flyway.baseline-version=20210809 參數(shù),以忽略 20210809 版本以及之前的所有 migration。

其他問題

執(zhí)行方式

Flyway 的 migration 會在 Spring Boot 應(yīng)用啟動時自動執(zhí)行,如果不想通過啟動應(yīng)用的方式執(zhí)行,官方提供了命令行、API、以及 Maven 和 Gradle 插件的方式,但總的來說都會麻煩一些,因為需要將已經(jīng)在 Spring Boot 中配置的參數(shù),再到其他執(zhí)行方式所各自要求的位置重新配置一遍,實用性一般。

生產(chǎn)環(huán)境數(shù)據(jù)安全性

Flyway 的 Clean 命令,會將 Flyway 所連接的數(shù)據(jù)庫中的所有內(nèi)容全部清理掉,不論其中的表或數(shù)據(jù)是否是通過 Flyway 添加進(jìn)去的。

在生產(chǎn)環(huán)境中使用 Flyway 確實也存在一定的風(fēng)險,但這個風(fēng)險不是 Flyway 本身造成的,有權(quán)連接生產(chǎn)庫的人的任何一個誤操作,都會導(dǎo)致生產(chǎn)環(huán)境數(shù)據(jù)的丟失,建議不要因噎廢食。

Flyway 對此也提供了一定的防范措施,可通過禁用 Clean 命令來防止此問題發(fā)生,比如通過 Spring Boot 的 spring.flyway.clean-disabled=true 參數(shù),或通過 Gradle 插件的配置:

flyway {
    ...
    cleanDisabled = true
    ...
}

每種執(zhí)行 Flyway 命令的方式均可設(shè)置此參數(shù),建議在所有環(huán)境都禁用 Clean 命令。

SQL 報錯

通過 Spring Boot 自動執(zhí)行 migration 時要注意,一旦 migration 執(zhí)行失敗,應(yīng)用啟動會終止。出現(xiàn) migration 執(zhí)行失敗時,需要將 Schema History Table 表中的失敗記錄處理掉,才能再次執(zhí)行 migration,否則應(yīng)用會一直無法啟動。

out-of-order

多人開發(fā)時,可能會出現(xiàn) A 寫了 V1 腳本,B 寫了 V2 腳本,B 的代碼先合并進(jìn)去了,V2 腳本先執(zhí)行了,此時 A 的 V1 腳本受版本號只能增加的要求不能再執(zhí)行。

這種情況可以通過將 spring.flyway.out-of-order 設(shè)置為 true 來暫時取消這個限制,不過還是強(qiáng)烈建議 A 將 V1 腳本版本號改為 V3。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容