Liquibase 实践指南

1、概述

本文主要介绍了 什么是 Liquibase,以及在 SpringBoot 项目中集成使用 Liquibase 对数据库表进行管理,包括自动创建数据库表、自动初始化数据、更新表结构。

2、认识 Liquibase

官网:Liquibase | Database Refactoring | Liquibase: liquibase.org/ Liquibase 是用于数据库重构、管理、记录变化与回滚的开源工具。 在写代码的时候,我们使用 Git 或 subversion 对代码进行版本控制,在数据库中,我们可以使用 liquibase 对数据库表进行版本控制。

3、Liquibase 特点

  • 支持项目代码多人多分支开发与合并;
  • 支持多种数据库类型:MySQL、PostgreSQL、Oracle、Sql Server、DB2、H2等,更多信息请移步官网: liquibase.org/databases
  • 支持多种变化日志格式:XML、YML、JSON、SQL;
  • 支持自定义上下文执行逻辑,可指定需要运行的上下文;
  • 集群安全的数据库更新;
  • 可生成数据库修改文档(HTML);
  • 使用命令对比两个数据库;
  • 可以使用 build 工具(Command Line、Ant、Maven)运行,也可以嵌入到 Application 中;
  • 可以自动生成数据库 SQL 脚本,供 DBA 重构代码使用;
  • 可以使用离线数据库;

4、Liquibase实践

4.1、环境依赖

  • windows 操作系统 PC/Mac OS
  • Java 1.8 运行环境,liquibase 2.x 依赖 java 1.5+,3.x 依赖 java1.6+
  • Maven 3.0
  • MySQL 5.7
  • liquibase 3.6.3
  • (可选)Navicat for MySQL ,用于查看数据库表结构与数据

4.2、使用命令行体验 Liquibase

下载压缩包

官网下载地址: download.liquibase.org/ liquibase-3.6.3-bin.zip: 我的下载地址



解压文件、下载 JDBC jar 包

将上一步下载好的 liquibase zip 包解压出来。 我电脑上使用的 MySQL ,所以我需要下载 mysql-connector-java 用来连接操作数据库,我这里使用的版本是 8.0.13,下载下来后将这个 jar 包放到 liquibase 根目录下的 lib 文件夹中,使用 liquibase 时,会自动扫描 lib 下的包。

下载地址: mysql-connector-java-8.0.13

创建数据库修改日志文件(database chanagelog file)

liquibase 支持多种格式的日志文件,包括 XML、YML、JSON、SQL,官方推荐使用 xml,个人喜欢更加简洁的 yml 格式。在 liquibase 根目录下创建文件夹 chanagelog ,用来存储修改日志文件。从官网拷贝示例,存储到 chanagelog 目录下,文件名为 master.yml ,文件内容如下:

注意直接从官方文档中考出来的,要修改 databaseChangeLog.preConditions.runningAs.username ,改为自己数据库的用户名,我使用的是 root 。
databaseChangeLog:
  - preConditions:
    - runningAs:
        username: root
  - changeSet:
      id: 1
      author: nvoxland
      changes:
        - createTable:
            tableName: person
            columns:
              - column:
                  name: id
                  type: int
                  autoIncrement: true
                  constraints:
                    primaryKey: true
                    nullable: false
              - column:
                  name: firstname
                  type: varchar(50)
              - column:
                  name: lastname
                  type: varchar(50)
                  constraints:
                    nullable: false
              - column:
                  name: state
                  type: char(2)
  - changeSet:
      id: 2
      author: nvoxland
      changes:
        - addColumn:
            tableName: person
            columns:
              - column:
                  name: username
                  type: varchar(8)
  - changeSet:
      id: 3
      author: nvoxland
      changes:
        - addLookupTable:
            existingTableName: person
            existingColumnName: state
            newTableName: state
            newColumnName: id
            newColumnDataType: char(2)

运行命令

在数据库中新建数据库 test_liquibase ,在 liquibase 根目录打开命令行,执行命令

liquibase.bat --url="jdbc:mysql://localhost:3306/test_liquibase?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8" --changeLogFile="/chanagelog/master.yml" --username=root --password=123456 --driver=com.mysql.cj.jdbc.Driver update
liquibase 命令中必须的必须选项有: –url=数据库连接,其中 serverTimezone=GMT%2B8 参数用来指定时区,在使用高版本 jdbc 时需要,不指定可能会报时区错误 –chanagelog=数据库修改日志文件 –username=数据库用户名 –password=数据库密码 –driver=数据库连接驱动 最后的 update 是 liquibase 命令,必须放到选项的后面。 关于命令的更多内容可以使用 liquibase --help 命令查看帮助信息,也可以到官网查询详细信息: liquibase.org/documenta

执行结果:


检查数据库

查看 test_liquibase 数据库,发现多了 4 张表:


