Liquibase-資料庫腳本版本管理控制

1. 簡介

Liquibase是一個用於跟蹤、管理和應用資料庫變化的開源的資料庫重構工具。它將所有資料庫的變化(包括結構和數據)都保存在XML文件中,便於版本控制。

Liquibase使參與應用程式發布過程的任何人都可以輕鬆地:

  • 不依賴於特定的資料庫,Liquibase會自動適配目標資料庫進行腳本初始化,目前支援至少30種主流資料庫。
  • 提供資料庫比較功能,比較結果保存在XML中,基於該XML可以用Liquibase輕鬆部署或升級資料庫。
  • 以XML記錄/存儲資料庫變化,其中以authorid唯一標識一個變化(ChangSet),支援資料庫變化的合併,因此支援多開發人員同時工作。
  • 在資料庫中保存資料庫修改歷史(DatabaseChangeHistory),在資料庫升級時自動跳過已應用的變化(ChangSet)。
  • 提供變化應用的回滾功能,可按時間、數量或標籤(tag)回滾已應用的變化。通過這種方式,開發人員可輕易的還原資料庫在任何時間點的狀態。
  • 可生成資料庫修改文檔(HTML格式)
  • 提供數據重構的獨立的IDE和Eclipse插件
  • 將所有變化(包括結構和數據)存在XML文件中,便於版本控制的工具
    springboot支援,只需要導入依賴。
    application.yml配置(可選)
    不配置,默認去resource/db/changelog下找db.changelog-mastert.yml文件

2. Quick Start

使用步驟

  • step1: 創建一個資料庫變更日誌(change log)文件。
  • step2: 在變更日誌文件內部創建一個變更集(change set)。
  • step3: 通過命令行或構建腳本對資料庫進行變更集。
  • step4: 檢驗資料庫中的變更

面向spring開發😉,通過springboot 整合 liquibase 了解其作用。

2.1 添加maven依賴

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.9.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.liquibase</groupId>
        <artifactId>liquibase-core</artifactId>
        <version>4.8.0</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

2.2 application.yaml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
    username: root
    password: 123456
  liquibase:
    # 指定配置文件路徑
    change-log: classpath:db/db.changelog-master.xml
    # 覆蓋本地 ddl dml
    drop-first: true
    # 是否啟用
    enabled: true
    # 記錄版本日誌表
    database-change-log-table: databasechangelog
    # 記錄版本改變lock表
    database-change-log-lock-table: databasechangeloglock

2.3 添加 liquibase xml

db.changelog-master.xml

liquibase 配置文件入口,主要用來引用其他的changelog.xml,如下配置文件中的include,當然也可以使用includeAll來引用整個目錄

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
        xmlns="//www.liquibase.org/xml/ns/dbchangelog"
        xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="//www.liquibase.org/xml/ns/dbchangelog
            //www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd">
    <include file="classpath:db/changelog/changelog-init-0.0.1.xml"/>
</databaseChangeLog>

changelog-init-0.0.1.xml

主要記錄了ddl的變化資訊,比如 如下配置文件中創建了兩個表roleuser

<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="//www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="//www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:pro="//www.liquibase.org/xml/ns/pro" xmlns:xsi="//www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="//www.liquibase.org/xml/ns/dbchangelog-ext //www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd //www.liquibase.org/xml/ns/pro //www.liquibase.org/xml/ns/pro/liquibase-pro-4.6.xsd //www.liquibase.org/xml/ns/dbchangelog //www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.6.xsd">
    <changeSet author="ludangxin" id="1662615627445-2">
        <createTable tableName="role" remarks="角色資訊表">
            <column name="name" remarks="角色名稱" type="VARCHAR(255)"/>
            <column name="role_key" remarks="角色key" type="VARCHAR(255)"/>
        </createTable>
    </changeSet>
    
    <changeSet author="ludangxin" id="1662615627445-3">
        <createTable tableName="user" remarks="用戶資訊表">
            <column name="id" type="INT" remarks="主鍵">
                <constraints nullable="false" primaryKey="true"/>
            </column>
            <column name="username" type="VARCHAR(255)" remarks="用戶名稱"/>
            <column name="password" type="VARCHAR(255)" remarks="密碼"/>
            <column name="age" type="INT" remarks="性別"/>
            <column name="sex" type="VARCHAR(255)" remarks="性別"/>
            <column name="role" type="VARCHAR(255)" remarks="角色"/>
            <column name="create_time" type="DATETIME" defaultValueComputed="NOW()" remarks="創建時間"/>
        </createTable>
    </changeSet>
