Spring Boot 简单集成 Liquibase

7,917 阅读7分钟

Liquibase 是一个用于跟踪、管理和应用数据库变化的开源的数据库重构工具。它将所有数据库的变化(包括结构和数据)都保存在 changelog文件中,便于版本控制,它的目标是提供一种数据库类型无关的解决方案,通过执行 schema 类型的文件来达到迁移。

Liquibase 特性

Liquibase 具备如下特性:

  • 支持几乎所有主流的数据库,如 MySQL, PostgreSQL, Oracle, Sql Server, DB2 等;
  • 支持多开发者的协作维护;
  • 日志文件支持多种格式,如 XML, YAML, JSON, SQL等;
  • 支持上下文相关逻辑
  • 生成数据库变更文档
  • 支持多种运行方式,如命令行、Spring 集成、Maven 插件、Gradle 插件等。

更多详情介绍,请查阅 Liquibase 官方文档

Spring Boot 集成 Liquibase

添加依赖

因为 Spring Boot 已经内置支持整合 Liquibase,我们只需要在项目工程中引入 Liquibase 的依赖进行配置即可。

<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
    <version>3.6.3</version>
</dependency>

配置 application 文件

在 classpath 中配置 application.properties 或 application.yml 文件,

# 启用liquibase
liquibase.enabled=true
# 存储变化的文件(changelog)位置
liquibase.change-log=classpath:sample_change.sql
# 检查存储变化的文件是否存在
liquibase.check-change-log-location=true
# 分环境执行,若在 changelog 文件中设置了对应 context 属性,则只会执行与 dev 对应值的 changeset
liquibase.contexts=dev
# 执行前首先删除数据库,默认 false。若设置为 true,则执行变更前,会先删除目标数据库,请谨慎
liquibase.dropFirst=false
# 执行更新时将回滚 SQL 写入的文件路径
liquibase.rollback-file=
# 如果使用工程已配置的 datasource 数据源,则以下三个数据库连接参数可不配置
## 访问数据库的连接地址
liquibase.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
# 访问数据库的用户名
liquibase.user=test
# 访问数据库的密码
liquibase.password=test

注意:如果使用工程已配置的 datasource 数据源,则 liquibase.url、liquibase.userliquibase.password这个三个参数可不配置,目标数据源即为工程已配置好的数据源。因为 Spring 会自动为 liquibase 获取可用的数据源,详情可查看这个类:org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration

编写存储变更的 changelog 文件

目前 Liquibase 支持 XML、YAML、JSON 和 SQL 格式四种格式的 changelog 文件。为了方便和直观,下面以基于 SQL 格式编写 changelog 文件:sample_change.sql

--liquibase formatted sql

--changeset zhouyi:1
-- 创建用户表
CREATE TABLE `user` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `age` int(10) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--rollback drop table user;

--changeset zhouyi:2
insert into user(id, name, age) values(1,'张三',29);
insert into user(id, name, age) values(2,'李四',20);

启动 Spring Boot 应用后,可以从日志看到输出:

2019-03-27 13:13:16.439 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - DELETE FROM test.DATABASECHANGELOGLOCK
2019-03-27 13:13:16.442 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - INSERT INTO test.DATABASECHANGELOGLOCK (ID, `LOCKED`) VALUES (1, 0)
2019-03-27 13:13:16.506 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - SELECT `LOCKED` FROM test.DATABASECHANGELOGLOCK WHERE ID=1
2019-03-27 13:13:16.586 [main] [] INFO  liquibase.lockservice.StandardLockService.info:42 - Successfully acquired change log lock
2019-03-27 13:13:16.697 [main] [] INFO  liquibase.changelog.StandardChangeLogHistoryService.info:42 - Creating database history table with name: test.DATABASECHANGELOG
2019-03-27 13:13:16.700 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - CREATE TABLE test.DATABASECHANGELOG (ID VARCHAR(255) NOT NULL, AUTHOR VARCHAR(255) NOT NULL, FILENAME VARCHAR(255) NOT NULL, DATEEXECUTED datetime NOT NULL, ORDEREXECUTED INT NOT NULL, EXECTYPE VARCHAR(10) NOT NULL, MD5SUM VARCHAR(35) NULL, `DESCRIPTION` VARCHAR(255) NULL, COMMENTS VARCHAR(255) NULL, TAG VARCHAR(255) NULL, LIQUIBASE VARCHAR(20) NULL, CONTEXTS VARCHAR(255) NULL, LABELS VARCHAR(255) NULL, DEPLOYMENT_ID VARCHAR(10) NULL)
2019-03-27 13:13:17.570 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - SELECT COUNT(*) FROM test.DATABASECHANGELOG
2019-03-27 13:13:17.575 [main] [] INFO  liquibase.changelog.StandardChangeLogHistoryService.info:42 - Reading from test.DATABASECHANGELOG
2019-03-27 13:13:17.579 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - SELECT * FROM test.DATABASECHANGELOG ORDER BY DATEEXECUTED ASC, ORDEREXECUTED ASC
2019-03-27 13:13:17.585 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - SELECT COUNT(*) FROM test.DATABASECHANGELOGLOCK
2019-03-27 13:13:17.678 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - CREATE TABLE `user` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `age` int(10) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
2019-03-27 13:13:17.935 [main] [] INFO  liquibase.changelog.ChangeSet.info:42 - Custom SQL executed
2019-03-27 13:13:17.937 [main] [] INFO  liquibase.changelog.ChangeSet.info:42 - ChangeSet classpath:sample_change.sql::1::zhouyi ran successfully in 297ms
2019-03-27 13:13:17.938 [main] [] INFO  liquibase.executor.jvm.JdbcExecutor.info:42 - SELECT MAX(ORDEREXECUTED) FROM test.DATABASECHANGELOG