打开 databasechanagelog 表,发现在 master.yml 中的 3 个修改都已经被执行了(表中 EXECTYPE 值为 EXECUTED)。


4.3、SpringBoot 项目集成 Liquibase

SpringBoot 提供了自动装配,大大降低了其它组件的使用难度,在使用 Liquibase 时,可以说非常简单了。

4.3.1、创建项目

创建一个 SpringBoot 项目,添加依赖 web、jpa、liquibase,最终项目 pom.xml 如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>xyz.wqf</groupId>
    <artifactId>liquibasedemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>liquibasedemo</name>
    <description>Demo project for Spring Boot with Liquibase</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--jpa-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--liquibase-->
        <dependency>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-core</artifactId>
        </dependency>
    <!--liquibase-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

4.3.2、添加 changelog 与数据

为了放管理,我们在项目 resources 目录下创建 liquibase 目录用于存放 liquibase 相关的配置文件,在 liquibase 目录下创建 changelog 目录存放所有 changelog 文件,创建 initdata 目录用于存放初始化项目时的数据。


4.3.2.1、changelog

liquibase 目录下的 master.xml 为 liquibase 的入口,通过 include 标签将其它的 changelog 文件引入进来。

  • master.xml

```xml <?xml version="1.0" encoding="UTF-8"?>

<include file="classpath:liquibase/changelog/init_00000000000000.xml" relativeToChangelogFile="false"/>
<include file="classpath:liquibase/changelog/user_20190313171720.xml" relativeToChangelogFile="false"/>

```

  • init_00000000000000.xml

```xml

<property name="now" value="now()" dbms="mysql,h2"/>
  <property name="now" value="current_timestamp" dbms="postgresql"/>
  <property name="now" value="sysdate" dbms="oracle"/>
  <property name="autoIncrement" value="true" dbms="mysql,h2,postgresql,oracle"/>
  <property name="floatType" value="float4" dbms="postgresql, h2"/>
  <property name="floatType" value="float" dbms="mysql, oracle"/>
  <changeSet id="20190313172000" author="wqf31415">
      <createTable tableName="book">
          <column name="id" type="bigint" autoIncrement="${autoIncrement}">
              <constraints nullable="false" primaryKey="true"/>
          </column>
          <column name="title" type="varchar(255)" remarks="书名">
              <constraints nullable="false"/>
          </column>
          <column name="description" type="text" remarks="描述">
              <constraints nullable="true"/>
          </column>
          <column name="price" type="${floatType}" remarks="价格">
              <constraints nullable="true"/>
          </column>
          <column name="add_time" type="timestamp" defaultValue="${now}">
              <constraints nullable="true"/>
          </column>
      </createTable>
  </changeSet>

```

  • user_20190313171720.xml

```xml

<property name="now" value="now()" dbms="mysql,h2"/>
<property name="now" value="current_timestamp" dbms="postgresql"/>
<property name="now" value="sysdate" dbms="oracle"/>
<property name="autoIncrement" value="true" dbms="mysql,h2,postgresql,oracle"/>
<property name="floatType" value="float4" dbms="postgresql, h2"/>
<property name="floatType" value="float" dbms="mysql, oracle"/>
<changeSet id="20190313172800" author="wqf31415">
    <createTable tableName="user">
        <column name="id" autoIncrement="${autoIncrement}" type="bigint">
            <constraints nullable="false" primaryKey="true" />
        </column>
        <column name="name" type="varchar(255)" remarks="姓名">
            <constraints nullable="true"/>
        </column>
        <column name="password" type="varchar(255)" remarks="密码">
            <constraints nullable="true"/>
        </column>
        <column name="age" type="tinyint" remarks="年龄">
            <constraints nullable="true"/>
        </column>
        <column name="birthday" type="date" remarks="生日">
            <constraints nullable="true"/>
        </column>
    </createTable>
<!--导入数据-->
    <loadData tableName="user" file="classpath:liquibase/initdata/user.csv" separator=";" encoding="UTF-8"/>
</changeSet>

```

4.3.2.2、数据

在项目初始化,liquibase 完成建表后,可以导入预先定义好的数据。

  • user.csv
id;name;password;age;birthday
1;zhangsan;abc123;18;2001-03-23
2;lisi;ls1990;27;1990-11-08

4.3.3、修改配置文件

修改项目配置文件,添加数据库相关的配置,指定 liquibase 的 changelog文件。

server:
  port: 8909
spring:
  datasource:
    url: jdbc:mysql://172.16.19.229:3306/liquibase_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    show-sql: true
  application:
    name: LiquibaseDemo
  liquibase:
    change-log: classpath:liquibase/master.xml # 指定 changelog 文件

4.3.4、运行项目

在运行项目前需要提前创建 url 中指定的数据库,我这里需要创建名为 liquibase_demo 的数据库。 运行项目,查看数据库,建表正确且 user 表中数据正确。


