(16) Liquibase,資料庫版本控管工具
本篇文章開始會針對資料庫的部分進行 Table 的操作。將會介紹甚麼是Liquibase,包含 Liquibase 的目的、使用方法、內部機制等內容。
為什麼使用 Liquibase
試想,如果在專案迭帶與更新的過程中,沒有資料庫進行版本控管的機制,那麼在操作資料庫的部分都要手動執行對應專案版號的 SQL Command,這是不是超級災難的一件事情。
Liquibase 是一個用於管理資料庫版本的工具,可以讓你免於手動操作資料庫,可以讓正在執行的專案知道目前所在的資料庫版本是多少,自動的執行後續版本的資料庫異動內容。
另外,Liquibase 支援了多種不同的資料庫,比較常見的 MySQL、MSSQL、PostgreSQL、MariaDB、H2 等資料庫大多都有支援,如果想要查看完整的內容,可以查閱 Liquibase ─ Database Supported 。
Liquibase 的簡介
簡單來說,我們可以將資料庫的那些 Table Schema 用另外一種形式的方式儲存起來,像是 XML 或 YAML,就可以將這些文件進行管理,對於 GIT 或 SVN 等就可以做到專案版本控制的部分。
而另一方面,這些文件中每個要新增或修改的部分都會有一個「Changelog ID」,這會在所在環境中的資料庫,檢查哪些 ID 是已經有執行,而哪些 ID 尚未被執行,接著執行這些尚未被執行的內容,以達到對資料庫部分的版本控管。
導入 Liquibase 後所建立的表
當導入 Liquibase 後,Liquibas 會在資料庫中建立兩張表
1. DATABASECHANGELOG
這張表主要用於資料庫 Table 的版本控管,所有的版本演進都會被記錄在這張表中。這裡稍微簡單整理一下每個欄位所代表的意義(來源),其中,內容所提到的 <changeSet/>
會在後續進行說明:
- id: 在
<changeSet/>
中的 id 屬性值 - author: 在
<changeSet/>
中的 author屬性值 - filename:
<changeSet/>
所在的<databaseChangeLog/>
的檔案路徑 - dateexecuted:
<changeSet/>
的執行日期 - orderexecuted:
<changeSet/>
的執行順序 - exectype:
<changeSet/>
的執行類型 [1] - md5sum:
<changeSet/>
透過 md5 編碼後的結果,用於檢察值行過的內容是否變動 [2] - description: liquibase 依照值行的
<changeSet/>
內容產生可讀的一些描述 - commons: 在
<changeSet/>
中的 common 屬性值 - tag: 在
<changeSet/>
中的<tagDatabase/>
的 tag 屬性值,用於版號標記 [3] - liquibase: 紀錄值行
<changeSet/>
的 liquibase 的版本 - contexts: 在
<changeSet/>
中的 context 屬性值,其指定不同環境的值行內容 [4] - labels: 在
<changeSet/>
中的 labels 屬性值, [5] - deployment_id: 用一個 id 紀錄同一批值行的
<changeSet/>
[1] exectype:可能的值有如下幾個 EXECUTED、FAILED、SKIPPED、RERAN、MARK_RAN。
[2] md5sum:將 <changeSet/>
的以及資訊透過 MD5 的編碼方式,取得一個編碼後的字串,這個字串可以在每次啟動 liquibase 時,檢查那些已經被執行過的內容是不是有被修改 。如果修改已經執行過的 <changeSet/>
,那麼在執行時,就會發現 MD5 編碼後的結果不同。
[3] tag:可以參考 <tagDatabase/>
,簡單來說,在加入的 <changeSet/>
中,可以使用 <tagDatabase/>
元素,用來標記現在這個執行的內容的版本,在未來如果有想要回滾(Rollback)至某一版本的時候,就可以指定要回滾到哪一個版本的 tag。
[4] contexts:可以參考 Contexts ,簡單來說,在加入的 <changeSet/>
中,可以指定 context 的屬性值,那麼就可以在使用 liquibase 的時候,使用 –-context
來指定對應的 context 環境的 <changeSet/>
。
[5] labels:可以參考 Labels ,簡單來說,在加入的 <changeSet/>
中,可以指定 labels 的屬性值,這個 labels 值主要用於紀錄一些類似版號的標籤,你可以使用 --labels
來指定要執行的 <changeSet/>
。
這裡你可能會覺得 contexts 跟 labels 的內容非常相似,如果想要更深入瞭解他們之間的關係與比較,可以推薦你這篇文章 ─ Understanding Contexts vs. Labels。
2. DATABASECHANGELOGLOCK
為了避免多個 Liquibase 對資料庫進行操作,因此 Liquibase 在導入時會先建立這張表,並且依照這張表的「鎖」來決定是否可以透過 Liquibase 對資料庫進行修改。這裡稍微簡單整理一下每個欄位所代表的意義(來源):
- id: 鎖的 id,目前僅允許一把鎖,所以這個 id 目前沒有其他用處
- locked: 鎖的狀態,如果是1,則代表資料表是鎖起來的,不得使用其他 liquibase 操作
- lockgranted: 設定鎖被啟動的日期與時間
- lockedBy: 給予鎖的對象所產生的一些可讀性描述
Liquibase 的基本單位
一個 Liquibase 文件的基本單位為一個 <databaseChangeLog/>
,通常我們會直接叫他「ChangeLog」,他可以是 XML、YAML、JSON 或 SQL 等格式,但最建議使用XML,其次是可讀性較高的YAML,其他就相對不建議使用。這裏我們以 XML 來表示,如下:
主要是在 <databaseChangeLog/>
這個根元素內加入子元素,子元素的部分就是操作資料庫的內容定義,這部分等等會再提到。
其中可以看到許多 xmlns
的屬性,這個在 XML 格式中稱為「namespaces」或「命名空間」,所謂的命名空間會遵循下列的規則:
xmlns:[namespace-prefix]=[namespace-uri]
之所以要有命名空間的原因,是為了要避免「命名衝突」的問題發生,透過命名空間來確保在全域中,元素與對應字串符之間的關係。如果對這個部分不太熟悉的話,也可以參考 w3schools 提供的相關說明。
在 <databaseChangeLog/>
中,還有一個屬性 xsi:schemaLocation
,可以這樣使用這個屬性的原因是因為在先前已經有先宣告過 xmlns:xsi
屬性,若通俗一點的描述,就是在這個屬性內指定對應的URL,則XML可以在所指定的路徑內去找到元素的內容定義。
例如在這裡有指定 http://....../dbchangelog-3.6.xsd
的網址,如果你點進去看這個網址的內容,就可以看到裡頭定義了這個父元素下合法的子元素。例如:
就定義了在 <databaseChangeLog/>
元素中,可以加入子元素 property
,且 property
裡包含了 file、name、value 等可設定的屬性值。
更多關於XML的xmlns屬性設定細節,你也可以參考這一篇文章:
https://blog.csdn.net/weixin_30731287/article/details/96614868
databaseChangeLog 的子元素
上方有提到「dbchangelog-3.6.xsd
定義了 <databaseChangeLog/>
的子元素」,那麼我們就來看看他的子元素有哪些吧!(參考)
- changeSet
這個元素最主要是在定義資料庫所要修改的內容標記,當你要對資料庫進行任何修改,例如新增表、刪除表、加入欄位、修改欄位等等相關操作,都會寫在<changeSet/>
內。而這些要修改的內容,都會透過 liquibase 來執行。(查看範例) - property
這個元素可以讓你動態的定義一些參數,這些參數可以依照所以用的資料庫的種類進行對應的參數設定,接著就可以在<databaseChangeLog/>
中使用所定義的參數。(查看範例) - preConditions
這個元素可以依照所設定的條件,來決定是否使用底下的<changeSet/>
內容。(查看範例) - include
在使用<databaseChangeLog/>
的時候,為了要達到管理資料庫版本的控管,因此一定會將對應的<databaseChangeLog/>
寫在不同的擋案中。但 lisquibase 在取得這些擋案時,一定都會有一個入口檔案或主要檔案,而這個檔案就會記錄著每一版<databaseChangeLog/>
的引入位置與歷程。
通常這個檔案會叫他「master.xml」,此時就可以透過<include/>
元素來將多份<databaseChangeLog/>
import 進來。(查看範例)
changeSet 的子元素
由於 <changeSet/>
是整個管理資料庫的表的內容,因此這裡特別介紹一下在 <changeSet/>
父元素中有哪些常見的子元素可以使用。(來源)
Liquibase 提供了一系列相關操作 SQL 的內容,例如表的新增修改刪除、欄位的新增修改刪除、索引的建立刪除、預存程序的修改、序列號的新增刪除修改。
另外也提供了 SQL 的限制操作,例如在新增欄位時,就可以指定該欄位是否需要檢查所設定的限制、或可以設定 Defualt 值、該欄位是否應為 NotNull 、該欄位是否為 PK、該欄是否為唯一值、以及該欄位是否有 FK 等相關可以設定的限制內容。
Liquibase 管理資料庫版本的機制
在一開始有提到:
這些文件中每個要新增或修改的部分都會有一個「Changelog ID」,這會在所在環境中的資料庫,檢查哪些 ID 是已經有執行,而哪些 ID 尚未被執行,接著執行這些尚未被執行的內容,以達到對資料庫部分的版本控管。
而這個 ChangelogID 則會對應在 Changelog File 裡的 <changeSet/>
元素中,像是:
<changeSet id="00000000000000" author="albert">
......
</changeSet>
可以在資料庫中的 databasechangelog 表內看到這樣的一列資料,而這張表最主要的目的就是可以記錄已經被執行過的 <changeSet/>
:
id: 00000000000000
author: albert
filename: ...
dateexecuted: ...
orderexecuted: ...
exectype:
md5sum: ...
description: ...
commons: ...
tag: ...
liquibase: ...
contexts: ...
labels: ...
deployment_id: ...
其他關於 Liquibase JAR 的使用
Liquibase 除了可以透過 ChangeLog 來完成資料庫版本的控管之外,其實 Liquibase 所提供的 JAR 檔本身也有許多可以使用的功能,例如:
- 透過 liquibase 指令操作 Rollback 與 Update
- 透過 liquibase 指令產生 Javadoc-like 的文件
- 透過 liquibase 指令產生目標資料庫的 ChangeLog
- 透過 liquibase 指令計算 ChangeSet 的 CheckSum
其實還有提供很多更細的功能,你也可以參考 Liquibase ─ Commands 。
文末
這一篇文章先大概介紹 Liquibase 的功能以及目的,並稍微詳細的說明 Liquibase 的運作模式及細節。
下一篇文章將會開始把 Liquibase 導入至我們的 Springboot 專案中,並實際透過 Liquibase 的 ChangeLog 與 ChangeSet 來完成資料庫的 Table Schema 的操作。
前往
上一篇
下一篇
所有文章列表