flyway實現java 自動升級SQL指令碼

天下沒有收費的bug發表於2021-07-07

flyway實現java 自動升級SQL指令碼

為什麼要用Flyway

在日常開發中,我們經常會遇到下面的問題:

  1. 自己寫的SQL忘了在所有環境執行;

  2. 別人寫的SQL我們不能確定是否都在所有環境執行過了;

  3. 有人修改了已經執行過的SQL,期望再次執行;

  4. 需要新增環境做資料遷移;

  5. 每次發版需要手動控制先發DB版本,再發布應用版本;

  6. 其它場景...

由於專案需求的變化,或者前期設計缺陷,導致在後期需要修改資料庫,這應該是一個比較常見的事情,如果專案還沒上線,你可能把表刪除了重新建立,但是如果專案已經上線了,就不能這樣簡單粗暴了,每次運維部署專案,還得手動執行一遍SQL檔案。我們需要通過 SQL 指令碼在已有資料表的基礎上進行升級。

有了flyway,這些問題都能得到很好的解決。

使用了 Flyway 之後,如果再想進行資料庫版本升級,就不用該以前的資料庫指令碼了,直接建立新的資料庫指令碼,專案在啟動時檢測了有新的更高版本的指令碼,就會自動執行,這樣,在和其他同事配合工作時,也會方便很多。因為正常我們都是從 Git 上拉程式碼下來,不拉資料庫指令碼,這樣要是有人更新了資料庫,其他同事不一定能夠收到最新的通知,使用了 Flyway 就可以有效避免這個問題了。

所有的指令碼,一旦執行了,就會在 flyway_schema_history 表中有記錄,如果你不小心搞錯了,可以手動從 flyway_schema_history 表中刪除記錄,然後修改 SQL 指令碼後再重新啟動(生產環境不建議)。

Flyway是如何工作的

Flyway工作流程如下:

1、專案啟動,應用程式完成資料庫連線池的建立後,Flyway自動執行。

2、初次使用時,Flyway會建立一個flyway_schema_history表,用於記錄sql執行記錄。

3、Flyway會掃描專案指定路徑下(預設是classpath:db/migration)的所有sql指令碼,與flyway_schema_history表指令碼記錄進行比對。如果資料庫記錄執行過的指令碼記錄,與專案中的sql指令碼不一致,Flyway會報錯並停止專案執行。

4、如果校驗通過,則根據表中的sql記錄最大版本號,忽略所有版本號不大於該版本的指令碼。再按照版本號從小到大,逐個執行其餘指令碼。

專案中使用Flyway

首先,在pom檔案中引入flyway的核心依賴包:

1、引入核心依賴包:

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

這裡使用5.2.4版本。經測試7.0.0版本與目前我們使用的springboot版本有衝突,會導致flyway不執行。因此我們儘量不要使用高版本的flyway。

2、配置檔案:

簡單配置一個屬性即可使用

 # flyway 配置
 spring:
   flyway:
     # 啟用或禁用 flyway
     enabled: true
     # flyway 的 clean 命令會刪除指定 schema 下的所有 table, 生產務必禁掉。這個預設值是 false 理論上作為預設配置是不科學的。
     clean-disabled: true
     # SQL 指令碼的目錄,多個路徑使用逗號分隔 預設值 classpath:db/migration
     locations: classpath:db/migration
     #  metadata 版本控制資訊表 預設 flyway_schema_history
     table: flyway_schema_history
     # 如果沒有 flyway_schema_history 這個 metadata 表, 在執行 flyway migrate 命令之前, 必須先執行 flyway baseline 命令
     # 設定為 true 後 flyway 將在需要 baseline 的時候, 自動執行一次 baseline。
     baseline-on-migrate: true
     # 指定 baseline 的版本號,預設值為 1, 低於該版本號的 SQL 檔案, migrate 時會被忽略
     baseline-version: 1
     # 字元編碼 預設 UTF-8
     encoding: UTF-8
     # 是否允許不按順序遷移 開發建議 true  生產建議 false
     out-of-order: false
     # 需要 flyway 管控的 schema list,這裡我們配置為flyway  預設的話, 使用spring.datasource.url 配置的那個 schema,
     # 可以指定多個schema, 但僅會在第一個schema下建立 metadata 表, 也僅在第一個schema應用migration sql 指令碼.
     # 但flyway Clean 命令會依次在這些schema下都執行一遍. 所以 確保生產 spring.flyway.clean-disabled 為 true
     schemas: flyway
     # 執行遷移時是否自動呼叫驗證   當你的 版本不符合邏輯 比如 你先執行了 DML 而沒有 對應的DDL 會丟擲異常
     validate-on-migrate: true

flyway的properties配置清單(屬性未測試):

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