5、SpringBoot 配置文件-属性详解

5.1、SpringBoot 配置项

下表是在 SpringBoot 2.1.3.RELEASE 使用 liquibase 的配置项 在 SpringBoot 1.x 版本中 liquibase 配置项少一些,且配置项没有 spring,如 2.x 中的 spring.liquibase.change-log ,在 1.x 中为 liquibase.change-log

| 配置项 | 默认值 | 注释 | | :---------------------------------------------: | :----------------------------------------------: | :----------------------------------------------------------- | | spring.liquibase.change-log | classpath:/db/changelog/db.changelog-master.yaml | changeLogFile 配置路径 | | spring.liquibase.check-change-log-location | true | 是否检查 changelog 配置路径存在 | | spring.liquibase.contexts | | 只有指定的 context 的 changelog 才会被执行,多个 context 之间以逗号分隔 | | spring.liquibase.default-schema | | 默认数据库 | | spring.liquibase.liquibase-schema | | 用于存储 liquibase 对象的数据库 | | spring.liquibase.liquibase-tablespace | | 用于 liquibase 对象的表空间 | | spring.liquibase.database-change-log-table | DATABASECHANGELOG | 存储数据库改变记录执行情况的表名 | | spring.liquibase.database-change-log-lock-table | DATABASECHANGELOGLOCK | 存储当前使用 liquibase 的用户信息表名 | | spring.liquibase.drop-first | false | 是否先删除表 | | spring.liquibase.enabled | true | 是否启用 liquibase | | spring.liquibase.user | | liquibase 使用的数据库用户名,不指定时使用 spring.datasource 中的 | | spring.liquibase.password | | liquibase 使用的数据库用户密码,不指定时使用 spring.datasource 中的 | | spring.liquibase.url | | liquibase 使用的数据库url,不指定时使用 spring.datasource 中的 | | spring.liquibase.labels | | 指定标签的才会运行,多个标签以逗号分隔 | | spring.liquibase.parameters | | changelog 参数 | | spring.liquibase.rollback-file | | 当执行升级时写回滚 SQL 的文件 | | spring.liquibase.test-rollback-on-update | | 执行更新前是否验证回滚 |

6、Liquibase changelog 标签

6.1、ChangeLog 文件

所有 Liquibase 更改的根目录是 databaseChangeLog 文件。

6.1.1、ChangeLog可用属性

logicalFilePath :用于在创建 changeSet 的唯一标识符时覆盖文件名和路径。移动或重命名 change logs 时是必需的。

<?xml version="1.0" encoding="utf-8" ?>
<databaseChangeLog logicalFilePath="" 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">
</databaseChangeLog>

6.1.2、ChangeLog 可用子标签

6.1.2.1、preConditions 前提条件

可以附加 preConditions databaseChangeLog changeSet ,以控制基于数据库状态的更新的执行。

下面是使用 preConditions 的几个原因:

  • 记录 changelog 的作者在创建 changelog 时的假设。
  • 强制运行 changelog 的用户不违反这些假设。
  • 在执行不可恢复的更改(例如dropTable)之前,请执行数据检查。
  • 根据数据库的状态来控制运行哪些 changesets 和不运行哪些 changesets

可用的属性

  • onFail : 当 proConditions 遇到失败的时候如何处理
  • onError :当 proConditions 遇到错误的时候如何处理
  • onUpdateSQL :自版本1.9.5后当 proConditions 遇到更新 SQL 模型的时候如何处理
  • onFailMessage :自2.0起,在 proConditions 失败时要输出的自定义消息。
  • onErrorMessage :在 proConditions 错误时要输出的自定义消息。

onFail或者onError可能的取值

  • HALT :立即停止执行整个 changelog 。默认的值。
  • CONTINUE :跳过 changeset 。将在下次更新时再次尝试执行 changeset 。继续 changelog
  • MARK_RAN :跳过 changeset ,但将其标记为已执行。继续 changelog
  • WARN :输出警告并继续正常执行 changeset / changelog

changset 之外(例如,在 changelog 的开头),只有 HALT WARN 两种数值。

AND/OR/NOT 逻辑

可以使用可嵌套 <and> <or> <not> 标签将条件逻辑应用于 preConditions 。如果未指定条件标签,则默认为 <AND>

可用的 preConditions

<dbms> :

  • 如果针对所执行的数据库与指定的类型匹配,则通过。
  • type :预期的 数据库 类型。可以使用逗号分隔值指定多个 dbms 值。 必填

<runningAs> :

  • 如果执行的数据库用户与指定的用户名匹配,则通过。
  • username :数据库用户脚本应以原样运行。 必填

<columnExists> :

  • 从1.8开始如果数据库中存在具体的列,则通过
  • schemaName :表的 schema 的名称。 必填
  • tableName :列表的名称。 必填
  • columnName :列名称。 必填

