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.user
和liquibase.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 張表,
其中DATABASECHANGELOG
和 DATABASECHANGELOGLOCK
表是 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:1
和 zhouyi: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 程式碼);
- 測試驗證新的應用程式程式碼以及資料庫更改;
- 提交變更集和應用程式程式碼。