【Liquibase】基础概念 + demo实战-CSDN博客

1 定义

Liquibase是一个用于 用于跟踪、管理和应用数据库变化的开源工具
通过日志文件(changelog)的形式记录数据库的变更(changeset),然后执行日志文件中的修改,将数据库更新或回滚(rollback)到一致的状态。
它的目标是提供一种数据库类型无关的解决方案,通过执行schema类型的文件来达到迁移。

2 背景

在实际上线的应用中,随着版本的迭代,经常会遇到需要变更数据库表和字段,必然会遇到需要对这些变更进行记录和管理,以及回滚等等;
同时只有脚本化且版本可管理,才能在让数据库实现真正的DevOps(自动化执行 + 回滚等)。在这样的场景下Liquibase等工具的出现也就成为了必然。

3 概念

  1. changelog文件:需要定义一堆xml文件,这些xml称为changelog文件。
    1. 项目启动时,会自动执行 changelog xml文件。
    2. 每个文件包含多个变化集合 changeSet,每个changeSet 记录了作者、改变的内容。
    3. 在执行 changeSet 时,由于改动的内容可以通过 Liquibase 提供的标签编写,所以无关具体的数据库产品(MySQL、Oracle 等),Liquibase 底层会根据实际使用的数据库类型转化为对应的 SQL。
      1. 标签:例如 createTable、addColumn……等一些changeSet 中要修改的内容,通过标签进行操作。
  2. 执行锁:Liquibase 具有执行锁,已经执行过的内容不会重复执行。

4 工作流程

SQL 变更记录到 changeset ,多个changeset变更组成了日志文件( changelog ),liquibase将changelog更新日志文件同步到指定的 RDBMS 中。
83f18d42164435d63072f0a7ea1a524a_MD5.png

5 SpringBoot集成liquibase

5.1 maven依赖

 `<!
        <dependency>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-core</artifactId>
            <version>4.9.1</version>
        </dependency>` 



5.2 添加changelog

在 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]]



fee9edb9c3e949eb81be2889eb5f1c15_MD5.png

5.2.1 标签详解

因为 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.
你可以作如下的事情:
  1. 列举多个数据库类型并用逗号分隔
  2. 在数据库类型前加感叹号可以不在指定的数据库中执行
  3. 可以使用关键字 all 和 none |
    | runAlways | 每次运行的时候都执行这个 changeset, 即使之前执行过 |
    | runOnChange | 在第一次的时候会被执行, 然后每次 changeset 有修改的时候会继续执行 |
    | Context | 根据运行时的设置控制 changeset 是否执行. 任何字符串都可以作为 context 的名字, 而且是大小写不敏感的 |
    | Labels | 根据运行时的设置控制 changeset 是否执行. 任何字符串都可以作为 context 的名字, 而且是大小写不敏感的 |
    | runInTransaction | 指定 changeset 是否作为一个单独的事务执行 (如果可能的话). 默认值为 true |
    | failOnError | 如果执行时报错的话这个 migration 是否失败, 默认值为 true |
    | runOrder | 控制 changeset 的执行顺序, 用的也少, 按顺序执行就完了 |
    | created | 存储日期, 版本或者任何字符串的值, 而不是 remarks |
    | ignore | 忽略这个 changeset 的执行 |
    | logical-file-path | 当创建 changesets 的唯一标识符时重写文件名和路径. 当需要移动或者重命名 changelog 时会用到这个值 |
子标签
子标签 解释
comment 指定一个字母-数字格式的标识符, 这个是必须的.
注意, 如果 id 中有 0 的话, 最好是用引号将 id 包裹起来, 比如 “1.10”. 这样的话可以保留 id 中所有的字符
preConditions 指定 changeset 的创建人, 这个是必须的
validCheckSum 给这个 changeset 指定一个有效的 changeset, 而不管数据库里面存储的是什么. 主要使用场景是你要修改已经执行的 changeset 又不想报错时使用.1:any 这个特殊的值将匹配任何的 checksum, 不且 changeset 修改时不会执行
rollback 指定 sql 语句或者 Change Type 如何回滚这个 changeset
  1. file:表示要包含的changelog文件的路径,这个文件可以是LiquiBase支持的任意格式。表示将此路径下目录中的 changelog 文件引入;假设 db 目录下还有其他模块(目录),继续通过 或 元素引入即可。
  2. relativeToChangelogFile:如果为true,则表示file属性表示的文件路径是相对于根changelog而不是CLASSPATH的,默认为false。

注意: 目前没有解决重复引用和循环引用的问题,重复引用还好,LiquiBase在执行的时候可以判断重复,而循环引用会导致无限循环,需要注意!

指定的是changelog的目录,而不是为文件

5.3 添加sql文件

在 src/main/resources/db 创建目录 changelog/sql,用来存放数据库脚本文件;
例如,创建 001_create_person_table.sql 文件

  • 每个sql文件必须以-- liquibase formatted sql注释开头
  • 每个changeset必须以–changeset author:id注释开头
 `insert into departments values(1, '研发部');
insert into departments values(2, '销售部');

insert into departments values(3, '后勤部');

DELETE FROM departments WHERE DEPARTMENT_NAME = '研发部'` 