<tableExists> :

  • 从1.8开始,如果数据库中存在具体的表,则通过
  • schemaName :表的 schema 的名称。 必填
  • tableName :表的名称。 必填

<viewExists> :

  • 从1.8开始,如果数据库中存在具体的视图,则通过
  • schemaName :视图的 schema 的名称。 必填
  • viewName :视图的名称。 必填

<foreignKeyConstrainExists> :

  • 从1.8开始,如果数据库存在指定的外键,则通过
  • schemaName :外键的 schema 名称, 必填
  • foreignKeyName :外键的名称。 必填

<indexExists> :

  • 从1.8开始,如果数据库存在指定的索引,则通过
  • schemaName :索引的 schema 名称, 必填
  • indexName :索引名称, 必填

<sequenceExists> :

  • 从1.8开始,如果数据库存在指定的序列,则通过
  • schemaName :序列的 schema 名称, 必填
  • sequenceName :序列的名称, 必填

<primaryKeyExists> :

  • 从1.8开始,如果数据库中存在指定的主键,则通过
  • schemaName :主键的 schema 名称
  • primaryKeyName :主键的名称, 表名或者主键名是必填
  • tableName :包含主键的表的名称。从1.9开始 表名或者主键名是必填

<sqlCheck> :

  • 执行 SQL 字符串并检查返回的值。 SQL 必须返回具有单个值的单个行。要检查行数,请使用 SQL 函数 count 。要检查值范围,请在 SQL 中执行检查,并返回一个可以容易比较的值。
<sqlCheck expectedResult="1">
    SELECT COUNT(1) FROM pg_tables WHERE TABLENAME = 'myRequiredTable'
</sqlCheck>
  • expectedResult :这个值与 SQL 的执行结果作比较, 必填

<changeLogPropertyDefined> :

  • 检查是否存在给定的 changelog参数 。如果还给定了值,则仅当该值与给定值不同时,该值才会失败。
  • property :要检验的属性的名称, 必填
  • value :给定属性的必需值。

<customPrecondition> :

  • 可以通过创建实现 liquibase.precondition.CustomPrecondition 接口的类来创建自定义 precondition 。自定义类上的参数通过基于 <param> 子标签的反射进行设置。参数作为字符串传递到自定义 preCondition

xml <customPrecondition className="com.example.CustomTableCheck"> <param name="tableName" value="our_table"/> <param name="count" value="42"/> </customPrecondition>

  • className : custom precondition 类的名称。 必填
  • 子标签
  • param :传递给 custom precondition 的参数
    • param 子标签属性:
    • name :要设置的参数的名称。 必填
    • value :要将参数设置为的字符串值。 必填

具体属性及使用方法请查看 官方文档

6.1.2.2、property

从Liquibase 1.7开始

Liquibase 允许在 changelog 中动态替换参数。使用 ${} 语法描述要替换的参数。

配置参数的值

参数的值会被按照下面的顺序进行查找

  • 作为参数传递给您的 Liquibase 运行程序(请参阅 Ant command 等文档,了解如何传递它们) ant和command没有了解
  • 作为 JVM 系统属性
  • 在数据库 ChangeLog 文件本身的参数块( <property> 标签)中
  • 作为环境变量
<databaseChangeLog
        xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
        xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog     http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd
        http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
    <property name="clob.type" value="clob" dbms="oracle"/>
    <property name="clob.type" value="longtext" dbms="mysql"/>
    <changeSet id="1" author="joe">
         <createTable tableName="${table.name}">
             <column name="id" type="int"/>
             <column name="${column1.name}" type="${clob.type}"/>
             <column name="${column2.name}" type="int"/>
         </createTable>
    </changeSet>
</databaseChangeLog>

可用属性

  • name :表 schema 的名称; 必填
  • value :列表的名称; 必填
  • context :以逗号分隔列表表示的上下文。
  • dbms :作为逗号分隔列表给出的数据库类型。
  • global :定义属性是全局的还是仅限于数据库 ChangeLog 的。以 true false 表示。
<property name="simpleproperty" value="somevalue"/>
<property name="clob.type" value="clob" dbms="oracle,h2"/>
<property name="clob.type" value="longtext" dbms="mysql"/>
<property name="myproperty" value="yes" context="common,test"/>
<property name="localproperty" value="foo" global="false"/>

6.1.2.3、changeSet

changeset 标签是用于将数据库更改类型分组在一起的标记,并且是Liquibase在数据库上执行的更改单位。在changelog中跟踪由多个changeset 创建的变更列表。

每个 changeSet 标签都由 id 标签、 author 标签和 changelog classpath 名称的组合唯一标签。 id 标签仅用作标识符,它不指示更改运行的顺序,甚至不一定是整数。如果您不知道或不希望保存实际作者,只需使用占位符值,如 UNKNOW

