程序員優(yōu)雅哥學 SpringBoot 2.7 .2 實戰(zhàn)基礎 - 05 -使用 Liquibase 管理數(shù)據(jù)庫版本
在企業(yè)開發(fā)中,數(shù)據(jù)庫版本管理好像是一個偽命題,大多項目都是通過 Power Designer 之類的工具建模、生成 SQL 語句,然后去數(shù)據(jù)庫中執(zhí)行。在開發(fā)過程中如果遇到修改表結構,再補充修改表結構的語句,大家依次去執(zhí)行,在本地及各個環(huán)境中同步表結構。但這種模式,在我參與過的項目中或多或少都出現(xiàn)過問題:忘記同步表結構,導致在服務啟動或運行時出錯。
1 Liquibase 介紹
SpringBoot 官方文檔中推薦了兩款工具來管理數(shù)據(jù)庫版本:Flyway 和 Liquibase。前者我沒有在項目中使用過,所以本文就只討論 Liquibase。
使用 Liquibase 需要定義一堆 XML 文件,這些 XML 稱為 changelog 文件。每個 changelog 文件中又包含多個變化集合 changeSet,每個 changeSet 記錄了作者、改變的內容。changeSet 中要修改的內容,通過 createTable 、addColumn 等標簽進行操作。通過這種 XML 文件的方式,就可以將代碼版本與數(shù)據(jù)庫版本關聯(lián)在一起。項目啟動,會自動執(zhí)行 changelog XML 文件。Liquibase 具有執(zhí)行鎖,已經執(zhí)行過的內容不會重復執(zhí)行。在執(zhí)行 changeSet 時,由于改動的內容可以通過 Liquibase 提供的標簽編寫,所以無關具體的數(shù)據(jù)庫產品(MySQL、Oracle 等),Liquibase 底層會根據(jù)實際使用的數(shù)據(jù)庫類型轉化為對應的 SQL。
通過上面的描述,可以看出 Liquibase 帶來的幾個好處:
- 支持多類型的數(shù)據(jù)庫產品,無需維護 SQL 腳本;
- 項目啟動可以自動升級數(shù)據(jù)庫;
- 代碼版本與數(shù)據(jù)庫版本關聯(lián)在一起。
2 在老項目中使用 Liquibase
在咱們的 demo hero-springboot-demo 中,之前已經手動通過 SQL 語句創(chuàng)建了數(shù)據(jù)庫表 computer,現(xiàn)在想通過 Liquibase 來管理數(shù)據(jù)庫版本和維護表結構,該怎么辦呢?本節(jié)就通過這個案例來說明已存在的老項目中如何引用 Liquibase。
2.1 配置 Maven 插件
Liquibase 提供了 Maven 插件,使用該插件可以根據(jù)數(shù)據(jù)庫逆向生成 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&characterEncoding=utf8&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 插件:數(shù)據(jù)庫連接信息和Liquibase生成規(guī)則配置。生成的文件路徑為 temp 目錄下的 temp-changelog.xml,Liquibase 不會自動生成文件夾,需要手動在項目根目錄下創(chuàng)建 temp 目錄。
2.2 逆向生成 changelog
在控制臺執(zhí)行 mvn liquibase:generateChangeLog 或者在界面上執(zhí)行 generateChangeLog:

執(zhí)行后查看 temp/temp-changelog.xml 文件:

