【Liquibase】基础概念 + demo实战-CSDN博客
在实际上线的应用中,随着版本的迭代,经常会遇到需要变更数据库表和字段,必然会遇到需要对这些变更进行记录和管理,以及回滚等等;
同时只有脚本化且版本可管理,才能在让数据库实现真正的DevOps(自动化执行 + 回滚等)。在这样的场景下Liquibase等工具的出现也就成为了必然。
将 SQL 变更记录到 changeset ,多个changeset变更组成了日志文件( changelog ),liquibase将changelog更新日志文件同步到指定的 RDBMS 中。

`<!
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>4.9.1</version>
</dependency>`
在 src/main/resources 创建目录 db, db 目录用来存放 Liquibase 相关的 changelog 文件。
在 db/changelog 目录下创建 changelog-master.xml 文件,该文件为主配置文件,作用就是引入所有模块的 changelog 文件:
有更新时新增 include 标签和对应的文件即可。
指向changelog或文件夹的顺序是非常重要的。它需要告知Liquibase在运行SQL时的顺序。我们最好先运行changelog(其中包含了“create table(…)”),然后再运行使用该表的编译包。
`<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<include file="/sql/001_create_person_table.sql" relativeToChangelogFile="true"></include>
<changeSet id="T100-20221009-001" author="txg">
<sqlFile path="/db/changelog/sql/002_create_person1_table.sql"></sqlFile>
</changeSet>
</databaseChangeLog>`
![[59ddd7af80665f17c019602d722bf0e2_MD5.png]]

因为 liquibase 使用 DATABASECHANGELOG 这个表, 它会从 changelog 中按照顺序读取所有的 changesets, 对于每一个 changeset, 根据 id/author/filetpath 的组合查询这个 changeset 是否被执行过了。
如果这个 changeset 在数据库中被标记为已经执行了, 那么这个 changeset 就会被跳过, 除非有一个 runAlways 的属性设置为 true, 这个已经执行的 changeset 会被重新执行;changeset 中的所有 change 都执行完成之后,liquibase 将会在 DATABASECHANGELOG 中插入一条新的记录,id/author/filepath 以及这个 changeset 的 MD5Sum。
filepath 是一个路径, 用于定义 changelog-file 这个属性。 即使是同一个文件可以使用不同的路径引用,除非使用了 logical-file-path,否则还是被认为是不同的文件。
liquibase 试图在事务中执行每个 changeset,执行成功则提交, 失败则回滚。有些数据库会自动的提交事务,可能导致意想不到的问题。 因此,最佳实践是每个 changeset 中只有一个变更,除非你想在事务中执行一组非自动提交的变更,比如插入多个数据。
| 属性 | 解释 |
|---|---|
| id | 指定一个字母-数字格式的标识符, 这个是必须的. |
| 注意, 如果 id 中有 0 的话, 最好是用引号将 id 包裹起来, 比如 “1.10”. 这样的话可以保留 id 中所有的字符 | |
| author | 指定 changeset 的创建人, 这个是必须的 |
| dbms | 指定要执行 changeset 的数据库的类型.When the migration step is running, it checks the database type against this attribute. |
| 你可以作如下的事情: |
| 子标签 | 解释 |
|---|---|
| comment | 指定一个字母-数字格式的标识符, 这个是必须的. |
| 注意, 如果 id 中有 0 的话, 最好是用引号将 id 包裹起来, 比如 “1.10”. 这样的话可以保留 id 中所有的字符 | |
| preConditions | 指定 changeset 的创建人, 这个是必须的 |
| validCheckSum | 给这个 changeset 指定一个有效的 changeset, 而不管数据库里面存储的是什么. 主要使用场景是你要修改已经执行的 changeset 又不想报错时使用.1:any 这个特殊的值将匹配任何的 checksum, 不且 changeset 修改时不会执行 |
| rollback | 指定 sql 语句或者 Change Type 如何回滚这个 changeset |
注意: 目前没有解决重复引用和循环引用的问题,重复引用还好,LiquiBase在执行的时候可以判断重复,而循环引用会导致无限循环,需要注意!
指定的是changelog的目录,而不是为文件
在 src/main/resources/db 创建目录 changelog/sql,用来存放数据库脚本文件;
例如,创建 001_create_person_table.sql 文件
`insert into departments values(1, '研发部');
insert into departments values(2, '销售部');
insert into departments values(3, '后勤部');
DELETE FROM departments WHERE DEPARTMENT_NAME = '研发部'`

配置开启 liquibase,并指定主文件的路径
`spring:
datasource:
url: jdbc:oracle:thin:@172.20.0.7:1521:ctsdb
driver-class-name: oracle.jdbc.driver.OracleDriver
username: CTS_DEV
password: CTS_DEV
liquibase:
enabled: true
change-log: classpath:/db/changelog/changelog-master.xml
drop-first: false
server:
port: 8088`



`CREATE TABLE "CTS_DEV"."DATABASECHANGELOG"
( "ID" VARCHAR2(255) NOT NULL ENABLE,
"AUTHOR" VARCHAR2(255) NOT NULL ENABLE,
"FILENAME" VARCHAR2(255) NOT NULL ENABLE,
"DATEEXECUTED" TIMESTAMP (6) NOT NULL ENABLE,
"ORDEREXECUTED" NUMBER(*,0) NOT NULL ENABLE,
"EXECTYPE" VARCHAR2(10) NOT NULL ENABLE,
"MD5SUM" VARCHAR2(35),
"DESCRIPTION" VARCHAR2(255),
"COMMENTS" VARCHAR2(255),
"TAG" VARCHAR2(255),
"LIQUIBASE" VARCHAR2(20),
"CONTEXTS" VARCHAR2(255),
"LABELS" VARCHAR2(255),
"DEPLOYMENT_ID" VARCHAR2(10)
) SEGMENT CREATION IMMEDIATE
PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
NOCOMPRESS LOGGING
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE "USERS"`