Liquibase 执行数据库 ChangeLog 时,它按顺序读取 changeSet ,并针对每个 changeSet 检查 databasechangelog 表,以查看是否运行了 id/author/filepath 的组合。如果已运行,则将跳过 changeSet ,除非存在真正的 runAlways 标签。运行 changeSet 中的所有更改后, Liquibase 将在 databasechangelog 中插入带有 id/author/filepath 的新行以及 changeSet MD5Sum (见下文)。

Liquibase 尝试执行每个 changeSet 并在每次结束时提交事务,或者如果出现错误,则回滚。某些数据库将自动提交语句,这些语句会干扰此事务设置,并可能导致意外的数据库状态。因此,通常最好每个 changeSet 只进行一次更改,除非有一组非自动提交更改要应用为事务(如插入数据)。

可用属性

  • id :字母数字标识符, 必须
  • author :创建 changeSet 的人, 必须
  • dbms :要用于 changSet 的数据库的类型。运行迁移步骤时,它会根据此属性检查数据库类型。 有效的数据库类型名称列在受支持的数据库页上
  • runAlways :执行每次运行时设置的更改,即使更改之前已运行
  • runOnChange :在第一次看到更改时以及每次更改集更改时执行更改
  • context :如果在运行时传递了特定上下文,则执行更改。任何字符串都可用于上下文名称,并且它们处于不区分大小写状态。
  • runInTransaction : changeSet 是否应作为单个事务运行(如果可能)?默认值为 true 。从1.9开始,警告:小心使用此属性。如果设置为 false ,并且通过运行包含多个语句的 changeSet 部分发生错误,则 Liquibase 数据库更改日志表将保持无效状态。
  • failOnErroe :如果在执行 changeSet 时发生错误,是否认为此迁移失败?

可用的子标签

  • comment : changeSet 的说明。 XML 注释将提供相同的好处, Liquibase 的未来版本可能能够利用 <comment> 标记注释来生成文档
  • preConditions :将执行 changeSet 之前必须通过的前提条件。可用于在做不可恢复的内容(如自 1.7 起删除表)之前执行数据健全性检查
  • <AnyRefactoringTag(s)> :作为此 changeSet 的一部分运行的数据库更改(称为重构)
  • validCheckSum :列出被认为对此更改有效的校验,而不考虑数据库中存储的内容。自 1.7 起,主要用于需要修改 changeSet ,并且不希望在已运行过此修改的数据库上引发错误(不是建议的步骤)。
  • rollback :描述如何 回滚 changeSet 的 SQL 语句或重构标签 PS:如何进行回滚操作,会在后面专门介绍。

6.1.2.4、include/includeAll

include 标签将 change-logs分解为几个更易于管理的部分。如果需要更容易地包含多个文件,请使用includeAll标签。

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
  xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9
         http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd">
    <include file="com/example/news/news.changelog.xml"/>
    <include file="com/example/directory/directory.changelog.xml"/>
</databaseChangeLog>

6.2、变更集(changeSet)

变更集,一个 changeSet 中可以有多个修改,每个 changeSet 都需要指定 id 和修改人(author),要求 id + author + filepath 必须唯一,id另外还可以指定上下文(context)、数据库系统(dbms)等信息。 Liquibase changeSet 常用属性(基于 liquibase-3.6)

| 属性名 | 值类型 | 默认值 | 注释 | | :--------------: | :--------------------------: | :----: | :----------------------------------------------------------- | | id | String | | (必需)修改集编号 | | author | String | | (必需)修改人 | | context | String | | 修改上下文,指定后可以修改 springboot 配置文件中 spring.liquibase.context 项,来指定需执行的修改 | | labels | String | | 标签,与 context 功能相同 | | dbms | String | | 数据库系统,只用使用指定的数据库时才会执行,如 mysql、h2、postgresql、oracle等,多个数据库系统时以逗号分隔 | | alwaysRun | Boolean | false | 如果为 true 则每次 update 时都会执行 | | runOnChange | Boolean | false | 如果未 true,则每次 checksum 改变时都会执行 | | ignore | Boolean | false | 是否忽略此修改集 | | failOnError | Boolean | true | 如果为 false ,在执行修改时出现错误,liquibase 不会停止,会继续执行其他修改 | | runInTransaction | Boolean | true | 是否在执行时使用数据库事务管理 | | onValidationFail | 枚举,可选值:HALT、MARK_RAN | HALT | 验证失败后的处理方式,停止运行或记录运行状态 |

示例:

<databaseChangeLog
        xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog"
        xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd
        http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
    <changeSet id="20190314135820" author="wqf31415" context="dev" dbms="mysql"
               failOnError="true" onValidationFail="HALT"  >
        <!-- 修改集内容 -->
    </changeSet>
</databaseChangeLog>

6.2.1、创建表(createTable)

用于创建新的数据库表,写在 changeSet 标签内。 createTable 标签属性(基于 Liqubase-3.6)

