https://juejin.cn/post/7171232605478584328

https://juejin.cn/post/7170857098538909732

前言

研发过程中经常涉及到数据库变更,对表结构的修复及对数据的修改,为了保证各环境都能正确的进行变更,我们可能需要维护一个数据库升级文档来保存这些记录,有需要升级的环境按文档进行升级。

这样手工维护有几个缺点:

  1. 无法保证每个环境都按要求执行
  2. 遇到问题不一定有相对的回滚语句
  3. 无法自动化

为了解决这些问题,我们进行了一些调研,主要调研对象是 Liquibase 和 Flyway,我们希望通过数据库版本管理工具实现以下几个目标:

  1. 数据库升级
  2. 数据库回滚
  3. 版本标记

Liquibase还是Flyway

Flyway 和 Liquibase 都支持专业数据库重构和版本控制所需的所有功能,因此您将始终知道要处理的数据库模式的版本以及它是否与软件版本匹配。两种工具都集成在 Maven 或 Gradle 构建脚本中以及 Spring Boot 生态系统中,因此您可以完全自动化数据库重构。

Flyway 使用 SQL 定义数据库更改,因此您可以定制 SQL 脚本,使其与基础数据库技术(例如Oracle或PostgreSQL)良好地配合使用。另一方面,使用 Liquibase,您可以通过使用 XML,YAML 或 JSON 来定义数据库更改来引入抽象层。因此,Liquibase 更适合在具有不同基础数据库技术的不同环境中安装的软件产品中使用。

Flyway

数据库的变更可以用 SQL 或者 Java 来记录,Flyway 通过下面的步骤实现数据库变更:

  1. Flyway 先在数据库中检查自己的元数据表(默认为SCHEMA_VERSION)是否存在,如果没有,则创建一个;
  2. 检查 classpath 中所有的变更;
  3. 对比变更和自己的表,如果变更的版本低于或等于当前版本,不做任何变动;
  4. 否则,变更会按从低到高排序,并依次执行;
  5. 执行完,在 SCHEMA_VERSION 做相应的记录

Liquibase

工作方式与 Flyway 非常类似,但是 Liquibase 稍微复杂点,这点后续会单独介绍。

对比

两者的基本功能其实都差不多:

  • 都是 Java 开发的开源数据库变更管理工具
  • 支持大部分的数据库
  • 和 Maven/Gradle 无缝集成
  • 和 Spring 无缝集成
  • 非常类似的变更实现方式
  • 复杂变更如果 SQL 不能满足的话,都可以用 Java 代码实现

较大区别是 Flyway 的变更以纯 SQL 为脚本,简单直接;Liquibase 比较厚重,当然花样也比较多,包括:

  • 可指定不同的 profile
  • 具有通用的变更操作支持不同的数据库,如 createTable
  • Liquibase 开源版本支持 diff 模式,而此特性 Flyway 必须用商业版
  • Liquibase 开源版本支持回滚 rollback,而此特性 Flyway 必须用商业版,Liquibase 的付费版本据说对不同种类的回滚有更复杂的支持。
  • 两者指定变更执行顺序的方法不同,Flyway 通过固定的文件名格式来确定顺序,而 Liquibase 就是通过给定文件的顺序来执行,所以开发人员还要遵守好命名规则,例如按照日期/时间顺序命名

如果您想完全控制 SQL,Flyway 是首选工具,因为您可以使用完全定制的 SQL 甚至 Java 代码来更改数据库。多种数据源的情况下使用 Liquibase 会更加合适,不需要维护多种数据库脚本,和学习多种数据库语言,Liquibase 对于大型项目更加友好。

综上所述,我们在项目中选择 Liquibae。接下来简单来认识一下 Liquibase。

Liquibase

介绍

Liquibase 是一个用于数据库重构和迁移的开源工具,通过日志文件的形式记录数据库的变更,然后执行日志文件中的修改,将数据库更新或回滚到一致的状态。它的目标是提供一种数据库类型无关的解决方案,通过执行 schema 类型的文件来达到迁移。其优点主要有以下:

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

liquibase 官方文档地址: www.liquibase.org/documentati…

本地安装

根据自己的操作系统下载对应的二进制包,下载地址:www.liquibase.org/dow...

我这里下载的是 Mac 版本的压缩包,然后在本地解压,解压包存放位置为:

 
sh

复制代码
/Library/liquibase-4.4.3

sudo vi ~/.bash_profile,修改环境变量配置文件:

 
sh

复制代码
export PATH="/Library/liquibase-4.4.3:$PATH"

然后 source ~/.bash_profile,使配置文件生效。

最后执行下述命令,验证是否安装成功。

 
sh

复制代码
 % liquibase --version