flyway的yml配置清單(已測試,沒問題,推薦使用yml格式的配置檔案)

 # flyway 配置
 spring:
   flyway:
     # 啟用或禁用 flyway
     enabled: true
     # flyway 的 clean 命令會刪除指定 schema 下的所有 table, 生產務必禁掉。這個預設值是 false 理論上作為預設配置是不科學的。
     clean-disabled: true
     # SQL 指令碼的目錄,多個路徑使用逗號分隔 預設值 classpath:db/migration
     locations: classpath:db/migration
     #  metadata 版本控制資訊表 預設 flyway_schema_history
     table: flyway_schema_history
     # 如果沒有 flyway_schema_history 這個 metadata 表, 在執行 flyway migrate 命令之前, 必須先執行 flyway baseline 命令
     # 設定為 true 後 flyway 將在需要 baseline 的時候, 自動執行一次 baseline。
     baseline-on-migrate: true
     # 指定 baseline 的版本號,預設值為 1, 低於該版本號的 SQL 檔案, migrate 時會被忽略
     baseline-version: 1
     # 字元編碼 預設 UTF-8
     encoding: UTF-8
     # 是否允許不按順序遷移 開發建議 true  生產建議 false
     out-of-order: false
     # 需要 flyway 管控的 schema list,這裡我們配置為flyway  預設的話, 使用spring.datasource.url 配置的那個 schema,
     # 可以指定多個schema, 但僅會在第一個schema下建立 metadata 表, 也僅在第一個schema應用migration sql 指令碼.
     # 但flyway Clean 命令會依次在這些schema下都執行一遍. 所以 確保生產 spring.flyway.clean-disabled 為 true
     schemas: flyway
     # 執行遷移時是否自動呼叫驗證   當你的 版本不符合邏輯 比如 你先執行了 DML 而沒有 對應的DDL 會丟擲異常
     validate-on-migrate: true

spring.flyway.clean-disabled:這個屬性非常關鍵,它表示是否要清除已有庫下的表,如果執行的指令碼是 V1__xxx.sql,那麼會先清除已有庫下的表,然後再執行指令碼,這在開發環境下還挺方便,但是在生產環境下就要命了,而且它預設就是要清除,生產環境一定要自己配置設定為 true。

3、建立db/migration

因為flyway預設是讀取resources/db/migration下的資料夾,如果我們需要修改這個路徑,可以在配置檔案中實現

4、編寫sql檔案

此處的SQL語句命名需要遵從一定的規範,否則執行的時候flyway會報錯。命名規則主要有兩種:

  1. 僅需要被執行一次的SQL命名以大寫的"V"開頭,後面跟上"0~9"數字的組合,數字之間可以用“.”或者下劃線"_"分割開,然後再以兩個下劃線 __ 分割,其後跟檔名稱,最後以.sql結尾。比如,V20210707__create_user.sqlV20210707__add_user.sql

  2. 可重複執行的SQL,則以大寫的“R”開頭,後面再以兩個下劃線分割,其後跟檔名稱,最後以.sql結尾。。比如,R__truncate_user_dml.sql

其中,V開頭的SQL執行優先順序要比R開頭的SQL優先順序高。

V:固定大寫

20210707.01:20210707是日期,後面用.01代表序號

因為flyway的執行是有個順序的,比如你執行了V2021__create_user,又執行V2020_update_user。就會報錯,原因就是2020<2021。所以我們要保證序號是依次增大。

Flyway 是如何比較兩個 SQL 檔案的先後順序呢?它採用 採用左對齊原則, 缺位用 0 代替 。舉幾個例子:

 1.0.1.1 比 1.0.1 版本高。
 ​
 1.0.10 比 1.0.9.4 版本高。
 ​
 1.0.10 和 1.0.010 版本號一樣高, 每個版本號部分的前導 0 會被忽略。

__:這個是兩個 _

create_user是一個簡單的sql描述

.sql:以.sql結尾的檔案字尾是約定

 

 

我們只要在資料庫中建立flyway這個資料庫,啟動專案,flyway就會執行sql檔案,建立user表,並且會自動生成一個flyway_schema_history

從這段啟動日誌中,我們可以看到 Flyway 的執行資訊,資料庫指令碼的執行執行,同時這裡還說了,Flyway 還給建立了一個 flyway_schema_history 表,這個表用來記錄資料庫的更新歷史。

flyway_schema_history裡面會去記錄sql檔案的執行記錄,每次啟動專案,都會去flyway_schema_history看sql是否執行過,如果沒有執行過,說明這個sql是新的sql,就會自動執行,並且記錄到表裡面。

有了這條記錄,下次再啟動專案,V20210707.01、V20210707.02、V20210708.01 這個三個指令碼檔案就不會執行了,因為系統知道這個指令碼已經執行過了,如果你還想讓這些指令碼再執行一遍,需要手動刪除 flyway_schema_history 表中的對應記錄,那麼專案啟動時,這個指令碼就會被執行了。

R開頭的檔案和V開頭的檔案略有不同,R開頭的檔案只要傳送修改,都會執行一遍。V開頭的檔案如果執行過一般,在傳送修改,就會報錯。為了控制版本,我們儘量使用V開頭的檔案,這樣我們也可以很清楚的看到每個版本中的sql檔案。

常見問題

問題1

flyway遇到的問題Caused by: java.lang.ClassNotFoundException: org.flywaydb.core.api.callback.FlywayCallbac

原因:springboot版本和flyway版本不一致,一般是flyway版本過高。

解決辦法:將flyway的版本降到5.2.4就ok了

問題2

springboot 整合flyway 但是不生效,flyway不會自動執行sql

原因:如上

原因2:專案中沒有配置資料庫,沒有引入sq依賴或者配置

解決辦法:如上

解決辦法2:引入sql依賴,在yml檔案中配置sql資訊

問題3

flyway出錯 FlywayException: Validate failed: Detected failed migration to version

原因:sql指令碼和資料庫中有衝突,需要檢查sql指令碼哪裡錯了。簡單的說就是V開頭的sql檔案,已經執行過了,在 flyway_schema_history 表裡面有這個資料,但是你又改動了sql檔案,導致再次執行的時候報錯。

解決辦法:新建一個sql檔案,不要修改原來以V開頭的檔案或者在flyway_schema_history表中找到檔案相關執行記錄,刪掉重新執行。

相關文章