| 属性名 | 值类型 | 默认值 | 注释 | | :---------: | :----: | :----: | :--------- | | tableName | String | | (必需)表名 | | catalogName | String | | 目录名 | | schemaName | String | | 数据库名 | | tablespace | String | | 表空间 | | remarks | String | | 注释 |

示例:

<changeSet id="20190314135820" author="wqf31415" context="dev" dbms="mysql"
           failOnError="true" onValidationFail="HALT" labels="lab" ignore="false" runOrder="">
    <createTable tableName="changelog_example" catalogName="liquibase_demo" schemaName="liquibase_demo"
                 remarks="创建表changelog_example" tablespace="ts_changelog_example">
        <column name="id" type="bigint" autoIncrement="${autoIncrement}">
            <constraints primaryKey="true" nullable="false"/>
        </column>
        <column name="name" type="varchar(255)" remarks="姓名" >
            <constraints nullable="true" unique="true" uniqueConstraintName="unique_name"/>
        </column>
        <column name="active" type="bit" defaultValueBoolean="true" defaultValue="0">
            <constraints nullable="true" />
        </column>
    </createTable>
</changeSet>

6.2.1.1、列(column)

在创建表或给表添加列时,需要在 createTable 标签内添加 column 标签,用来指定列的属性。 column 标签常用属性

| 属性名 | 值类型 | 默认值 | 注释 | | :-----------: | :-----: | :----: | :------- | | name | String | | 字段名 | | type | String | | 数据类型 | | defaultValue | String | | 默认值 | | autoIncrement | Boolean | | 自增 | | remarks | String | | 注释 |

6.2.1.2、约束(constraints)

在 column 标签中可以给字段添加约束,如可否为空、主键、唯一等约束条件。 constraints 标签常用属性

| 属性名 | 值类型 | 默认值 | 注释 | | :-------------------: | :-----: | :----: | :--------- | | nullable | Boolean | true | 可否为空 | | notNullConstraintName | String | | 非空约束名 | | primaryKey | Boolean | | 是否主键 | | primaryKeyName | String | | 主键名 | | unique | Boolean | false | 是否唯一 |

6.2.2、添加字段(addColumn)

给已经创建的表添加字段,写在 changeSet 标签内,此标签内用 column 标签定义要添加的字段属性。

| 属性名 | 值类型 | 默认值 | 注释 | | :---------: | :----: | :----: | :--------- | | tableName | String | | (必需)表名 | | catalogName | String | | 目录名 | | schemaName | String | | 数据库名 |

示例:

<changeSet id="20190314154000" author="wqf" context="dev">
    <comment>增加 email 字段</comment>
    <addColumn tableName="changelog_example" catalogName="liquibase_demo" schemaName="liquibase_demo">
        <column name="email" type="varchar(255)">
            <constraints nullable="false"/>
        </column>
    </addColumn>
</changeSet>

6.2.3、删除字段(dropColumn)

dropColumn 标签用于删除已存在表中的字段,写在 changeSet 标签内,注意这个标签内部不能有内容,否则会报错。

| 属性名 | 值类型 | 默认值 | 注释 | | :---------: | :----: | :----: | :----------- | | tableName | String | | (必需)表名 | | name | String | | 要删除的列名 | | catalogName | String | | 目录名 | | schemaName | String | | 数据库名 |

示例:

<changeSet id="20190314181622" author="wqf" context="dev">
       <comment>删除字段 name</comment>
       <dropColumn tableName="changelog_example" columnName="name" schemaName="liquibase_demo" catalogName="liquibase_demo"/>
   </changeSet>

6.2.4、插入数据(insert)

insert 标签用于在表中插入数据,写在 changeSet 标签内,需要指定表名等属性,在标签内使用 column 标签指定数据值。

| 属性名 | 值类型 | 默认值 | 注释 | | :---------: | :----: | :----: | :------------------------------------------- | | tableName | String | | (必需)表名 | | dbms | String | | 数据库类型,只有在指定的这种数据库下才会执行 | | catalogName | String | | 目录名 | | schemaName | String | | 数据库名 |

示例:

<changeSet id="20190314183320" author="wqf">
    <comment>插入数据</comment>
    <insert tableName="changelog_example" dbms="mysql" catalogName="liquibase_demo" schemaName="liquibase_demo">
        <column name="email" value="abc@abc.com"/>
        <column name="login_name" value="abc"/>
        <column name="password" value="abc123"/>
    </insert>
</changeSet>

6.2.5、加载批量数据(loadData)

在 changeSet 中添加 loadData 标签,用来加载数据。

| 属性名 | 值类型 | 默认值 | 注释 | | :---------: | :----: | :----: | :------------------- | | tableName | String | | (必需)表名 | | file | String | | 要加载的数据文件路径 | | separator | String | | 分隔符 | | encoding | String | | 编码格式 | | quotchar | String | " | 引号 | | catalogName | String | | 目录名 | | schemaName | String | | 数据库名 |