</databaseChangeLog>

2.4 啟動測試

項目啟動完成後,查看資料庫如下圖,我們在changelog-init-0.0.1.xml文件中定義的腳本初始化到了資料庫中

通過該demo快速的完成了springboot 集成 liquibase,且完成資料庫的初始化。

2.5 測試changeset版本控制

前提是我們將application.yml中的drop-first置為false,因為drop-first=true相當於每次都重置資料庫

此時我們想在user表中新增一個create_by欄位,便直接在之前的changeset中添加了欄位,如下圖所示,然後啟動項目看結果

啟動時控制台報錯資訊如下:

報錯資訊是我們直接修改了changeset後導致md5值與之前的不匹配(直接在之前的changeset中做了修改)

🤔 liquibase如何判斷 是同一changeset的?

authorid唯一標識一個變化(ChangSet)

🤔 liquibase是如何進行changeset版本控制的?

Liquibase會對已經執行的changelog的每一個changeSet的內容進行md5計算,生成的值是databasechanglog表的MD5SUM欄位。

​ 當重新啟動Liquibase時,會對每個changeSet進行md5值計算,與databasechanglog表中的MD5SUM欄位進行對比,如果不一致,說明changeSet值已經被修改,無法啟動成功。

3. DDL操作

3.1 創建表

<changeSet author="ludangxin" id="20220908-1">
    <createTable tableName="order" remarks="訂單資訊表">
        <column name="id" remarks="主鍵" type="BIGINT">
            <constraints nullable="false" primaryKey="true"/>
        </column>
        <column name="order_number" remarks="訂單編號" type="VARCHAR(255)"/>
        <column name="user_id" remarks="用戶id" type="BIGINT"/>
    </createTable>
</changeSet>

3.2 刪除表

<changeSet author="ludangxin" id="20220908-2">
    <dropTable tableName="order"/>
</changeSet>

3.3 修改表

3.3.1 添加欄位

<changeSet author="ludangxin" id="20220908-3">
    <addColumn tableName="order">
        <column name="status" remarks="訂單狀態" type="INT" defaultValue="0"/>
    </addColumn>
</changeSet>

3.3.2 刪除欄位

<changeSet author="ludangxin" id="20220908-4">
    <dropColumn tableName="order" columnName="order_number"/>
</changeSet>

3.3.3 修改欄位

<changeSet author="ludangxin" id="20220908-5">
    <renameColumn tableName="order" oldColumnName="status" newColumnName="state" columnDataType="VARCHAR(10)" remarks="訂單狀態"/>
</changeSet>

修改欄位類型 不建議使用 因為會把欄位的其他資訊搞丟,比如欄位注釋

<changeSet author="ludangxin" id="20220908-7">
    <modifyDataType tableName="order" columnName="state" newDataType="INT" />
</changeSet>

4. DML操作

4.1 數據初始化

項目部署難免會有系統內置的數據,這時我們可以通過使用liquibase進行初始化

新建csv文件user-init-0.0.1.csv

"id","username","password","age","sex","role","create_time","create_by"
"111","張三","222","23","1","admin","2022-09-08 14:22:33","system"
"112","李四","333","26","1","admin","2022-09-08 14:22:33","system"
"113","王五","444","25","1","admin","2022-09-08 14:22:33","system"

使用loadData標籤進行數據的初始化

<changeSet author="ludangxin" id="20220908-8">
    <loadData tableName="user" file="data/user-init-0.0.1.csv" 
              separator="," 
              encoding="UTF-8" 
              relativeToChangelogFile="true"/>
</changeSet>

4.2 新增數據

<changeSet author="ludangxin" id="20220908-6">
    <insert tableName="order">
        <column name="id" valueNumeric="666"/>
        <column name="user_id" value="2222"/>
        <column name="state" value="2"/>
    </insert>
    <insert tableName="order">
        <column name="id" valueNumeric="888"/>
        <column name="user_id" value="3333"/>
        <column name="state" value="1"/>
    </insert>
</changeSet>

4.3 初始化總是變動的數據

使用上述的loadData標籤載入數據,當數據發生變化時,直接修改csv文件進行發布時,會報錯版本不一致。

這時可以使用loadUpdateData標籤進行處理,注意的是changeset上需要加參數runOnChange="true"(當數據發生改變時不去校驗md5)如下

