SpringBoot結合Liquibase實現資料庫變更管理
theme: cyanosis
《從零打造專案》系列文章
工具
ORM框架選型
- SpringBoot專案基礎設施搭建
- SpringBoot整合Mybatis專案實操
- SpringBoot整合Mybatis Plus專案實操
- SpringBoot整合Spring Data JPA專案實操
資料庫變更管理
定時任務框架
快取
安全框架
開發規範
前言
在《SpringBoot專案基礎設施搭建》一文中有提到過 liquibase,以及還自定義了一個 Maven 外掛,可能大家當時看到這塊內容,雖然好奇但不知道該如何使用。本文將帶著大家實操一個 SpringBoot 結合 Liquibase 的專案,看看如何新增資料表、修改表字段、初始化資料等功能,順帶使用一下 Liquibase 模版生成器外掛。
如果對 Liquibase 不瞭解,可以先看一下我的上一篇文章《資料庫變更管理:Liquibase or Flyway》。
實操
本專案包含兩個小專案,一個是 liquibase 模版生成器外掛,專案名叫做 liquibase-changelog-generate,另一個專案是 liquibase 應用,叫做 springboot-liquibase。
Liquibase模版生成器外掛
建立一個 maven 專案 liquibase-changelog-generate,本專案具備生成 xml 和 yaml 兩種格式的 changelog,個人覺得 yaml 格式的 changelog 可讀性更高。
1、匯入依賴
```xml
2、定義一個介面,提前準備好公用程式碼,主要是判斷 changelog id 是否有非法字元,並且生成 changelog name。
```java public interface LiquibaseChangeLog {
default String getChangeLogFileName(String sourceFolderPath) { System.out.println("> Please enter the id of this change:"); Scanner scanner = new Scanner(System.in); String changeId = scanner.nextLine(); if (StrUtil.isBlank(changeId)) { return null; }
String changeIdPattern = "^[a-z][a-z0-9_]*$";
Pattern pattern = Pattern.compile(changeIdPattern);
Matcher matcher = pattern.matcher(changeId);
if (!matcher.find()) {
System.out.println("Change id should match " + changeIdPattern);
return null;
}
if (isExistedChangeId(changeId, sourceFolderPath)) {
System.out.println("Duplicate change id :" + changeId);
return null;
}
Date now = new Date();
String timestamp = DateUtil.format(now, "yyyyMMdd_HHmmss_SSS");
return timestamp + "__" + changeId;
}
default boolean isExistedChangeId(String changeId, String sourceFolderPath) { File file = new File(sourceFolderPath); File[] files = file.listFiles(); if (null == files) { return false; }
for (File f : files) {
if (f.isFile()) {
if (f.getName().contains(changeId)) {
return true;
}
}
}
return false;
} } ```
3、每個 changelog 檔案中的 changeSet 都有一個 author 屬性,用來標註是誰建立的 changelog,目前我的做法是執行終端命令來獲取 git 的 userName,如果有更好的實現,望不吝賜教。
```java public class GitUtil {
public static String getGitUserName() { try { String cmd = "git config user.name"; Process p = Runtime.getRuntime().exec(cmd); InputStream is = p.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); String line = reader.readLine(); p.waitFor(); is.close(); reader.close(); p.destroy(); return line; } catch (IOException | InterruptedException e) { e.printStackTrace(); } return "hresh"; } } ```
4、生成 xml 格式的 changelog
```java @Mojo(name = "generateModelChangeXml", defaultPhase = LifecyclePhase.PACKAGE) public class LiquibaseChangeLogXml extends AbstractMojo implements LiquibaseChangeLog {
// 配置的是本maven外掛的配置,在pom使用configration標籤進行配置 property就是名字, // 在配置裡面的標籤名字。在呼叫該外掛的時候會看到 @Parameter(property = "sourceFolderPath") private String sourceFolderPath;
@Override public void execute() throws MojoExecutionException, MojoFailureException { System.out.println("Create a new empty model changelog in liquibase yaml file."); String userName = GitUtil.getGitUserName();
String changeLogFileName = getChangeLogFileName(sourceFolderPath);
if (StrUtil.isNotBlank(changeLogFileName)) {
generateXmlChangeLog(changeLogFileName, userName);
}
}
private void generateXmlChangeLog(String changeLogFileName, String userName) {
String changeLogFileFullName = changeLogFileName + ".xml";
File file = new File(sourceFolderPath, changeLogFileFullName);
String content = "<?xml version=\"1.1\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
+ "
} ```
5、生成 yaml 格式的 changelog
```java @Mojo(name = "generateModelChangeYaml", defaultPhase = LifecyclePhase.PACKAGE) public class LiquibaseChangeLogYaml extends AbstractMojo implements LiquibaseChangeLog {
// 配置的是本maven外掛的配置,在pom使用configration標籤進行配置 property就是名字, // 在配置裡面的標籤名字。在呼叫該外掛的時候會看到 @Parameter(property = "sourceFolderPath") private String sourceFolderPath;
@Override public void execute() throws MojoExecutionException, MojoFailureException { System.out.println("Create a new empty model changelog in liquibase yaml file."); String userName = GitUtil.getGitUserName();
String changeLogFileName = getChangeLogFileName(sourceFolderPath);
if (StrUtil.isNotBlank(changeLogFileName)) {
generateYamlChangeLog(changeLogFileName, userName);
}
}
private void generateYamlChangeLog(String changeLogFileName, String userName) { String changeLogFileFullName = changeLogFileName + ".yml"; File file = new File(sourceFolderPath, changeLogFileFullName); String content = "databaseChangeLog:\n" + " - changeSet:\n" + " id: " + changeLogFileName + "\n" + " author: " + userName + "\n" + " changes:"; try { FileWriter fw = new FileWriter(file.getAbsoluteFile()); BufferedWriter bw = new BufferedWriter(fw); bw.write(content); bw.close(); fw.close(); } catch (IOException e) { e.printStackTrace(); } }
} ```
6、執行 mvn install 命令,然後會在 maven 的 repository 檔案中生成對應的 jar 包。
專案整體結構如下圖所示:
因為個人感覺 yaml 檔案看起來比較簡潔,所以雖然外掛提供了兩種格式,但後續我選擇 yaml 檔案。
Liquibase專案
本專案只是演示如何通過 Liquibase 新增資料表、修改表字段、初始化資料等功能,並不涉及具體的業務功能,所以程式碼部分會比較少。
1、引入依賴
```xml
2、application.yml 配置如下:
```yaml server: port: 8088
spring: application: name: springboot-liquibase datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mysql_db?serverTimezone=Hongkong&characterEncoding=utf-8&useSSL=false username: root password: root liquibase: enabled: true change-log: classpath:liquibase/master.xml # 記錄版本日誌表 database-change-log-table: databasechangelog # 記錄版本改變lock表 database-change-log-lock-table: databasechangeloglock
mybatis: mapper-locations: classpath:mapper/*Mapper.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl lazy-loading-enabled: true
changeLogFile: src/main/resources/liquibase/master.xml
輸出檔案路徑配置
outputChangeLogFile: src/main/resources/liquibase/out/out.xml
```
3、resources 目錄下建立 Liquibase 相關檔案,主要是 master.xml
```xml
```
還需要建立 liquibase/changelogs 目錄。
4、建立一個啟動類,準備啟動專案
```java @SpringBootApplication public class LiquibaseApplication {
public static void main(String[] args) { SpringApplication.run(LiquibaseApplication.class, args); } } ```
接下來我們就進行測試使用 Liquibase 來進行資料庫變更控制。
建立表
準備通過 Liquibase 來建立資料表,首先點選下面這個命令:
然後在控制檯輸入 create_table_admin,回車,我們可以看到對應的檔案如下:
我們填充上述檔案,將建表字段加進去。
yaml
databaseChangeLog:
- changeSet:
id: 20221124_161016_997__create_table_admin
author: hresh
changes:
- createTable:
tableName: admin
columns:
- column:
name: id
type: ${id}
autoIncrement: true
constraints:
primaryKey: true
nullable: false
- column:
name: name
type: varchar(50)
- column:
name: password
type: varchar(100)
- column:
name: create_time
type: ${time}
關於 Liquibase yaml SQL 格式推薦去官網查詢。
啟動專案後,先來檢視控制檯輸出:
接著去資料庫中看 databasechangelog 表記錄
以及 admin 表結構
新增表字段
使用我們的模版生成器外掛,輸入 add_column_address_in_admin,回車得到一個模版檔案,比如說我們在 admin 表中新增 address 欄位。
yaml
databaseChangeLog:
- changeSet:
id: 20221124_163754_923__add_column_address_in_admin
author: hresh
changes:
- addColumn:
tableName: admin
columns:
- column:
name: address
type: varchar(100)
再次重啟專案,這裡我就不貼控制檯輸出日誌了,直接去資料庫中看 admin 表的變化。
建立索引
輸入 create_index_in_admin,回車得到模版檔案,然後填充內容:
yaml
databaseChangeLog:
- changeSet:
id: 20221124_164641_992__create_index_in_admin
author: hresh
changes:
- createIndex:
tableName: admin
indexName: idx_name
columns:
- column:
name: name
檢視 admin 表變化:
如果要修改索引,一般都是先刪再增,刪除索引可以這樣寫:
yaml
databaseChangeLog:
- changeSet:
id: 20221124_164641_992__create_index_in_admin
author: hresh
changes:
- dropIndex:
tableName: admin
indexName: idx_name
初始化資料
輸入 init_data_in_admin ,修改模版檔案
yaml
databaseChangeLog:
- changeSet:
id: 20221124_165413_348__init_data_in_admin
author: hresh
changes:
- sql:
dbms: mysql
sql: "insert into admin(name,password) values('hresh','1234')"
stripComments: true
重啟專案後,可以發現數據表中多了一條記錄。
關於 Liquibase 還有很多操作沒介紹,等大家實際應用時再去發掘了,這裡就不一一介紹了。
Liquibase 好用是好用,那麼有沒有視覺化的介面呢?答案當然是有的。
plugin-生成資料庫修改文件
雙擊liquibase plugin面板中的liquibase:dbDoc
選項,會生成資料庫修改文件,預設會生成到target
目錄中,如下圖所示
訪問index.html
會展示如下頁面,簡直應有盡有
關於 liquibase 的更多有意思的命令使用,可以花時間再去挖掘一下,這裡就不過多介紹了。
問題
控制檯輸出 liquibase.changelog Reading resource 讀取了很多沒必要的檔案
控制檯截圖如下所示:
我們查詢一個 AbstractChangeLogHistoryService 檔案所在位置,發現它是 liquibase-core 包下的檔案,如下所示:
為什麼會這樣呢?首先來看下我們關於 liquibase 的配置,如下圖所示:
其中 master.xml 檔案內容如下:
```xml
```
從上面可以看出,resource 目錄下關於 liquibase 的資料夾和 liquibase-core 中的一樣,難道是因為重名導致讀取了那些檔案,我們試著修改一下資料夾名稱,將 changelog 改為 changelogs,順便修改 master.xml。
再次重啟專案,發現控制檯就正常輸出了。
簡單去看了下 Liquibase 的執行流程,看看讀取 changelog 時做了哪些事情,最終定位到 liquibase.integration.spring.SpringResourceAccessor 檔案中的 list()方法,原始碼如下:
```java
public SortedSet
searchPath = this.finalizeSearchPath(searchPath);
Resource[] resources = ResourcePatternUtils.getResourcePatternResolver(this.resourceLoader).getResources(searchPath);
SortedSet
for(int var11 = 0; var11 < var10; ++var11) { Resource resource = var9[var11]; boolean isFile = this.resourceIsFile(resource); if (isFile && includeFiles) { returnSet.add(this.getResourcePath(resource)); }
if (!isFile && includeDirectories) {
returnSet.add(this.getResourcePath(resource));
}
}
return returnSet; } ```
其中 searchPath 變數值為 classpath:/liquibase/changelog/*,然後通過 ResourcePatternUtils 讀取檔案時,就把 liquibase-core 包下同路徑的檔案都掃描出來了。如下圖所示:
所以我們的應對措施暫時定為修改 changelog 目錄名為 changelogs。
總結
感興趣的朋友可以去我的 Github 下載相關程式碼,如果對你有所幫助,不妨 Star 一下,謝謝大家支援!
- SpringBoot結合Liquibase實現資料庫變更管理
- Spring Security結合JWT實現認證與授權
- Spring Security入門學習
- JVM系列之:你知道為什麼要有兩個 Survivor嗎?關於卡表技術又有多少了解
- 高考報志願:可惜當年填志願時,沒人告訴我這些...
- Java併發進階之:Java記憶體模型(JMM)詳解
- 工作那麼久,該如何提升程式碼質量
- JVM系列之:你知道Java有多少種記憶體溢位嗎
- JVM系列之:日誌分析工具:GCViewer、VisualVM、GCeasy
- JVM系列之:GC調優基礎以及初識jstat命令
- JVM系列之:關於即時編譯器的其他一些優化手段
- Git實操小課堂
- 2020面試準備之併發進階
- 2020面試準備之併發基礎
- Java併發程式設計學習系列八:單例模式
- 全面學習volatile
- 基於SpringBoot將Json資料匯入到資料庫
- SpringCloud學習之Rest服務
- SpringCloud學習之Eureka
- 使用Kettle動態生成頁碼並實現分頁資料同步