从数据库中可以看到变化,首次运行新增了 3 张表,

数据库变更

其中DATABASECHANGELOGDATABASECHANGELOGLOCK 表是 Liquibase 自动生成的,而 user 表是执行我们编写的 changeset变更集后生成的,并且也已经执行了第 2 个变更集,插入了两条数据。

SQL 格式的 changelogs 文件

SQL 格式的 changelog 文件使用 SQL 注释作为元数据,为确保这个文件被 Liquibase 识别为 changelog文件,,SQL 文件必须以以下注释开头:

--liquibase formatted sql

changeset 变更

SQL 格式的 changelog 文件中在变更的 SQL 前需要加上以下注释,表示为一个 changeset变更集:

--changeset author:id attribute1:value1 attribute2:value2 [...]

在之前的 sample_change.sql 文件中编写了两条 changeset (变更集),

变更集 changeset 是通过 author + id 的方式来保证唯一性,如以上 zhouyi:1zhouyi:2 两条表更集。

变更集提供以下属性:

属性 说明
stripComments 设置为 true 可在执行之前删除 SQL 中的任何注释, 否则为 false。如果未设置, 则默认值为 true
splitStatements
endDelimiter 应用于语句结尾的分隔符。默认为“;”,也可以设置为“”
runAlways 在每次运行时执行变更集, 即使之前已运行
runOnChange 在首次看到更改并每次更改变更集时执行更改
context 如果在运行时传递了特定上下文, 则执行更改。任何字符串都可以用于上下文名称, 并且大小写不敏感。
logicalFilePath 用于在创建变更集的唯一标识符时重写文件名和路径。移动或重命名更改日志时所必需。
labels 标签是对变更集进行分类的通用方法集类似上下文, 但工作方式正好相反。如果不是在运行时定义一组上下文, 然后在变更集中定义一个匹配表达式, 而是在上下文中定义一组标签, 在运行时定义一个匹配表达式。
runInTransaction 变更集是否应作为单个事务运行 (如果可能),默认值为 true。请注意此属性,如果设置为 false, 并且通过运行包含多个语句的变更集部分发生错误, 则 liquibase 数据库的 databasechangeloglock 表将处于无效状态
failOnError 如果在执行变更集时发生错误, 迁移是否应返回失败
dbms 要用于该变更集的数据库的类型。当迁移步骤运行时, 它将根据此属性检查数据库类型,如:oracle、mysql
logicalFilePath 在数据库 databasechangeloglock 中设置逻辑文件路径, 而不是在执行 liquibase 的 sql 物理文件位置。

前置条件

可以为每个变更集指定先决条件。目前, 仅支持 SQL 检查前置条件。

--preconditions onFail:HALT onError:HALT
--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM my_table

回滚操作

变更集可能包括回滚变更集时要应用的语句。回滚声明也是使用表注释。

--rollback SQL STATEMENT

例如在前面编写的 changelog 文件中的第一个 changeset:

--changeset zhouyi:1
-- 创建用户表
CREATE TABLE `user` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `age` int(10) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--rollback drop table user;

首先的变更的 SQL 含义是新建一个 user 表,回滚的 SQL 则表示删除新建的 user 表。

Liquibase 最佳实践

下面介绍可应用于项目的一些最佳实践。

一个变更集只设置一次更改

尽可能地避免对一个变更集进行多次更改,以避免自动提交 SQL 语句而可能使数据库处于非预期状态。 如 --changeset zhouyi:1 变更集,只新建一张 user 表,后面不再修改该变更集,如果需要变更,可以新增一条变更集。

变更集的 ID

选择适合您的方法。有的人是使用从 1 开始的序列号, 并且在更改日志中是唯一的,也有些人选择一个描述性的名称(例如:new-address-table

总是考虑回滚

尽量尝试以可以回滚的方式编写变更集,如 --changeset zhouyi:1 变更集新建一个 user 表,在后面跟上回滚的 SQL, --rollback drop table user;

为变更集添加注释

尽量为每一个变更集条目增加注释,如 -- 创建用户表

开发人员使用流程

  • 使用您最喜欢的 IDE 或编辑器, 创建一个包含更改的新本地更改集;
  • 运行 Liquibase 以执行新的变更集 (这将测试 SQL 代码);
  • 在应用程序代码中执行相应的更改 (例如, Java 代码);
  • 测试验证新的应用程序代码以及数据库更改;
  • 提交变更集和应用程序代码。