示例:

<changeSet id="20190314183320" author="wqf">
      <loadData tableName="user" file="classpath:liquibase/initdata/user.csv" separator=";" encoding="UTF-8" 
                quotchar="&quot;" schemaName="liquibase_demo" catalogName="liquibase_demo"/>
</changeSet>

6.2.6、修改列名(renameColumn)

在 changeSet 中添加 renameColumn 标签用于修改列名。

| 属性名 | 值类型 | 默认值 | 注释 | | :------------: | :----: | :----: | :--------- | | tableName | String | | (必需)表名 | | oldColumnName | String | | 原列名 | | newColumnName | String | | 新列名 | | columnDataType | String | | 数据类型 | | remarks | String | | 注释 | | catalogName | String | | 目录名 | | schemaName | String | | 数据库名 |

示例:

<changeSet id="20190314184833" author="wqf">
    <comment>修改字段名</comment>
    <renameColumn tableName="changelog_example" oldColumnName="password" newColumnName="pwd" catalogName="liquibase_demo"
                  schemaName="liquibase_demo" columnDataType="varchar(255)" remarks="修改字段名"/>
</changeSet>

6.2.7、创建索引(createIndex)

为了提高数据查询速度,我们可以给关键字段添加索引,最好是在建表时就添加索引。如我们给 state 表的 collection_time device_id 字段添加联合索引,配置如下:

<changeSet id="201907100943" author="wqf">
    <createIndex tableName="state" indexName="index_collectionTime_deviceId">
        <column name="collection_time"></column>
        <column name="device_id"></column>
    </createIndex>
</changeSet>

6.2.8、添加数据库标签 (tagDatabase)

创建一个数据库标签以便将来回滚。

| 属性名 | 值类型 | 默认值 | 注释 | | ------ | ------ | ------ | -------- | | tag | String | | 标签名称 |

<changeSet  author="liquibase-docs"  id="tagDatabase-example">  
    <tagDatabase  tag="version_1.3"/>  
</changeSet>

6.2.9、Community Change Types

Change Types 是Liquibase对数据库架构所做的变更。

Liquibase Community 版可以进行以下类型的更改:

6.2.9.1、Changes that add something

| Change Types | 释义 | | ------------------------------------------------------------ | ------------------------------------------------------------ | | addAutoIncrement | 将现有的列转换为自动递增(也称为“身份”)列。 | | addColumn | addColumn更改类型将新列添加到现有表。 | | addDefaultValue | 将默认值添加到指定列的数据库定义中。必须设置defaultValue,defaultValueNumeric,defaultValueBoolean或defaultValueDate之一。 | | addForeignKeyConstraint | 将外键约束添加到现有列。 | | addLookupTable | 创建包含存储在列中的值的查找表,并创建新表的外键。 | | addNotNullConstraint | 向现有表添加非空约束。如果传递defaultNullValue属性,则在应用约束之前,列的所有空值都将更新为传递的值。 | | addPrimaryKey | 从现有列或一组列中添加主键。 | | addUniqueConstraint | 向现有列或一组列添加唯一约束。 |

6.2.9.2、Changes that create something

| Change Types | 释义 | | ------------------------------------------------------------ | ------------------------------------------------------------ | | createIndex | 在现有列或一组列上创建索引。 | | createProcedure | 定义存储过程的定义。此更改类型比原始SQL命令更适合用于创建过程,因为它不会尝试删除注释或拆分行。 | | createSequence | 创建一个新的数据库序列。 | | createTable | 创建一个表。 | | createView | 创建一个视图。 |

6.2.9.3、Changes that drop something

| Change Types | 释义 | | ------------------------------------------------------------ | ---------------------- | | dropAllForeignKeyConstraints | 删除全部外键约束 | | dropDefaultValue | 删除默认值 | | dropIndex | 删除索引 | | dropPrimaryKey | 删除主键 | | dropSequence | 删除序列 | | dropUniqueConstraint | 删除唯一约束 | | dropNotNullConstraint | 删除非空约束 | | dropProcedure | 删除存储过程 | | dropColumn | 删除列 | | dropForeignKeyConstraint | 删除指定的一个外键约束 | | dropTable | 删除表 | | dropView | 删除视图 |

6.2.9.4、Changes that rename something

| Change Types | 释义 | | ------------------------------------------------------------ | ------------ | | renameTable | 重命名表名 | | renameColumn | 重命名列名 | | renameView | 重命名视图名 | | renameSequence | 重命名序列名 |

6.2.9.5、SQL Changes

| Change Types | 释义 | | ------------------------------------------------------------ | ------------------------- | | sql | 执行指定 sql | | sqlFile | 执行指定 SQL 文件中的 sql |

6.2.9.6、Other kinds of changes

