Spring Boot 簡單整合 Liquibase

eddy_zy發表於2019-03-27

Liquibase 是一個用於跟蹤、管理和應用資料庫變化的開源的資料庫重構工具。它將所有資料庫的變化(包括結構和資料)都儲存在 changelog檔案中,便於版本控制,它的目標是提供一種資料庫型別無關的解決方案,通過執行 schema 型別的檔案來達到遷移。

Liquibase 特性

Liquibase 具備如下特性:

  • 支援幾乎所有主流的資料庫,如 MySQL, PostgreSQL, Oracle, Sql Server, DB2 等;
  • 支援多開發者的協作維護;
  • 日誌檔案支援多種格式,如 XML, YAML, JSON, SQL等;
  • 支援上下文相關邏輯
  • 生成資料庫變更文件
  • 支援多種執行方式,如命令列、Spring 整合、Maven 外掛、Gradle 外掛等。

更多詳情介紹,請查閱 Liquibase 官方文件

Spring Boot 整合 Liquibase

新增依賴

因為 Spring Boot 已經內建支援整合 Liquibase,我們只需要在專案工程中引入 Liquibase 的依賴進行配置即可。

<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
    <version>3.6.3</version>
</dependency>
複製程式碼

配置 application 檔案

在 classpath 中配置 application.properties 或 application.yml 檔案,

# 啟用liquibase
liquibase.enabled=true
# 儲存變化的檔案(changelog)位置
liquibase.change-log=classpath:sample_change.sql
# 檢查儲存變化的檔案是否存在
liquibase.check-change-log-location=true
# 分環境執行,若在 changelog 檔案中設定了對應 context 屬性,則只會執行與 dev 對應值的 changeset
liquibase.contexts=dev
# 執行前首先刪除資料庫,預設 false。若設定為 true,則執行變更前,會先刪除目標資料庫,請謹慎
liquibase.dropFirst=false
# 執行更新時將回滾 SQL 寫入的檔案路徑
liquibase.rollback-file=
# 如果使用工程已配置的 datasource 資料來源,則以下三個資料庫連線引數可不配置
## 訪問資料庫的連線地址
liquibase.url=jdbc:mysql://10.10.4.41:3306/test?useUnicode=true&characterEncoding=utf-8
# 訪問資料庫的使用者名稱
liquibase.user=test
# 訪問資料庫的密碼
liquibase.password=test

複製程式碼

注意:如果使用工程已配置的 datasource 資料來源,則 liquibase.url、liquibase.userliquibase.password這個三個引數可不配置,目標資料來源即為工程已配置好的資料來源。因為 Spring 會自動為 liquibase 獲取可用的資料來源,詳情可檢視這個類:org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration

編寫儲存變更的 changelog 檔案

目前 Liquibase 支援 XML、YAML、JSON 和 SQL 格式四種格式的 changelog 檔案。為了方便和直觀,下面以基於 SQL 格式編寫 changelog 檔案:sample_change.sql

--liquibase formatted sql

--changeset zhouyi:1
-- 建立使用者表
CREATE TABLE `user` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `age` int(10) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--rollback drop table user;

--changeset zhouyi:2
insert into user(id, name, age) values(1,'張三',29);
insert into user(id, name, age) values(2,'李四',20);
複製程式碼

啟動 Spring Boot 應用後,可以從日誌看到輸出:

2019-03-27 13:13:16.439 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - DELETE FROM test.DATABASECHANGELOGLOCK
2019-03-27 13:13:16.442 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - INSERT INTO test.DATABASECHANGELOGLOCK (ID, `LOCKED`) VALUES (1, 0)
2019-03-27 13:13:16.506 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - SELECT `LOCKED` FROM test.DATABASECHANGELOGLOCK WHERE ID=1
2019-03-27 13:13:16.586 [main] [] INFO  liquibase.lockservice.StandardLockService.info:42 - Successfully acquired change log lock
2019-03-27 13:13:16.697 [main] [] INFO  liquibase.changelog.StandardChangeLogHistoryService.info:42 - Creating database history table with name: test.DATABASECHANGELOG
2019-03-27 13:13:16.700 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - CREATE TABLE test.DATABASECHANGELOG (ID VARCHAR(255) NOT NULL, AUTHOR VARCHAR(255) NOT NULL, FILENAME VARCHAR(255) NOT NULL, DATEEXECUTED datetime NOT NULL, ORDEREXECUTED INT NOT NULL, EXECTYPE VARCHAR(10) NOT NULL, MD5SUM VARCHAR(35) NULL, `DESCRIPTION` VARCHAR(255) NULL, COMMENTS VARCHAR(255) NULL, TAG VARCHAR(255) NULL, LIQUIBASE VARCHAR(20) NULL, CONTEXTS VARCHAR(255) NULL, LABELS VARCHAR(255) NULL, DEPLOYMENT_ID VARCHAR(10) NULL)
2019-03-27 13:13:17.570 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - SELECT COUNT(*) FROM test.DATABASECHANGELOG
2019-03-27 13:13:17.575 [main] [] INFO  liquibase.changelog.StandardChangeLogHistoryService.info:42 - Reading from test.DATABASECHANGELOG
2019-03-27 13:13:17.579 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - SELECT * FROM test.DATABASECHANGELOG ORDER BY DATEEXECUTED ASC, ORDEREXECUTED ASC
2019-03-27 13:13:17.585 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - SELECT COUNT(*) FROM test.DATABASECHANGELOGLOCK
2019-03-27 13:13:17.678 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - CREATE TABLE `user` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `age` int(10) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
2019-03-27 13:13:17.935 [main] [] INFO  liquibase.changelog.ChangeSet.info:42 - Custom SQL executed
2019-03-27 13:13:17.937 [main] [] INFO  liquibase.changelog.ChangeSet.info:42 - ChangeSet classpath:sample_change.sql::1::zhouyi ran successfully in 297ms
2019-03-27 13:13:17.938 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - SELECT MAX(ORDEREXECUTED) FROM test.DATABASECHANGELOG