`CREATE TABLE "CTS_DEV"."DATABASECHANGELOGLOCK"
( "ID" NUMBER(*,0) NOT NULL ENABLE,
"LOCKED" NUMBER(1,0) NOT NULL ENABLE,
"LOCKGRANTED" TIMESTAMP (6),
"LOCKEDBY" VARCHAR2(255),
CONSTRAINT "PK_DATABASECHANGELOGLOCK" PRIMARY KEY ("ID")
USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE "USERS" ENABLE
) SEGMENT CREATION IMMEDIATE
PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
NOCOMPRESS LOGGING
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE "USERS"`


成功执行sql文件里的脚本:

`public static void main(String[] args) {
String dbUrl = "jdbc:oracle:thin:@172.20.0.7:1521:ctsdb";
String changeLogFile = "classpath:/db/changelog/changelog-master1.xml";
String dbUsername = "CTS_DEV";
String dbPassWord = "CTS_DEV";
liquiBaseStart(dbUrl,dbUsername,dbPassWord,changeLogFile);
}
public void liquiBaseStart(String dbUrl,String dbUsername,String dbPassWord,String changeLogFile) {
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection connection = DriverManager.getConnection(dbUrl, dbUsername, dbPassWord);
JdbcConnection jdbcConnection = new JdbcConnection(connection);
Liquibase liquibase = new Liquibase(changeLogFile, new ClassLoaderResourceAccessor(), jdbcConnection);
liquibase.update("");
connection.close();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
} catch (LiquibaseException e) {
throw new RuntimeException(e);
}
}`
该插件可以根据数据库逆向生成 changlog 文件,从已有的数据库生成xml配置信息

配置数据库连接信息和Liquibase生成规则配置。
`<!
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<propertyFile>src/main/resources/db/liquibase.properties</propertyFile>
<propertyFileWillOverride>true</propertyFileWillOverride>
<outputChangeLogFile>src/main/resources/liquibase/changelog/changelog_init.xml</outputChangeLogFile>
</configuration>
</plugin>`
`esources
|-liquibase
|-user
| |- master.xml
| |- release.1.0.0
| | |- release.xml
| | |- user.xml -- 用户相关表ChangeSet
| | |- user.csv -- 用户初始化数据
| | |- company.xml -- 公司相关表ChangeSet
| |- release.1.1.0
| | |- release.xml
| | |- ...`
首先说明一下 Spring Boot 中 Liquibase 默认是如何执行以及执行结果。
因此我们可以认为一个 SpringLiquibase 执行为一个模块。
引入多模块管理时,基于上节文件管理规范,我们基于模块管理再做下调整。
`resources
|-liquibase
|-user
| |- master.xml
| |- release.1.0.0
| | |- release.xml
| | |- user.xml -- 用户相关表ChangeSet
| | |- user.csv -- 用户初始化数据
| | |- company.xml -- 公司相关表ChangeSet
| |- release.1.1.0
| | |- release.xml
| | |- ...
|- order
| |- master.xml
| |- release.1.0.0
| | |- ...`
如果启动服务时,控制台提示如下信息:
Liquibase - Waiting for changelog lockWaiting for changelog lock....
通常是由于 Liquibase 在重构数据库时使数据库死锁。解决方法如下:
1 查看锁住数据库的id:
SELECT * FROM DATABASECHANGELOGLOCK where LOCKED = true;
2 解锁:
UPDATE DATABASECHANGELOGLOCKSET locked=0, lockgranted=null, lockedby=nullWHERE id={id}
{id} 为第一步中查询出来对应记录的id。
liquibase 在处理 changeset 的时候, 会先计算校验和并将其存储到 DATABASECHANGELOG 这个表中. 在 changeset 运行之后, 如果 changeset 有任何修改 liquibase 都能通过对比校验和感知到.
如果已经运行的 changeset 发生了修改, 会导致校验和变化,liquibase 会退出执行并且报错:
Validation failed: change set check sums <changeset identifer> was: <old checksum> but is now: <newchecksum>.
这是因为 liquibase 不知道你做了哪些变更. 如果确实是 changeset 需要修改, 而你又想忽略这个错误, 有两个选择:
手动修改 DATABASECHANGELOG 中的记录
第一个方法是手动修改 DATABASECHANGELOG 这个表中对应的记录, 可以将其设置为空值. 这个应该在所有的环境都被部署之后进行. 下次你再运行 liquibase 的时候, 将会更新 checksum 到正确的新值.
属性
第二个方法是给 changeset 增加一个 元素. 这个文本将原来报错的 md5 放到这里来.
runOnChange 属性
校验和通常会和 changeSet 的 runOnChange 属性结合使用. 有时候你并不想增加新的 changeset, 因为只需要知道当前版本, 但是你又想每次更新的时候使用.
runOnChange 默认是 false 的. 如果将其设置为 true 的话, 那么只要 changeset 有更新则会执行.