Liquibase(数据库版本控制工具)
在《快速开始》篇已经跑通 ContiNew Admin 项目的同学一定知道,ContiNew Admin 项目首次启动前,无需手动执行 SQL 脚本来创建数据库表及导入初始数据,仅需配置好数据库名后启动程序就行。
实际上,这得益于 ContiNew Admin 集成的 Liquibase 组件,项目启动后,Liquibase 会在指定数据库中自动建表并初始化数据。
当然了,Liquibase 的功能远不止于此,本篇将对它进行简要介绍。
简介
Liquibase 是一个开源的数据库版本控制工具,主要用于跟踪、管理和应用数据库变化。它支持多种数据库系统,包括 MySQL、PostgreSQL、Oracle、SQL Server 等,并且能够以声明性的方式定义数据库模式的变化,确保这些变化可以以可重复的方式应用。
我们在项目实际开发时,至少拥有三套环境,一般为 dev 开发环境、test/uat 测试环境、prod 生产环境,每套环境的数据库往往都需要随着项目的迭代进行同步更新。在没有使用数据库版本控制工具之前,这些环境的数据库变更通常依赖于开发人员手动执行 SQL 脚本,或者通过数据库迁移工具来管理。然而,手动执行 SQL 脚本容易出错,难以追踪变更历史,且难以保证不同环境间的一致性。
Liquibase 通过其独特的变更日志(changelog)机制,很好地解决了这些问题。变更日志以 XML、JSON、SQL 或 YAML 格式的文件存储,详细记录了每一次数据库变更的内容,包括新增表、修改表结构、添加索引、添加外键约束等操作。这些变更日志可以被 Liquibase 自动解析和执行,确保数据库结构在不同环境间能够保持一致。
集成组件
引入依赖
在项目中集成 Liquibase 非常简单,首先,我们在 pom.xml
中引入依赖。(不需要指定版本,Spring Boot 已经默认指定了其版本)
<dependencies>
<!-- Liquibase(用于管理数据库版本,跟踪、管理和应用数据库变化) -->
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
</dependencies>
配置
Liquibase 配置前还需要配置好数据源,这个就不介绍了。
在 application.yml
中添加 Liquibase 配置,指定 changelog 文件路径,启用 Liquibase。
## Liquibase 配置
spring.liquibase:
# 是否启用
enabled: true
# 配置文件路径
change-log: classpath:/db/changelog/db.changelog-master.yaml
编写 CHANGELOG
在 resources
目录下创建 db/changelog
目录,然后在这个目录下创建 db.changelog-master.yaml
文件。这个文件主要是为了集中引入分散的 CHANGELOG 文件。
databaseChangeLog:
- include:
file: db/changelog/table.sql
然后根据需要,创建对应的 CHANGELOG 文件,上方简介也提到过了,CHANGELOG 文件可以是 XML、JSON、YAML、SQL 格式文件,本篇以 ContiNew Admin 项目里的使用格式 SQL 举例。如果你需要使用其他格式,可以翻阅官方资料,学习相应语法。SQL 格式的优势可复制性和可阅读性强,但是如果要存在多套不同的数据库,那用 XML 这类格式更好一些,因为它们用声明式语法来指定 changelog,这样方便迁移和兼容不同数据库。而 SQL 就比较限定了某一种数据库语法格式,有利有弊,根据你自己需要选择。
创建 table.sql
文件,添加如下注释。
-- liquibase formatted sql
然后还是根据需要添加 SQL 内容,本篇还是以 MySQL 语法为举例添加一个菜单表结构,用来创建菜单表。
核心就是 -- changeset
这个语法,每次你要添加数据库表变更,都需要用它来开头,至于后面的内容就是作者等信息。
-- comment
可以为这次 changelog 记录增加注释。
-- liquibase formatted sql
-- changeset charles7c:1
-- comment 初始化表结构
CREATE TABLE IF NOT EXISTS `sys_menu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`title` varchar(30) NOT NULL COMMENT '标题',
`parent_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '上级菜单ID',
`type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型(1:目录;2:菜单;3:按钮)',
`path` varchar(255) DEFAULT NULL COMMENT '路由地址',
`name` varchar(50) DEFAULT NULL COMMENT '组件名称',
`component` varchar(255) DEFAULT NULL COMMENT '组件路径',
`redirect` varchar(255) DEFAULT NULL COMMENT '重定向地址',
`icon` varchar(50) DEFAULT NULL COMMENT '图标',
`is_external` bit(1) DEFAULT b'0' COMMENT '是否外链',
`is_cache` bit(1) DEFAULT b'0' COMMENT '是否缓存',
`is_hidden` bit(1) DEFAULT b'0' COMMENT '是否隐藏',
`permission` varchar(100) DEFAULT NULL COMMENT '权限标识',
`sort` int NOT NULL DEFAULT 999 COMMENT '排序',
`status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:启用;2:禁用)',
`create_user` bigint(20) NOT NULL COMMENT '创建人',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE INDEX `uk_title_parent_id`(`title`, `parent_id`),
INDEX `idx_parent_id`(`parent_id`),
INDEX `idx_create_user`(`create_user`),
INDEX `idx_update_user`(`update_user`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜单表';
启动程序
这么添加完成后,第一次启动程序,Liquibase 会在对应数据库首先自动创建好两张表。
- databasechangelog(记录表,很关键)
- databasechangeloglock(锁表,无需关心)
创建好这两张表后,就会检测 CHANGELOG 文件,根据文件内容自动执行相应表创建等变更操作,操作完后,databasechangelog
文件就
ID | AUTHOR | FILENAME | DATEEXECUTED | ... | MD5SUM | COMMENTS |
---|---|---|---|---|---|---|
1 | charles7c | db/changelog/table.sql | 2024-12-30 20:35:21 | 9:8e4080d1f7a635035839b7df0c02ef9c | 初始化表结构 |
相信聪明的你,看到这张表内容后,一下子就明白了吧。原理很简单的,按照 Liquibase 语法来写变更日志,写好后,每次启动程序 Liquibase 检测有没有需要执行的 changelog,有就执行,然后插入对应的执行记录,并且为文件生成一个 MD5 值,这个 MD5 值也是检测文件有没有修改之前内容的关键。
因为,它要防止老六在写着写着后面的 changeset,还会去把前面已经执行过的 changeset 内容改掉。这是破坏性操作,是不被允许的,正常你只能继续往下追加 -- changeset
。
常见问题
像上面提到的,如果有老 6 不老老实实追加 changeset,而是改动之前执行过的内容,那么在启动程序后就会报类似如下错误。
Unsatisfied dependency expressed through bean property 'sqlSessionTemplate': Error creating bean with name 'liquibase' defined in class path resource [org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration$LiquibaseConfiguration.class]: Validation Failed:
1 changesets check sum
db/changelog/xxx/continew-admin_xxx.sql::1::Charles7c was:
8:1181b1e4347607c6084736f1a9c27327 but is now: 8:382915a29ba9c391a43b7b346e5fb6b3
...
简单来说就是对比 MD5 发现和以前的不一样了,那 Liquibase 就会罢工。
如果要解决呢,解决方法有两个:
- 如果你没什么重要资料,清库,把所有内容删掉重新启动,重新执行最简单
- 将提示的最新 MD5 值替换到对应记录里,这样启动就不报错了,但是你改动的之前的内容,它肯定不会去执行的,需要你自己手动去更新最近的 SQL 变更
温馨提示
ContiNew 项目团队没有老 6,我们也希望能尽可能使用 changeset 追加变更,以方便你们升级 SQL。
但有时候强迫症,或者我们也会定期几个版本进行一次 SQL 压缩合并来保持代码简洁。这样难免会调整之前执行过的内容,所以如果这时候你 pull 下了最新代码,启动程序执行,就会抛出如上错误。
但我相信你能理解,毕竟这是一个开源模板项目,而且代码简洁干净是我们的追求,但我们尽力压迫强迫症,延长压缩 SQL 的版本周期。