複製程式碼

從資料庫中可以看到變化,首次執行新增了 3 張表,

資料庫變更

其中DATABASECHANGELOGDATABASECHANGELOGLOCK 表是 Liquibase 自動生成的,而 user 表是執行我們編寫的 changeset變更集後生成的,並且也已經執行了第 2 個變更集,插入了兩條資料。

SQL 格式的 changelogs 檔案

SQL 格式的 changelog 檔案使用 SQL 註釋作為後設資料,為確保這個檔案被 Liquibase 識別為 changelog檔案,,SQL 檔案必須以以下注釋開頭:

--liquibase formatted sql
複製程式碼

changeset 變更

SQL 格式的 changelog 檔案中在變更的 SQL 前需要加上以下注釋,表示為一個 changeset變更集:

--changeset author:id attribute1:value1 attribute2:value2 [...]
複製程式碼

在之前的 sample_change.sql 檔案中編寫了兩條 changeset (變更集),

變更集 changeset 是通過 author + id 的方式來保證唯一性,如以上 zhouyi:1zhouyi:2 兩條表更集。

變更集提供以下屬性:

屬性 說明
stripComments 設定為 true 可在執行之前刪除 SQL 中的任何註釋, 否則為 false。如果未設定, 則預設值為 true
splitStatements
endDelimiter 應用於語句結尾的分隔符。預設為“;”,也可以設定為“”
runAlways 在每次執行時執行變更集, 即使之前已執行
runOnChange 在首次看到更改並每次更改變更集時執行更改
context 如果在執行時傳遞了特定上下文, 則執行更改。任何字串都可以用於上下文名稱, 並且大小寫不敏感。
logicalFilePath 用於在建立變更集的唯一識別符號時重寫檔名和路徑。移動或重新命名更改日誌時所必需。
labels 標籤是對變更集進行分類的通用方法集類似上下文, 但工作方式正好相反。如果不是在執行時定義一組上下文, 然後在變更集中定義一個匹配表示式, 而是在上下文中定義一組標籤, 在執行時定義一個匹配表示式。
runInTransaction 變更集是否應作為單個事務執行 (如果可能),預設值為 true。請注意此屬性,如果設定為 false, 並且通過執行包含多個語句的變更集部分發生錯誤, 則 liquibase 資料庫的 databasechangeloglock 表將處於無效狀態
failOnError 如果在執行變更集時發生錯誤, 遷移是否應返回失敗
dbms 要用於該變更集的資料庫的型別。當遷移步驟執行時, 它將根據此屬性檢查資料庫型別,如:oracle、mysql
logicalFilePath 在資料庫 databasechangeloglock 中設定邏輯檔案路徑, 而不是在執行 liquibase 的 sql 物理檔案位置。

前置條件

可以為每個變更集指定先決條件。目前, 僅支援 SQL 檢查前置條件。

--preconditions onFail:HALT onError:HALT
--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM my_table
複製程式碼

回滾操作

變更集可能包括回滾變更集時要應用的語句。回滾宣告也是使用表註釋。

--rollback SQL STATEMENT
複製程式碼

例如在前面編寫的 changelog 檔案中的第一個 changeset:

--changeset zhouyi:1
-- 建立使用者表
CREATE TABLE `user` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `age` int(10) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--rollback drop table user;
複製程式碼

首先的變更的 SQL 含義是新建一個 user 表,回滾的 SQL 則表示刪除新建的 user 表。

Liquibase 最佳實踐

下面介紹可應用於專案的一些最佳實踐。

一個變更集只設定一次更改

儘可能地避免對一個變更集進行多次更改,以避免自動提交 SQL 語句而可能使資料庫處於非預期狀態。 如 --changeset zhouyi:1 變更集,只新建一張 user 表,後面不再修改該變更集,如果需要變更,可以新增一條變更集。

變更集的 ID

選擇適合您的方法。有的人是使用從 1 開始的序列號, 並且在更改日誌中是唯一的,也有些人選擇一個描述性的名稱(例如:new-address-table

總是考慮回滾

儘量嘗試以可以回滾的方式編寫變更集,如 --changeset zhouyi:1 變更集新建一個 user 表,在後面跟上回滾的 SQL, --rollback drop table user;

為變更集新增註釋

儘量為每一個變更集條目增加註釋,如 -- 建立使用者表

開發人員使用流程

  • 使用您最喜歡的 IDE 或編輯器, 建立一個包含更改的新本地更改集;
  • 執行 Liquibase 以執行新的變更集 (這將測試 SQL 程式碼);
  • 在應用程式程式碼中執行相應的更改 (例如, Java 程式碼);
  • 測試驗證新的應用程式程式碼以及資料庫更改;
  • 提交變更集和應用程式程式碼。

相關文章