使用 Liquibase 管理資料庫版本 – SpringBoot 2.7 .2 實戰基礎

優雅哥 SpringBoot 2.7 .2 實戰基礎 – 05 -使用 Liquibase 管理資料庫版本

在企業開發中,資料庫版本管理好像是一個偽命題,大多項目都是通過 Power Designer 之類的工具建模、生成 SQL 語句,然後去資料庫中執行。在開發過程中如果遇到修改表結構,再補充修改表結構的語句,大家依次去執行,在本地及各個環境中同步表結構。但這種模式,在我參與過的項目中或多或少都出現過問題:忘記同步表結構,導致在服務啟動或運行時出錯。

1 Liquibase 介紹

SpringBoot 官方文檔中推薦了兩款工具來管理資料庫版本:FlywayLiquibase。前者我沒有在項目中使用過,所以本文就只討論 Liquibase。

使用 Liquibase 需要定義一堆 XML 文件,這些 XML 稱為 changelog 文件。每個 changelog 文件中又包含多個變化集合 changeSet,每個 changeSet 記錄了作者、改變的內容。changeSet 中要修改的內容,通過 createTableaddColumn 等標籤進行操作。通過這種 XML 文件的方式,就可以將程式碼版本與資料庫版本關聯在一起。項目啟動,會自動執行 changelog XML 文件。Liquibase 具有執行鎖,已經執行過的內容不會重複執行。在執行 changeSet 時,由於改動的內容可以通過 Liquibase 提供的標籤編寫,所以無關具體的資料庫產品(MySQL、Oracle 等),Liquibase 底層會根據實際使用的資料庫類型轉化為對應的 SQL。

通過上面的描述,可以看出 Liquibase 帶來的幾個好處:

  1. 支援多類型的資料庫產品,無需維護 SQL 腳本;
  2. 項目啟動可以自動升級資料庫;
  3. 程式碼版本與資料庫版本關聯在一起。

2 在老項目中使用 Liquibase

在咱們的 demo hero-springboot-demo 中,之前已經手動通過 SQL 語句創建了資料庫表 computer,現在想通過 Liquibase 來管理資料庫版本和維護表結構,該怎麼辦呢?本節就通過這個案例來說明已存在的老項目中如何引用 Liquibase。

2.1 配置 Maven 插件

Liquibase 提供了 Maven 插件,使用該插件可以根據資料庫逆向生成 changlog 文件。在 pom.xml 的 plugins 下添加 Liquibase 插件:

<plugin>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-maven-plugin</artifactId>
    <version>4.9.1</version>
    <configuration>
        <propertyFileWillOverride>true</propertyFileWillOverride>
        <outputChangeLogFile>temp/temp-changelog.xml</outputChangeLogFile>
        <driver>com.mysql.cj.jdbc.Driver</driver>
        <url>jdbc:mysql://127.0.0.1:3306/hero_springboot_demo?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true</url>
        <username>root</username>
        <password>Mysql.123</password>
        <outputFileEncoding>UTF-8</outputFileEncoding>
        <verbose>true</verbose>
        <diffTypes>tables, views, columns, indexs,foreignkeys, primarykeys, uniqueconstraints, data</diffTypes>
    </configuration>
</plugin>

上面配置 Liquibase 的 Maven 插件:資料庫連接資訊和Liquibase生成規則配置。生成的文件路徑為 temp 目錄下的 temp-changelog.xml,Liquibase 不會自動生成文件夾,需要手動在項目根目錄下創建 temp 目錄。

2.2 逆向生成 changelog

在控制台執行 mvn liquibase:generateChangeLog 或者在介面上執行 generateChangeLog:

image-20220801160733941

執行後查看 temp/temp-changelog.xml 文件:

image-20220801163132937

生成的這個文件就是 changelog 文件,該文件中包括兩個 changeSet:第一個是創建表結構;第二個初始化數據。

在第一個 changeSet 的 createTable 中,對 id 欄位配置了自增屬性 autoIncrement:

<column autoIncrement="true" name="id" type="BIGINT">
    <constraints nullable="false" primaryKey="true"/>
</column>

不知道為啥,我這不生效。要使該欄位自增,要用 addAutoIncrement 標籤。第一個 changeSet 需要添加上該標籤,添加後如下:

<changeSet id="T100-20220801-yyg-001" author="yyg">
    <createTable remarks="電腦" tableName="computer">
        <column autoIncrement="true" name="id" type="BIGINT">
            <constraints nullable="false" primaryKey="true"/>
        </column>
        <column name="size" remarks="尺寸" type="DECIMAL(4, 1)"/>
        <column name="operation" remarks="作業系統" type="VARCHAR(32)"/>
        <column name="year" remarks="年份" type="VARCHAR(4)"/>
    </createTable>
    <addAutoIncrement tableName="computer" columnName="id" columnDataType="BIGINT"/>
</changeSet>

獲取到歷史表結構及數據的 changelog 文件後,接下來的步驟與 SpringBoot 整合 Liquibase 一致。

3 在 SpringBoot 中使用 Liquibase

3.1 添加依賴

pom.xml 文件中添加 liquibase 依賴 liquibase-core,該依賴版本號在 spring-boot-dependencies中已定義,直接添加依賴即可。

<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
</dependency>

3.2 添加 changelog

src/main/resources 創建目錄 dbdb 目錄用來存放 Liquibase 相關的 changelog 文件。

db 目錄下還可以按模組創建其他目錄,由於我們這裡只有一個 computer 類,屬於 demo 演示,故就在 db 目錄下創建子目錄 demodb/demo/ 目錄就存放 demo 演示的所有 changelog 配置文件。將前面 Liquibase 逆向生成的 temp-changelog.xml 文件移動到 db/demo/ 目錄下,並重命名為 demo-changelog-v1.xml

db 目錄下創建 changelog-master.xml 文件,該文件為主配置文件,作用就是引入所有模組的 changelog 文件:

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
                   xmlns="//www.liquibase.org/xml/ns/dbchangelog"
                   xsi:schemaLocation="//www.liquibase.org/xml/ns/dbchangelog
                        //www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">

    <includeAll path="demo" relativeToChangelogFile="true"/>
</databaseChangeLog>

除了根節點,裡面就一句話,表示將當前路徑下 demo 目錄中的 changelog 文件都引入。假設 db 目錄下還有其他模組(目錄),繼續通過 <includeAll> 元素引入即可。

src/main/resources 中文件目錄結構如下:

src/main/resources/
|- db/
	|- demo/
		|- demo-changelog-v1.xml
	|- changelog-master.xml

3.3 changeSet

回過頭來看 demo-changelog-v1.xml 文件,裡面的內容是之前生成的,前面講過包括兩個 changeSetchangeSet 用來定義對資料庫的表更操作,包括:

表結構的操作:創建表、刪除表、修改表結構(添加列、刪除列等)

表數據的操作:表中數據的增刪改查

視圖的操作、索引的操作等

幾乎只要是對資料庫的操作,都可以寫在 changeSet 中。當服務啟動的時候,會自動執行主配置文件中包含的所有 changeSet。需要特別注意:changeSet 一經執行,就不能修改!! 雖然 changeSet 元素有一個屬性 runOnChange ,非常不建議亂用。如果要修改 changeSet裡面的內容怎麼辦呢?重新寫一個 changeSet,在裡面編寫要修改的內容。例如在第一個 changeSet 中使用 <createTable> 創建表,表中有一個列名為 field,在該 changeSet 執行後(成功啟動過服務),想將該列列名 field 修改為 f,這時候不能直接修改第一個 changeSet,而是要寫第二個 changeSet,通過 <renameColumn> 來修改列名,然後重啟服務。

