liquibase 使用示例
背景及前言
liquibase是一款数据库版本管理工具,可以辅助对数据库版本(主要是更新的sql版本)作相应管理。完善的数据库版本管理,建议仍然以pdm为基础,liquibase(而且是sql形式,而不是xml等)为辅助进行。
网上文章较多,但多为springboot搭建,程序启动后自动执行。个人认为,此方式对复杂场景(不同类型数据库,产品存在多客户定制化数据库等)并不适合。本文谨以命令行方式尝试对以上场景进行处理。
参考文章:
liquibase中文教程:https://blog.csdn.net/u012934325/article/details/100652805
博客园文章:https://www.cnblogs.com/sanri1993/p/12125280.html
1、原理介绍
如果没有liquibase,我们该怎么做数据库版本管理
数据库管理无非是为了解决以下几个问题:
- 某功能的数据库是怎么设计的,有哪几张表,大概有什么字段,相互之间怎么关联
- 每个表字段的含义是啥,尤其对于状态之类的的带枚举值的字段;是否可空
- 数据库的初始化数据有哪些
- 后来,因为什么原因,什么人什么时间,对什么地方做了什么改动
在项目开发中,如果以上问题均能准确的回答出来,可以说数据库管理是做的不错的。
liquibase的解决办法(原理)如下:
- 人工维护数据库的变更文档changelog,可以是xml、json、yaml或者sql格式;文档中每个更改都有唯一的id;
- liquibase读取changelog文档,并在数据库执行;同时将已执行的脚本id记录到表liquibasechangelog中;
说明:
changelog支持的格式很多,但经尝试,xml等并不能兼容多种数据库,仍然建议优先采用sql方式(而且此方式的changelog相对比较简洁!);
liquibase还维护另一张表liquibasechangeloglock,顾名思义,锁表,用于避免多个liquibase客户端同时修改数据库;
liquibase生成的sql不考虑顺序问题,此问题需要人工干预,手动修改changelog文件;
liquibase以changelog的路径以及脚本的id跟author作为判断语句是否执行的id,所以,相同文件放在不同目录,是可以多次执行的(可以设置相同的逻辑id解决此问题);
不难看出,liquibase主要解决了什么人、什么时间做了什么改动的问题,对于初始化数据,字段含义等也能作相应管理,但对数据库的整体设计,直观展现方面无能为力,强烈建议使用pdm等数据库设计工具予以辅助!
2、使用示例
1、下载客户端,配置环境
下载页面:https://download.liquibase.org/
下载 .zip 或者.tar.gz文件,然后解压
确保本地已安装jdk环境
2、创建初始化脚本并更新到数据库
下载并解压客户端,假设目录为myLiquibase,在此目录创建liquibase.properties,内容如下:
# mysql config
driver: com.mysql.cj.jdbc.Driver
classpath: ./lib/mysql-connector-java-8.0.11.jar
url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=UTC&autoReconnect=true&allowPublicKeyRetrieval=true&useOldAliasMetadataBehavior=true
username: root
password: abcd1234
changeLogFile: myChangeLog.xml
liquibase.hub.mode=off
注意,先下载相应的数据库驱动,放到lib目录,并如实配置classpath等;
在myLiquibase目录,创建myChangeLog.xml,内容如下:
<?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">
<include file="/sql/create_table.xml" relativeToChangelogFile="true"></include>
<include file="/sql/init_data.sql" relativeToChangelogFile="true"></include>
</databaseChangeLog>
create_table.xml内容如下:
<?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">
<changeSet id="1" author="bob">
<createTable tableName="department">
<column name="id" type="int">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="name" type="varchar(50)">
<constraints nullable="false"/>
</column>
<column name="active" type="varchar(1)" defaultValue="Y"/>
</createTable>
</changeSet>
</databaseChangeLog>
init_data.sql如下:
-- liquibase formatted sql
-- changeset jack:2022_04_2
insert into department values(1, '研发部', 'Y');
insert into department values(2, '销售部', 'Y');
-- changeset tom:2022_04_22
insert into department values(3, '后勤部', 'Y');
执行命令
./liquibase update
执行结果
需要注意的是: sql文件一定要以-- liquibase formatted sql开头!!否则,可能会报语法错误等异常(实际sql语法正常,这个抛出的异常提示非常扯淡)
查看数据库
可以看到,表确实创建了,数据也确实插入了,而且支持xml跟sql文件混合。对了,databasechangelog这个表的数据长啥样子?
不难看出,这里ID是版本号,AUTHOR是文件里作者,FILENAME就是文件路径。
一句话,原理就是:文件里按照固定格式标记版本号,程序执行的时候在数据库记录版本号。
以上为第一次执行,后续的sql文件改了,该怎么执行?
继续 ./liquibase update 即可,比如我们新增一行,将init_data.sql改为:
-- liquibase formatted sql
-- changeset jack:2022_04_2
insert into department values(1, '研发部', 'Y');
insert into department values(2, '销售部', 'Y');
-- changeset tom:2022_04_22
insert into department values(3, '后勤部', 'Y');
-- changeset tom:2022_04_24
insert into department values(4, '战略规划部', 'Y');
执行结果
可以看到,只执行了我们新增的changeset;数据库的数据也是正确的,不再截图。
3、数据迁移
1、生成已有库的结构文件
./liquibase --changeLogFile="sql/my_test_table.mysql.sql" generateChangeLog
注意文件的格式必须是.数据库类型.sql结尾,比如.oracle.sql或者.mysql.sql,否则会报错;且该数据库类型必须跟真实数据库类型一致,否则会报错,文件为空;
- 能否生成带数据的文件?
找到一个maven的,未验证 https://itwenti.com/?p=1021
(未验证)
对当前数据库状态生成 changlog
mvn liquibase:generateChangeLog
只对数据生成 changelog ,(先用别的方式往数据库创建数据后再用此方式生成changelog)
mvn liquibase:generateChangeLog -Dliquibase.diffTypes=data
2、生成两个库的差异化(结构)文件
配置文件如下:
# mysql config
driver: com.mysql.cj.jdbc.Driver
classpath: ./lib/mysql-connector-java-8.0.11.jar
url: jdbc:mysql://127.0.0.1:3306/test2?useSSL=false&serverTimezone=UTC&autoReconnect=true&allowPublicKeyRetrieval=true&useOldAliasMetadataBehavior=true
username: root
password: abcd1234
# 待比较库
referenceDriver: com.mysql.cj.jdbc.Driver
referenceUrl: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=UTC&autoReconnect=true&allowPublicKeyRetrieval=true&useOldAliasMetadataBehavior=true
referenceUsername: root
referencePassword: abcd1234
liquibase.hub.mode=off
直连的为目标库(待升级库),reference连接待比较库。reference没有配置classpath。支持跨数据库种类进行比较。
执行命令:
./liquibase --changeLogFile="sql/my_diff_table.mysql.sql" diffChangeLog
执行结果:
这里有个提示:BEST PRACTICE: The changelog generated by diffChangeLog/generateChangeLog should be inspected for correctness and completeness before being deployed. Some database objects and their dependencies cannot be represented automatically, and they may need to be manually updated before being deployed.
google翻译结果:最佳实践:在部署之前,应检查 diffChangeLog/generateChangeLog 生成的变更日志的正确性和完整性。 某些数据库对象及其依赖项无法自动表示,可能需要在部署之前手动更新。
就是说,生成的这个sql可能会有问题,最好人工检查一遍。
3、执行差异文件
修改changelog配置,将差异文件包含其中,然后
./liquibase update
总结:
liquibase就是按照约定格式记录了数据库更新语句,然后在执行的时候在数据库记录了相关id,更新时对id进行比较。支持跨数据库类型比较数据结构,但生成的结构文件不保证绝对正确。支持回滚(需要配置回滚语句)或者执行指定更新;
需要跨数据库类型更新的,需要人工配置两套脚本,在兼容性方面需要人工处理;
对于生成差异文件带数据的,应该是支持的(网上有,未验证),因为不可能把某数据库的数据全部导出,意义不大;多数情况下,我们需要的是程序运行的基础必须数据,这种一般手动维护即可。