bb0b3218ebb1cbdd5190eed96c385544_MD5.png

5.4 添加liquibase配置

配置开启 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` 

b0cc6a5b8bac5bcc9dde39214814e556_MD5.png

5.4.1 配置项详解

0330e59189993d289d8562fdf283dc05_MD5.png

5.5 启动服务

5.5.1 自动创建表

  • 服务启动成功后,会自动创建两张 liquibase 相关的表:DATABASECHANGELOG 和 DATABASECHANGELOGLOCK表。分别记录changelog的执行日志和锁日志。
  • LiquiBase在执行changelog中的changeSet时,会首先查看DATABASECHANGELOG表,若是已经执行过,则会跳过(除非changeSet的runAlways属性为true),若是没有执行过,则执行并记录changelog日志;
  • changelog中的一个changeSet对应一个事务,在changeSet执行完后commit,若是出现错误则rollback;
5.5.1.1 DATABASECHANGELOG
  • author和id唯一标识一个变化(ChangSet)
  • Liquibase 使用 databasechangelog 表来跟踪已运行的changeSet。
  • 该表将每个更改设置作为一行进行跟踪,由存储changelog文件的路径的id、author和filename列的组合标识。
  • Liquibase会对已经执行的changelog的每一个changeSet的内容进行md5计算,生成的值是databasechanglog表的MD5SUM字段。当重新启动Liquibase时,会对每个changeSet进行md5值计算,与databasechanglog表中的MD5SUM字段进行对比,如果不一致,说明changeSet值已经被修改,无法启动成功。

a6e4f0764ec62d3feaeb5f607d95906a_MD5.png

`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"` 



5.5.1.2 DATABASECHANGELOGLOCK
  • Liquibase 使用 databasechangeloglock 表确保一次只运行一个 Liquibase 实例。
  • 因为Liquibase 只是从 databasechangelog 表读取以确定需要运行的changeSet,因此,如果同时对同一数据库执行多个 Liquibase实例,则会发生冲突。如果多个开发人员使用相同的数据库实例,或者集群中有多个服务器在启动时自动运行 Liquibase,则可能会发生这种情况。
  • 如果 Liquibase 未干净地退出,则锁住的行可能会保留为锁定状态。可以通过运行UPDATE DATABASECHANGELOGLOCK SET LOCKED=0 清除当前锁

602f6503c32c6a6790305bc80133b91a_MD5.png

`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"` 


5c1b96e727ed2d15bbb9d92f65da13b0_MD5.png

e62aeb381546cc84f1f2d8370985210b_MD5.png

5.6 验证

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

4b70a4f6279fe1240adff6557703b922_MD5.png

6 Java代码实现

方法

`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);
        }
    }` 


7 plugin

该插件可以根据数据库逆向生成 changlog 文件,从已有的数据库生成xml配置信息

  1. 生成xml文件
    1. 通过idea的maven功能,找到 liquibase plugin,双击 liquibase:generateChangeLog 选项,执行完成之后就会在 properties 文件中配置的 outputChangeLogFile 路径生成对应的xml文件
  2. 生成数据库修改文档
    1. 双击liquibase plugin面板中的liquibase:dbDoc选项,会生成数据库修改文档,默认会生成到target目录中
  3. 发布changelog
    1. 之前我们对changelog的编辑都需要通过启动项目来运行changelog,有时候我们可能想不重启项目便能将修改发布运行到数据库中
    2. 双击liquibase plugin面板中的liquibase:update选项,便可以将修改同步到数据库中

b96b00abeaadafe4aece26dafbc3bb13_MD5.png

7.1 配置项

配置数据库连接信息和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>` 

8 管理

根据发布进行管理
  1. 每个发布新建一个文件夹,所有发布相关的 ChangeSet 文件以及数据初始化文件,均放在些文件夹中。
  2. 每个发布新建一个 master.xml。此 master.xml 中,include 本次发布需要执行的 ChangeSet 文件
  3. 根据开发小组独立 ChangeSet文件(可选)
  4. 根据功能独立 ChangeSet 文件。例如 user.xml, company.xml
`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 默认是如何执行以及执行结果。

  1. 在启动时,LiquibaseAutoConfiguration 会根据默认配置初始化 SpringLiquibase
  2. SpringLiquibase.afterPropertiesSet()中执行 ChangeSet 文件
  3. 第一次跑 ChangeSets 的时候,会在数据库中自动创建两个表databasechangelog和databasechangeloglock

因此我们可以认为一个 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
    | | |- ...` 


9 基本规范

  • ChangeSet id 使用[任务ID]-[日期]-[序号],如 T100-20181009-001
  • ChangeSet 必须填写 author
  • Liquibase 禁止对业务数据进行 sql 操作
  • 使用时,禁止包含 schema 名称
  • Liquibase 禁止使用存储过程
  • 所有表,列要加 remarks 进行注释
  • 已经执行过的 ChangeSet 严禁修改。
  • 不要随便升级项目 liquibase 版本,特别是大版本升级。不同版本 ChangeSet MD5SUM 的算法不一样。

10 常见问题

10.1 Waiting for changelog lock

如果启动服务时,控制台提示如下信息:
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。

10.2 change set check sums…

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 有更新则会执行.