| Change Types | 释义 | | ------------------------------------------------------------ | ------------------------------------------------------------ | | delete | 从现有表中删除数据。 | | executeCommand | 执行系统命令。由于此更改类型不会像大多数情况下那样生成SQL,因此使用Liquibase命令(例如migrationSQL)可能无法按预期工作。因此,请尽可能使用生成SQL的更改类型。 | | loadData | 将数据添加到Changelog时,将数据从CSV文件加载到现有表中。 | | mergeColumns | 合并两个列中的值(以字符串形式连接),然后将结果值存储在新列中。 | | output | 记录消息并继续执行。 | | setTableRemarks | 为指定数据表添加备注 | | customChange | 自定义 Change | | empty | 空,不执行任何操作 | | insert | 将数据插入到现有表中。 | | loadUpdateData | 将数据从CSV文件加载或更新到现有表中。通过发布检查记录是否存在的SQL批处理,不同于loadData。如果找到,则记录为UPDATE,否则记录为INSERTed。另外,生成DELETE语句以进行回滚。 | | modifyDataType | 修改数据类型 | | setColumnRemarks | 添加列注释 | | stop | 通过消息停止Liquibase执行。主要用于调试和逐步执行变更日志 | | update | 更新现有表中的数据。 | | alterSequence | 更改现有序列的属性。 | | tagDatabase | 创建一个数据库标签以便将来回滚。 |

7、Liquibase & Maven

7.1、在 Maven pom 文件配置 Liquibase

Maven中集成LiquiBase,主要是配置 liquibase-maven-plugin ,首先给出一个示例:

<plugin>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-maven-plugin</artifactId>
    <version>3.4.2</version>
    <configuration>
        <propertyFileWillOverride>true</propertyFileWillOverride>
        <propertyFile>src/main/resources/liquibase/liquibase.properties</propertyFile>
        <changeLogFile>${changeLogFile}</changeLogFile>
        <driver>${driver}</driver>
        <url>${url}</url>
        <username>${username}</username>
        <password>${password}</password>
    </configuration>
    <executions>
        <execution>
            <phase>process-resources</phase>
            <goals>
                <goal>update</goal>
            </goals>
        </execution>
    </executions>
</plugin>

其中 <configuration> 节点中的配置可以放在单独的配置文件里。

changeLogFile:src/main/resources/liquibase/master.xml
driver:com.mysql.cj.jdbc.Driver
url:jdbc:mysql://localhost:3306/test
username:root
password:aries2776

7.2、 liquibase:update

执行changelog中的变更:

mvn liquibase:update

7.3、 liquibase:rollback

rollback有3中形式,分别是:

  • rollbackCount: 表示rollback的changeset的个数;
  • rollbackDate:表示rollback到指定的日期;
  • rollbackTag:表示rollback到指定的tag,需要使用LiquiBase在具体的时间点打上tag;可以在 changeLog 文件中显式定

7.3.1、 rollbackCount

rollbackCount 比较简单,示例如:

mvn liquibase:rollback -Dliquibase.rollbackCount=3

回滚指定数量的 changeSet

7.3.2、 rollbackDate

rollbackDate 需要注意日期的格式,必须匹配当前平台上执行 DateFormat.getDateInstance() 得到的格式,比如我的格式为 MMM d, yyyy ,示例如:

mvn liquibase:rollback -Dliquibase.rollbackDate="Apr 10, 2016"

7.3.3、 rollbackTag

rollbackTag 使用tag标识,所以需要先打tag,示例如:

mvn liquibase:tag -Dliquibase.tag=tag20160410

或者

<?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">
    <!-- 创建 tag -->
    <changeSet id="8" author="aries">
        <tagDatabase tag="20201210-before-tag" />
    </changeSet>
    <changeSet id="7" author="aries">
        <createTable tableName="entity">
            <column name="entity_id" autoIncrement="1" type="int">
                <constraints primaryKey="true" nullable="false" unique="true"/>
            </column>
            <column name="name" type="varchar(200)" />
            <column name="tenant_api_name" type="varchar(200)" />
            <column name="api_name" type="varchar(200)" />
            <column name="entity_type" type="varchar(200)" />
            <column name="primary_key_field" type="varchar(200)" />
            <column name="status" type="int(11)" />
            <column name="params" type="json" />
            <column name="b_time_field" type="varchar(200)" />
        </createTable>
    </changeSet>
</databaseChangeLog>

回滚操作:

mvn liquibase:rollback -Dliquibase.rollbackTag=20201210-before-tag

7.4、 liquibase:generateChangeLog

<build>
    <plugins>
        <plugin>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-maven-plugin</artifactId>
            <version>3.4.2</version>
            <configuration>
                <propertyFile>src/main/resources/liquibase.properties</propertyFile>
                <propertyFileWillOverride>true</propertyFileWillOverride>
                <!--生成文件的路径-->