changeSet元素有兩個必填的屬性 authoridauthor 表示作者,當前的是誰定義的這個changeSet,就填誰的名字,這樣便於追溯。 id 要求唯一,我在項目開發中,id一般按照這個規則:[任務ID]-[日期]-[作者]-[序號],如 T100-20220801-yyg-001

通常小版本更新(如欄位級別的變更),就在同一個文件中追加新的 changeSet 即可。一個 changelog 文件可以包括多個 changeSet,每個 changeSet 中可以包括多個語句,如多個 createTable、insert 等。

大版本更新,就重新編寫 changeLog 文件,如 demo-changelog-v2.xml

按照上面所講,我們修改一下 demo-changelog-v1.xml 中的兩個 changeSet 的 id 和 author:

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
                   xmlns="//www.liquibase.org/xml/ns/dbchangelog"
                   xsi:schemaLocation="//www.liquibase.org/xml/ns/dbchangelog
                    //www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
    <!--
    1. id使用[任務ID]-[日期]-[作者]-[序號],如 T100-20220801-yyg-001
    2. 必須填寫author
    3. 所有 表、列 必須加remarks進行注釋
    4. 已經執行過的ChangeSet嚴禁修改
    -->
    <changeSet id="T100-20220801-yyg-001" author="yyg">
        <createTable remarks="電腦" tableName="computer">
				...
				</createTable>
    </changeSet>
    <changeSet id="T100-20220801-yyg-002" author="yyg">
        ...
    </changeSet>
</databaseChangeLog>

3.4 添加配置

在 application.yml 中添加 liquibase 的配置:

spring:
  liquibase:
    enabled: true
    drop-first: false
    change-log: classpath:/db/changelog-master.xml

上述配置開啟 liquibase,並指定主文件的路徑。

3.5 啟動服務測試

現在啟動服務,會發現服務啟動失敗,而且控制台會出現如下提示:

Reason: liquibase.exception.DatabaseException: Table 'computer' already exists

此時需要刪除資料庫中的表,然後重新啟動服務。

服務啟動成功後,會自動創建兩張 liquibase 相關的表:DATABASECHANGELOGDATABASECHANGELOGLOCK表。

現在嘗試在 demo-changelog-v1.xml中編寫第三個 changeSet:

<changeSet id="T100-20220801-yyg-003" author="yyg">
    <addColumn tableName="computer">
        <column name="color" type="VARCHAR(8)" defaultValue="red" remarks="顏色"/>
    </addColumn>
</changeSet>

這個 changeSet 為 computer 表新增列 color,默認值為 red。重啟服務,服務啟動後查看 computer 表:

  1. 已新增列 color
  2. color 默認值已設置為 red

image-20220801225227371

這樣便成功在 SpringBoot 中使用 liquibase。

4 常見問題

4.1 Waiting for changelog lock

如果啟動服務時,控制台提示如下資訊:

Liquibase - Waiting for changelog lock
Waiting for changelog lock....

通常是由於 Liquibase 在重構資料庫時使資料庫死鎖。解決方法如下:

1 查看鎖住資料庫的id:

SELECT * FROM DATABASECHANGELOGLOCK where LOCKED = true;

2 解鎖:

UPDATE DATABASECHANGELOGLOCK
SET locked=0, lockgranted=null, lockedby=null
WHERE id={id}

{id} 為第一步中查詢出來對應記錄的id。

4.2 主鍵自增無效

前面已經談到,需要配置 addAutoIncrement 標籤

<addAutoIncrement tableName="xxx" columnName="xx" columnDataType="BIGINT"/>

4.3 默認值無效

與主鍵自增無效類似,為 column 設置默認值 defaultValuedefaultValueNumeric也無效。

設置默認值需要使用標籤 addDefaultValue

<addDefaultValue tableName="xxx" columnName="xx" defaultValueNumeric="0"/>

Liquibase 還有很多強大的功能,就留給大家在使用過程中一步一步探索吧。

image

今日程式設計師優雅哥(/ youyacoder;[email protected])學習到此結束~~~