生成的這個文件就是 changelog 文件,該文件中包括兩個 changeSet:第一個是創(chuàng)建表結構;第二個初始化數(shù)據(jù)。
在第一個 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="操作系統(tǒng)" type="VARCHAR(32)"/>
<column name="year" remarks="年份" type="VARCHAR(4)"/>
</createTable>
<addAutoIncrement tableName="computer" columnName="id" columnDataType="BIGINT"/>
</changeSet>
獲取到歷史表結構及數(shù)據(jù)的 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 創(chuàng)建目錄 db, db 目錄用來存放 Liquibase 相關的 changelog 文件。
db 目錄下還可以按模塊創(chuàng)建其他目錄,由于我們這里只有一個 computer 類,屬于 demo 演示,故就在 db 目錄下創(chuàng)建子目錄 demo, db/demo/ 目錄就存放 demo 演示的所有 changelog 配置文件。將前面 Liquibase 逆向生成的 temp-changelog.xml 文件移動到 db/demo/ 目錄下,并重命名為 demo-changelog-v1.xml。
在 db 目錄下創(chuàng)建 changelog-master.xml 文件,該文件為主配置文件,作用就是引入所有模塊的 changelog 文件:
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<includeAll path="demo" relativeToChangelogFile="true"/>
</databaseChangeLog>
除了根節(jié)點,里面就一句話,表示將當前路徑下 demo 目錄中的 changelog 文件都引入。假設 db 目錄下還有其他模塊(目錄),繼續(xù)通過 <includeAll> 元素引入即可。
src/main/resources 中文件目錄結構如下:
src/main/resources/
|- db/
|- demo/
|- demo-changelog-v1.xml
|- changelog-master.xml
3.3 changeSet
回過頭來看 demo-changelog-v1.xml 文件,里面的內容是之前生成的,前面講過包括兩個 changeSet。changeSet 用來定義對數(shù)據(jù)庫的表更操作,包括:
表結構的操作:創(chuàng)建表、刪除表、修改表結構(添加列、刪除列等)
表數(shù)據(jù)的操作:表中數(shù)據(jù)的增刪改查
視圖的操作、索引的操作等
幾乎只要是對數(shù)據(jù)庫的操作,都可以寫在 changeSet 中。當服務啟動的時候,會自動執(zhí)行主配置文件中包含的所有 changeSet。需要特別注意:changeSet 一經執(zhí)行,就不能修改?。?/strong> 雖然 changeSet 元素有一個屬性 runOnChange ,非常不建議亂用。如果要修改 changeSet里面的內容怎么辦呢?重新寫一個 changeSet,在里面編寫要修改的內容。例如在第一個 changeSet 中使用 <createTable> 創(chuàng)建表,表中有一個列名為 field,在該 changeSet 執(zhí)行后(成功啟動過服務),想將該列列名 field 修改為 f,這時候不能直接修改第一個 changeSet,而是要寫第二個 changeSet,通過 <renameColumn> 來修改列名,然后重啟服務。
changeSet元素有兩個必填的屬性 author 和 id。author 表示作者,當前的是誰定義的這個changeSet,就填誰的名字,這樣便于追溯。 id 要求唯一,我在項目開發(fā)中,id一般按照這個規(guī)則:[任務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="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<!--
1. id使用[任務ID]-[日期]-[作者]-[序號],如 T100-20220801-yyg-001
2. 必須填寫author
3. 所有 表、列 必須加remarks進行注釋
4. 已經執(zhí)行過的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 啟動服務測試
現(xiàn)在啟動服務,會發(fā)現(xiàn)服務啟動失敗,而且控制臺會出現(xiàn)如下提示:
Reason: liquibase.exception.DatabaseException: Table 'computer' already exists
此時需要刪除數(shù)據(jù)庫中的表,然后重新啟動服務。
服務啟動成功后,會自動創(chuàng)建兩張 liquibase 相關的表:DATABASECHANGELOG 和 DATABASECHANGELOGLOCK表。
現(xiàn)在嘗試在 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 表:
- 已新增列 color
- color 默認值已設置為 red

這樣便成功在 SpringBoot 中使用 liquibase。
4 常見問題
4.1 Waiting for changelog lock
如果啟動服務時,控制臺提示如下信息:
Liquibase - Waiting for changelog lock
Waiting for changelog lock....
通常是由于 Liquibase 在重構數(shù)據(jù)庫時使數(shù)據(jù)庫死鎖。解決方法如下:
1 查看鎖住數(shù)據(jù)庫的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 設置默認值 defaultValue、defaultValueNumeric也無效。
設置默認值需要使用標簽 addDefaultValue:
<addDefaultValue tableName="xxx" columnName="xx" defaultValueNumeric="0"/>
Liquibase 還有很多強大的功能,就留給大家在使用過程中一步一步探索吧。

今日程序員優(yōu)雅哥(/ youyacoder)學習到此結束~~~