####################################################
## _ _ _ _ ##
## | | (_) (_) | ##
## | | _ __ _ _ _ _| |__ __ _ ___ ___ ##
## | | | |/ _` | | | | | '_ \ / _` / __|/ _ \ ##
## | |___| | (_| | |_| | | |_) | (_| \__ \ __/ ##
## \_____/_|\__, |\__,_|_|_.__/ \__,_|___/\___| ##
## | | ##
## |_| ##
## ##
## Get documentation at docs.liquibase.com ##
## Get certified courses at learn.liquibase.com ##
## Free schema change activity reports at ##
## https://hub.liquibase.com ##
## ##
####################################################
Starting Liquibase at 10:06:20 (version 4.4.3 #53 built at 2021-08-05 18:32+0000)
Running Java under /Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/jre (Version 1.8.0_301) Liquibase Version: 4.4.3
Liquibase Community 4.4.3 by Datical

下载 PostgreSQL 驱动到 lib 包中,下载地址为:jdbc.postgresql.org/download.ht…

本次下载版本为:42.2.12

结合Idea使用

Liquibase问题

随着项目的发展,一个项目中的代码量会非常庞大,同时数据库表也会错综复杂。如果一个项目使用了Liquibase对数据库结构进行管理,越来越多的问题会浮现出来。

  1. ChangeSet 文件同时多人在修改,自己的 ChangeSet 被改掉,甚至被删除掉。
  2. 开发人员将 ChangeSet 添加到已经执行过的文件中,导致执行顺序出问题。
  3. 开发人员擅自添加对业务数据的修改,其它环境无法执行并报错。
  4. ChangeSet 中 SQL 包含 schema 名称,导致其它环境 schema 名称变化时,ChangeSet 报错。
  5. 开发人员不小心改动了已经执行过的 ChangeSet,在启动时会报错。

Liquibase基本规范

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

根据发布进行管理

  1. 每个发布新建一个文件夹,所有发布相关的 ChangeSet 文件以及数据初始化文件,均放在些文件夹中。
  2. 每个发布新建一个 master.xml。此 master.xml 中,include 本次发布需要执行的 ChangeSet 文件
  3. 根据开发小组独立 ChangeSet文件(可选)
  4. 根据功能独立 ChangeSet 文件。例如 user.xml, company.xml
 
sh

复制代码
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
| | |- ...

模块化管理

首先说明一下 Spring Boot 中 Liquibase 默认是如何执行以及执行结果。

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

因此我们可以认为一个 SpringLiquibase 执行为一个模块。

引入多模块管理时,基于上节文件管理规范,我们基于模块管理再做下调整。

 
sh

复制代码
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
| | |- ...

如何在一个Spring Boot运行多个SpringLiquibase呢?

1、禁用Spring Boot自动运行Liquibase。

 
sh

复制代码
# application.properties
# spring boot 2以上
spring.liquibase.enabled=false
# spring boot 2以下
liquibase.enabled=false

2、修改配置项

 
java

复制代码
@Configuration
public class LiquibaseConfiguration() { /**
* 用户模块Liquibase
*/
@Bean
public SpringLiquibase userLiquibase(DataSource dataSource) {
SpringLiquibase liquibase = new SpringLiquibase();
// 用户模块Liquibase文件路径
liquibase.setChangeLog("classpath:liquibase/user/master.xml");
liquibase.setDataSource(dataSource);
liquibase.setShouldRun(true);
liquibase.setResourceLoader(new DefaultResourceLoader());
// 覆盖Liquibase changelog表名
liquibase.setDatabaseChangeLogTable("user_changelog_table");
liquibase.setDatabaseChangeLogLockTable("user_changelog_lock_table");
return liquibase;
}
/**
* 订单模块Liquibase
*/
@Bean
public SpringLiquibase orderLiquibase() {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setChangeLog("classpath:liquibase/order/master.xml");
liquibase.setDataSource(dataSource);
liquibase.setShouldRun(true);
liquibase.setResourceLoader(new DefaultResourceLoader());
liquibase.setDatabaseChangeLogTable("order_changelog_table");
liquibase.setDatabaseChangeLogLockTable("order_changelog_lock_table");
return liquibase;
}
}

Liquibase命令

对应在 IDEA 中的位置如下图所示:

changelog文件

变更集 changeset 是通过 author + id 的方式来保证唯一性

 
java

复制代码
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<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.6.xsd">
<!--第一种标签建表方式-->
<changeSet author="future_zwp (generated)" id="reference-2019082600-00" context="team2,uat,prod">
<createTable tableName="personal_bank_swift">
<column name="id" type="serial">
<constraints primaryKey="true"/>
</column>
<column name="bank_code" type="text" remarks="银行编码"></column>
<column name="clearing_code" type="text" ></column>
<column name="swift_code" type="text" ></column>
<column name="create_by" type="text" ></column>
<column name="created_at" type="timestamp"></column>
<column name="updated_by" type="text" ></column>
<column name="updated_at" type="timestamp"></column>
<column name="pt" type="text"></column>
</createTable>
<rollback>
<dropTable tableName="personal_bank_swift"/>
</rollback>
</changeSet> <changeSet author="future_zwp (generated)" id="reference-2019082600-01" context="team2,uat,prod">
<createIndex indexName="idx_bank_info_bank_clearing" tableName="personal_bank_swift">
<column name="bank_code"/>
<column name="clearing_code"/>
</createIndex>
</changeSet> <changeSet author="future_zwp (generated)" id="reference-2019082600-02" context="team2,uat,prod">
<createIndex indexName="idx_personal_bank_swift_swift_code" tableName="personal_bank_swift">
<column name="swift_code"/>
</createIndex>
</changeSet> <!--第二种sql建表方式,所有的sql语句都支持,学习成本低,更灵活-->
<changeSet author="zhaowenpeng" id="reference-2019082600-03" context="team2,uat,prod">
<sql splitStatements="true">
drop table if exists personal_bank_swift; create table personal_bank_swift
(
id serial primary key,
bank_code text,
clearing_code text,
swift_code text,
create_by text,
create_at timestamp(6),
updated_by text,
updated_at timestamp(6)
); comment on column personal_bank_swift.bank_code
is '银行编码'; create index idx_bank_info_bank_clearing on personal_bank_swift(bank_code,clearing_code);
create index idx_personal_bank_swift_swift_code on personal_bank_swift(swift_code);
</sql>
</changeSet> <!--引用sql文件-->
<changeSet author="hresh" id="reference-2019082600-04" context="team2,uat,prod">
<sqlFile path="sql/init-personal_bank_swift.sql"></sqlFile>
</changeSet>
</databaseChangeLog>

默认表

第一次执行完成后目标数据库会多出两张表:

  • DATABASECHANGELOG 表
  • DATABASECHANGELOGLOCK表

1、databasechangelog

Liquibase 使用 databasechangelog 表来跟踪已运行的changeSet。

该表将每个更改设置作为一行进行跟踪,由存储changelog文件的路径的id、author和filename列的组合标识。

2、databasechangeloglock

Liquibase 使用 databasechangeloglock 表确保一次只运行一个 Liquibase 实例。

因为Liquibase 只是从 databasechangelog 表读取以确定需要运行的changeSet,因此,如果同时对同一数据库执行多个 Liquibase实例,则会发生冲突。如果多个开发人员使用相同的数据库实例,或者集群中有多个服务器在启动时自动运行 Liquibase,则可能会发生这种情况。

如果 Liquibase 未干净地退出,则锁住的行可能会保留为锁定状态。您可以通过运行UPDATE DATABASECHANGELOGLOCK SET LOCKED=0清除当前锁

总结

关于 Liquibase 还有很多知识点需要学习,本文只是简单地带大家认识一下它,不真正使用还是无法理解它的作用,所以下一篇文章我们将实操一个项目,来为大家演示 Liquibase 的功能。

参考文献

Spring Boot使用Liquibase最佳实践

LiquiBase 管理数据库变更实践

liquibase使用教程

Liquibase的基本使用说明

liquibase的changelog详解

liquibase yaml命令参考大全

 

SpringBoot结合Liquibase实现数据库变更管理的更多相关文章

  1. 使用 dbdeploy.net 管理数据库变更

    使用 dbdeploy.net 管理数据库变更 没有包含数据库的持续集成都是假的.这可不是我说的.一直以来都没能找到一个理想的数据库变更管理工具.直到转了 java 再回来,才发现 dbdeploy ...

  2. springboot集成liquibase,h2数据库

    Liquibase是一个用于跟踪.管理和应用数据库变化的开源的数据库重构工具.它将所有数据库的变化(包括结构和数据)都保存在XML文件中,便于版本控制. Liquibase具备如下特性:* 不依赖于特 ...

  3. springboot学习-jdbc操作数据库--yml注意事项--controller接受参数以及参数校验--异常统一管理以及aop的使用---整合mybatis---swagger2构建api文档---jpa访问数据库及page进行分页---整合redis---定时任务

    springboot学习-jdbc操作数据库--yml注意事项--controller接受参数以及参数校验-- 异常统一管理以及aop的使用---整合mybatis---swagger2构建api文档 ...

  4. Oracle 数据库用户管理

    Oracle 数据库用户管理 Oracle 权限设置      一.权限分类: 系统权限:系统规定用户使用数据库的权限.(系统权限是对用户而言). 实体权限:某种权限用户对其它用户的表或视图的存取权限 ...

  5. 企业架构研究总结(27)——TOGAF架构开发方法(ADM)之架构变更管理阶段

    1.10 架构变更管理(Architecture Change Management) 企业架构开发方法各阶段——架构变更管理 1.10.1 目标 本阶段的目标是: 确保基线架构持续符合当前实际. 评 ...

  6. TOGAF架构开发方法(ADM)之架构变更管理阶段

    TOGAF架构开发方法(ADM)之架构变更管理阶段 1.10 架构变更管理(Architecture Change Management) 企业架构开发方法各阶段——架构变更管理 1.10.1 目标 ...

  7. cuckoo数据库变更

    1.cuckoo版本升级 cuckoo默认的数据库为sqlite,默认连接方式为sqlite:///os.path.join(REPORT_ROOT, "db", "cu ...

  8. 20181218-PostgreSQL数据库Extension管理

    20181218-PostgreSQL数据库Extension管理 注意:在集群的一个数据库中安装扩展,在集群的另一个数据库要使用的话,仍需安装 1. 查看当前已安装Extension postgre ...

  9. mysql用户授权、数据库权限管理、sql语法详解

    mysql用户授权.数据库权限管理.sql语法详解 —— NiceCui 某个数据库所有的权限 ALL 后面+ PRIVILEGES SQL 某个数据库 特定的权限SQL mysql 授权语法 SQL ...

  10. SAP 生产订单变更管理 OCM Order Changement Management

    SAP OCM Order Changement Management  一.目的 订单变更管理系统是当我们的订单(生产订单.计划订单.采购订单)已经存在的时候,其物料主数据或销售数据有变更时,我们可 ...

随机推荐

  1. 一个SMMU内存访问异常的问题

    最近碰到棘手的问题: 以太网进行iperf测试时, 发生了SMMU (System Memory Management Unit)访问异常导致内核崩溃. 原本只是内部测试发现, 后面在试验车上也概率性 ...

  2. iOS比较枚举NSOrderedSame NSOrderedAscending NSOrderedDescending使用小结

    项目开发中偶然间看到这种比较枚举,之前没注意过,仔细研究了一下结果还挺有意思,我们可以用升降序相等来帮助理解比较结果. NSString 两个字符串的比较,用 a compare:b 来比,得出的结果 ...

  3. 如何在Windows 11系统中将任意文件(如bat/log等)固定在开始菜单?

    在Windows 11系统中,默认只支持将.exe/文件夹/.zip固定在开始菜单,如果想将其他文件如.bat/.log等文件固定在开始菜单将在右键菜单中找不到选项. 一个更简单的办法: 对任意文件右 ...

  4. 基于 KubeSphere 的 Nebula Graph 多云架构管理实践

    本文是杭州站 Meetup 讲师乔雷根据其分享内容整理而成的文章. 图数据库是一种使用图结构进行语义查询的数据库,它使用节点.边和属性来表示和存储数据.图数据库的应用领域非常广泛,在反应事物之间联系的 ...

  5. 【小 w 的代数】(提供一种 n^2 log 的解法)

    前言: 卖点 记录 CTH 的发言 CTH:你这真是 n^3 的 CTH:我也不知道你线段树优化个啥,\(n^3 \log n\) CTH:你优化到哪了啊 CTH:······你从赛时打这个题到现在 ...

  6. 基础控件(Button,Edittext,ImageView,ProgressBar,ToolBar,AlertDialog,PopupWindow)

    Button 触发事件 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns: ...

  7. 每日学学Java开发规范,编程规约(附阿里巴巴Java开发手册(终极版))

    前言 每次去不同的公司,码不同的代码,适应不同的规范,经常被老大教育规范问题,我都有点走火入魔的感觉,还是要去看看阿里巴巴Java开发规范,从中熟悉一下,纠正自己,码出高效,码出质量. 想细看的可以去 ...

  8. Maven的顶级功能——依赖管理

    依赖管理 依赖管理解决的问题 我们学完一个语言的语法之后,我们应该都能知道函数的引用或者类的创建等了.为了方便管理,我们就会把一些特定的功能实现写在一个代码文件中,我们只需要使用的时候导入就行了,这样 ...

  9. c语言小练习——字符串长度、拷贝、拼接、比较

    /* 使用c语言知识实现下面程序: 1,实现strlen函数的功能 2,实现strcpy函数的功能 3,实现strcat函数的功能 4,实现strcmp函数的功能 不允许使用已有的str函数*/ 1 ...

  10. Air780E量产binpkg文件如何获取

    ​ 今天我们学习Air780E量产binpkg文件如何获取: 一.背景 最近luatos开发客户增多,客户在量产烧录的时候需要binpkg文件,但是有些客户不知道binpkg文件是什么,在哪里获取,是 ...