(16) Liquibase,資料庫版本控管工具

Albert Hg
learning-from-jhipster
12 min readDec 30, 2020

本篇文章開始會針對資料庫的部分進行 Table 的操作。將會介紹甚麼是Liquibase,包含 Liquibase 的目的、使用方法、內部機制等內容。

為什麼使用 Liquibase

試想,如果在專案迭帶與更新的過程中,沒有資料庫進行版本控管的機制,那麼在操作資料庫的部分都要手動執行對應專案版號的 SQL Command,這是不是超級災難的一件事情。

Liquibase 是一個用於管理資料庫版本的工具,可以讓你免於手動操作資料庫,可以讓正在執行的專案知道目前所在的資料庫版本是多少,自動的執行後續版本的資料庫異動內容。

另外,Liquibase 支援了多種不同的資料庫,比較常見的 MySQL、MSSQL、PostgreSQL、MariaDB、H2 等資料庫大多都有支援,如果想要查看完整的內容,可以查閱 Liquibase ─ Database Supported

Liquibase 的簡介

簡單來說,我們可以將資料庫的那些 Table Schema 用另外一種形式的方式儲存起來,像是 XMLYAML,就可以將這些文件進行管理,對於 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 來表示,如下:

https://gist.github.com/albert-hg/a0a4fa19650b4edc6c813902789099ae

主要是在 <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/> 的子元素」,那麼我們就來看看他的子元素有哪些吧!(參考)

  1. changeSet
    這個元素最主要是在定義資料庫所要修改的內容標記,當你要對資料庫進行任何修改,例如新增表、刪除表、加入欄位、修改欄位等等相關操作,都會寫在 <changeSet/> 內。而這些要修改的內容,都會透過 liquibase 來執行。(查看範例)
  2. property
    這個元素可以讓你動態的定義一些參數,這些參數可以依照所以用的資料庫的種類進行對應的參數設定,接著就可以在 <databaseChangeLog/> 中使用所定義的參數。(查看範例)
  3. preConditions
    這個元素可以依照所設定的條件,來決定是否使用底下的 <changeSet/> 內容。(查看範例)
  4. include
    在使用 <databaseChangeLog/> 的時候,為了要達到管理資料庫版本的控管,因此一定會將對應的 <databaseChangeLog/> 寫在不同的擋案中。但 lisquibase 在取得這些擋案時,一定都會有一個入口檔案或主要檔案,而這個檔案就會記錄著每一版 <databaseChangeLog/> 的引入位置與歷程。
    通常這個檔案會叫他「master.xml」,此時就可以透過 <include/> 元素來將多份 <databaseChangeLog/> import 進來。(查看範例)

changeSet 的子元素

由於 <changeSet/> 是整個管理資料庫的表的內容,因此這裡特別介紹一下在 <changeSet/> 父元素中有哪些常見的子元素可以使用。(來源)

https://gist.github.com/albert-hg/01b6ab0845e7b06723710e7f249800d2

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 的操作。

--

--

Albert Hg
learning-from-jhipster

I am a programmer but love other things. I am a nobody but keep myself going. I am a person who wishes to reach the heaven but lost the wings.