<changeSet author="ludangxin" id="20220908-9" runOnChange="true">
    <loadUpdateData tableName="user" file="data/user-init-0.0.1.csv"
              primaryKey="id"
              separator=","
              encoding="UTF-8"
              relativeToChangelogFile="true"/>
</changeSet>

5. 集成maven

使用maven集成liquibase可以方便的通過liquibase maven plaugin實現很多功能。例如:腳本運行,生成文檔,資料庫差異比較等

5.1 添加 properties

添加properties 文件用來配置liquibase plugin的配置資訊。例如 資料庫的鏈接資訊,配置文件路徑的配置等

liquibase.properties

-- 資料庫連接資訊
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
username=root
password=123456
-- liquibse系統表 表名稱配置
databaseChangeLogTableName=databasechangelog
databaseChangeLogLockTableName=databasechangeloglock
-- 輸出文件路徑配置
outputChangeLogFile=src/main/resources/db/changelog/changelog-output-0.0.1.xml
-- liuquibase xml文件路徑指定
changeLogFile=src/main/resources/db/db.changelog-master.xml

5.2 修改pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="//maven.apache.org/POM/4.0.0"
         xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="//maven.apache.org/POM/4.0.0 //maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    
    <groupId>org.example</groupId>
    <artifactId>liquibase-demo2</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <liquibase.version>4.8.0</liquibase.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-core</artifactId>
            <version>${liquibase.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>
    
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.liquibase</groupId>
                    <artifactId>liquibase-maven-plugin</artifactId>
                    <version>${liquibase.version}</version>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.liquibase</groupId>
                <artifactId>liquibase-maven-plugin</artifactId>
                <configuration>
                    <!--properties文件路徑,該文件記錄了資料庫連接資訊等-->
                 <propertyFile>src/main/resources/db/liquibase.properties</propertyFile>
                    <propertyFileWillOverride>true</propertyFileWillOverride>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

6. plugin-逆向生成xml

通過liquibase maven 插件,從已有的資料庫生成xml配置資訊

通過idea的maven功能,找到 liquibase plugin,雙擊如圖liquibase:generateChangeLog選項,執行完成之後就會在properties文件中配置的outputChangeLogFile路徑生成對應的xml文件,如下圖所示

7. plugin-生成資料庫修改文檔

雙擊liquibase plugin面板中的liquibase:dbDoc選項,會生成資料庫修改文檔,默認會生成到target目錄中,如下圖所示

訪問index.html會展示如下頁面,簡直應有盡有

8. plugin-發布changelog

之前我們對changelog的編輯都需要通過啟動項目來運行changelog,有時候我們可能想不重啟項目便能將修改發布運行到資料庫中

雙擊liquibase plugin面板中的liquibase:update選項,便可以將修改同步到資料庫中

註:這裡有個bug(也可能不是bug,我目前還沒找到對應的解決辦法,如果您有解決方案,🙏),通過plugin發布changelog時,由於我們在db.changelog-masterinclude的是classpath路徑,但是通過plugin發布時會報錯找不到include的xml文件,此時我們可以通過設置相對路徑來解決這個問題,比如<include file="classpath:changelog/changelog-init-0.0.1.xml" relativeToChangelogFile="true"/>,然而這又引發了另外一個問題,plugin和直接啟動springboot項目生成的databasechangelog表中的filename欄位值不同,導致運行changelog時報錯,因為liquibase默認會比較同一filename下的changeset

9. plugin-比較資料庫差異

首先使用liquibase diff 功能前,我們在properties中加入參考的資料庫 test3配置資訊

用於差異比較的資料庫,以此資料庫為準,生成diff xml

# 對比參考的資料庫資訊 (用於plugin-diff)        
referenceUrl=jdbc:mysql://localhost:3306/test3?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
referenceDriver=com.mysql.cj.jdbc.Driver
referenceUsername=root
referencePassword=123456
# 生成的差異比較xml的輸出路徑
diffChangeLogFile=src/main/resources/db/changelog/changelog-diff-0.0.1.xml

我們先觀察下兩個資料庫有什麼樣的差異,再驗證生成的diff xml

兩個資料庫如下,差異都用紅色框起來了。

然後再用liquibase插件看下生成的 diff xml資訊

雙擊liquibase plugin面板中的liquibase:diff選項,如下圖所示

執行完畢後,查看diff xml 內容如下:

生成的xml文件符合預期,將與test3資料庫的差異都生成到了diff xml中