SpringBoot结合Liquibase实现数据库变更管理
https://juejin.cn/post/7171232605478584328
https://juejin.cn/post/7170857098538909732
前言
研发过程中经常涉及到数据库变更,对表结构的修复及对数据的修改,为了保证各环境都能正确的进行变更,我们可能需要维护一个数据库升级文档来保存这些记录,有需要升级的环境按文档进行升级。
这样手工维护有几个缺点:
- 无法保证每个环境都按要求执行
- 遇到问题不一定有相对的回滚语句
- 无法自动化
为了解决这些问题,我们进行了一些调研,主要调研对象是 Liquibase 和 Flyway,我们希望通过数据库版本管理工具实现以下几个目标:
- 数据库升级
- 数据库回滚
- 版本标记
Liquibase还是Flyway
Flyway 和 Liquibase 都支持专业数据库重构和版本控制所需的所有功能,因此您将始终知道要处理的数据库模式的版本以及它是否与软件版本匹配。两种工具都集成在 Maven 或 Gradle 构建脚本中以及 Spring Boot 生态系统中,因此您可以完全自动化数据库重构。
Flyway 使用 SQL 定义数据库更改,因此您可以定制 SQL 脚本,使其与基础数据库技术(例如Oracle或PostgreSQL)良好地配合使用。另一方面,使用 Liquibase,您可以通过使用 XML,YAML 或 JSON 来定义数据库更改来引入抽象层。因此,Liquibase 更适合在具有不同基础数据库技术的不同环境中安装的软件产品中使用。
Flyway
数据库的变更可以用 SQL 或者 Java 来记录,Flyway 通过下面的步骤实现数据库变更:
- Flyway 先在数据库中检查自己的元数据表(默认为SCHEMA_VERSION)是否存在,如果没有,则创建一个;
- 检查 classpath 中所有的变更;
- 对比变更和自己的表,如果变更的版本低于或等于当前版本,不做任何变动;
- 否则,变更会按从低到高排序,并依次执行;
- 执行完,在 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 版本的压缩包,然后在本地解压,解压包存放位置为:
/Library/liquibase-4.4.3
sudo vi ~/.bash_profile,修改环境变量配置文件:
export PATH="/Library/liquibase-4.4.3:$PATH"
然后 source ~/.bash_profile,使配置文件生效。
最后执行下述命令,验证是否安装成功。
% 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对数据库结构进行管理,越来越多的问题会浮现出来。
- ChangeSet 文件同时多人在修改,自己的 ChangeSet 被改掉,甚至被删除掉。
- 开发人员将 ChangeSet 添加到已经执行过的文件中,导致执行顺序出问题。
- 开发人员擅自添加对业务数据的修改,其它环境无法执行并报错。
- ChangeSet 中 SQL 包含 schema 名称,导致其它环境 schema 名称变化时,ChangeSet 报错。
- 开发人员不小心改动了已经执行过的 ChangeSet,在启动时会报错。
Liquibase基本规范
- ChangeSet id 使用[任务ID]-[日期]-[序号],如 T100-20181009-001
- ChangeSet 必须填写 author
- Liquibase 禁止对业务数据进行 sql 操作
- 使用
<sql>时,禁止包含 schema 名称 - Liquibase 禁止使用存储过程
- 所有表,列要加 remarks 进行注释
- 已经执行过的 ChangeSet 严禁修改。
- 不要随便升级项目 liquibase 版本,特别是大版本升级。不同版本 ChangeSet MD5SUM 的算法不一样。
根据发布进行管理
- 每个发布新建一个文件夹,所有发布相关的 ChangeSet 文件以及数据初始化文件,均放在些文件夹中。
- 每个发布新建一个 master.xml。此 master.xml 中,include 本次发布需要执行的 ChangeSet 文件
- 根据开发小组独立 ChangeSet文件(可选)
- 根据功能独立 ChangeSet 文件。例如 user.xml, company.xml
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 默认是如何执行以及执行结果。
- 在启动时,LiquibaseAutoConfiguration 会根据默认配置初始化 SpringLiquibase
- SpringLiquibase.afterPropertiesSet()中执行 ChangeSet 文件
- 第一次跑 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
| | |- ...
如何在一个Spring Boot运行多个SpringLiquibase呢?
1、禁用Spring Boot自动运行Liquibase。
# application.properties
# spring boot 2以上
spring.liquibase.enabled=false
# spring boot 2以下
liquibase.enabled=false
2、修改配置项
@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 的方式来保证唯一性
<?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 的功能。
参考文献
SpringBoot结合Liquibase实现数据库变更管理的更多相关文章
- 使用 dbdeploy.net 管理数据库变更
使用 dbdeploy.net 管理数据库变更 没有包含数据库的持续集成都是假的.这可不是我说的.一直以来都没能找到一个理想的数据库变更管理工具.直到转了 java 再回来,才发现 dbdeploy ...
- springboot集成liquibase,h2数据库
Liquibase是一个用于跟踪.管理和应用数据库变化的开源的数据库重构工具.它将所有数据库的变化(包括结构和数据)都保存在XML文件中,便于版本控制. Liquibase具备如下特性:* 不依赖于特 ...
- springboot学习-jdbc操作数据库--yml注意事项--controller接受参数以及参数校验--异常统一管理以及aop的使用---整合mybatis---swagger2构建api文档---jpa访问数据库及page进行分页---整合redis---定时任务
springboot学习-jdbc操作数据库--yml注意事项--controller接受参数以及参数校验-- 异常统一管理以及aop的使用---整合mybatis---swagger2构建api文档 ...
- Oracle 数据库用户管理
Oracle 数据库用户管理 Oracle 权限设置 一.权限分类: 系统权限:系统规定用户使用数据库的权限.(系统权限是对用户而言). 实体权限:某种权限用户对其它用户的表或视图的存取权限 ...
- 企业架构研究总结(27)——TOGAF架构开发方法(ADM)之架构变更管理阶段
1.10 架构变更管理(Architecture Change Management) 企业架构开发方法各阶段——架构变更管理 1.10.1 目标 本阶段的目标是: 确保基线架构持续符合当前实际. 评 ...
- TOGAF架构开发方法(ADM)之架构变更管理阶段
TOGAF架构开发方法(ADM)之架构变更管理阶段 1.10 架构变更管理(Architecture Change Management) 企业架构开发方法各阶段——架构变更管理 1.10.1 目标 ...
- cuckoo数据库变更
1.cuckoo版本升级 cuckoo默认的数据库为sqlite,默认连接方式为sqlite:///os.path.join(REPORT_ROOT, "db", "cu ...
- 20181218-PostgreSQL数据库Extension管理
20181218-PostgreSQL数据库Extension管理 注意:在集群的一个数据库中安装扩展,在集群的另一个数据库要使用的话,仍需安装 1. 查看当前已安装Extension postgre ...
- mysql用户授权、数据库权限管理、sql语法详解
mysql用户授权.数据库权限管理.sql语法详解 —— NiceCui 某个数据库所有的权限 ALL 后面+ PRIVILEGES SQL 某个数据库 特定的权限SQL mysql 授权语法 SQL ...
- SAP 生产订单变更管理 OCM Order Changement Management
SAP OCM Order Changement Management 一.目的 订单变更管理系统是当我们的订单(生产订单.计划订单.采购订单)已经存在的时候,其物料主数据或销售数据有变更时,我们可 ...
随机推荐
- 如何快速定位 Linux Panic 出错的代码行
问题描述 内核调试中最常见的一个问题是:内核Panic后,如何快速定位到出错的代码行? 就是这样一个常见的问题,面试过的大部分同学都未能很好地回答,这里希望能够做很彻底地解答. 问题分析 内核Pani ...
- 数据库周刊57丨Oracle 2021年度安全警报;MySQL 8.0.23发布;MySQL索引优化导致的死锁案例;巨杉数据库跨引擎事务实践;MongoDB企业级能力解析;OceanBase OBCP 实验指导手册……
摘要:墨天轮数据库周刊第57期发布啦,每周1次推送本周数据库相关热门资讯.精选文章.干货文档. 热门资讯 1.Oracle 2021年度安全警报: Critical Patch Update 发布8个 ...
- dotnet定义扩展方法
// 扩展方法 // 1.创建静态类静态方法 2. 在静态方法中参数中使用 this 关键字指定需要扩展的类 // 密封类 不能直接继承,通过扩展方法,拿到父类的属性和方法进行扩展补充
- 使用AVX2指令集加速推荐系统MMR层余弦相似度计算
原文:blog.fanscore.cn/a/62/ 1. 背景 前一段时间公司上线了一套Go实现的推荐系统,上线后发现MMR层虽然只有纯计算但耗时十分离谱,通过pprof定位问题所在之后进行了优化,虽 ...
- 博客配套视频已上传至 B 站,欢迎关注
博客配套视频已上传至 B 站,欢迎关注+一键三连 链接: https://space.bilibili.com/383551518?spm_id_from=333.1007.0.0 b 站直接看 链接 ...
- Android复习(四)权限—>定义自定义应用权限
定义自定义应用权限 本文档介绍了应用开发者如何使用 Android 提供的安全功能来定义自己的权限.通过定义自定义权限,应用可以与其他应用共享其资源和功能.如需详细了解权限,请参阅权限概览. 背景 A ...
- KubeSphere 部署向量数据库 Milvus 实战指南
作者:运维有术星主 Milvus 是一个为通用人工智能(GenAI)应用而构建的开源向量数据库.它以卓越的性能和灵活性,提供了一个强大的平台,用于存储.搜索和管理大规模的向量数据.Milvus 能够执 ...
- OpenFunction 应用系列之一: 以 Serverless 的方式实现 Kubernetes 日志告警
概述 当我们将容器的日志收集到消息服务器之后,我们该如何处理这些日志?部署一个专用的日志处理工作负载可能会耗费多余的成本,而当日志体量骤增.骤降时亦难以评估日志处理工作负载的待机数量.本文提供了一种基 ...
- 【Kernel】基于 QEMU 的 Linux 内核编译和安装
目录 安装虚拟机系统 共享目录 编译内核 卸载内核 参考资料 本文主要记录个人做存储系统研究时,在 QEMU 环境下编译和安装 Linux 内核的过程 安装虚拟机系统 之前在 利用 RocksDB + ...
- 别再售卖 5块钱 的 Win10 激活码了,后果很严重
为了推广Windows 10系统(以下简称Win10),微软过去几年中一直给免费升级,Win7免费洗白的策略现在都还管用. 微软的大方也让很多人忘了Win10系统是要收费的,而且价格不便宜,国内的话, ...