在 Spring Boot 项目中,提供了对 Liquibase 的内置支持。
引言
什么是数据库版本控制?为什么要进行数据库版本控制?可参考我上一篇文章 SpringBoot 集成数据库版本管理工具 Flyway 。
Liquibase 通过在变更日志(Change Log )文件,配置每一个变更集(Change Set ),实现数据库变更的管理。
Liquibase 提供了多种格式,如下:
在 Spring Boot 中,默认配置使用 YAML Format 。所以我们在入门的示例中,也使用这种格式。
下面,就让我们开始入门 Liquibase 吧。
第一步:首先创建一个 Spring Boot 项目。 第二步:pom.xml
文件引入 Liquibase
依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > org.liquibase</groupId > <artifactId > liquibase-core</artifactId > </dependency > </dependencies >
application.yaml
配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 spring: datasource: url: jdbc:mysql://localhost:3306/github_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai driver-class-name: com.mysql.cj.jdbc.Driver username: root password: ${MYSQL_PWD:123456} liquibase: enabled: true change-log: classpath:/db/changelog/db_changelog.yaml url: ${spring.datasource.url} user: ${spring.datasource.username} password: ${spring.datasource.password}
常用:基于 SQL 的版本控制 修改 db_changelog.yaml
变更日志,增加 ID = 0
的变更集合( Change Set ) ,不执行任何 SQL 。代码如下:
1 2 3 4 5 databaseChangeLog: - changeSet: id: 0 author: yizhuo comments: 空
启动 Spring Boot 项目
1 2 3 4 5 6 @SpringBootApplication public class LiquibaseApplication { public static void main (String[] args) { SpringApplication.run(LiquibaseApplication.class , args ) ; } }
执行日志如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 2020-07-04 20:25:22.804 INFO 70787 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT COUNT(*) FROM github_demo.DATABASECHANGELOGLOCK 2020-07-04 20:25:22.885 INFO 70787 --- [ main] liquibase.executor.jvm.JdbcExecutor : CREATE TABLE github_demo.DATABASECHANGELOGLOCK (ID INT NOT NULL, `LOCKED` BIT(1) NOT NULL, LOCKGRANTED datetime NULL, LOCKEDBY VARCHAR(255) NULL, CONSTRAINT PK_DATABASECHANGELOGLOCK PRIMARY KEY (ID)) 2020-07-04 20:25:23.017 INFO 70787 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT COUNT(*) FROM github_demo.DATABASECHANGELOGLOCK 2020-07-04 20:25:23.037 INFO 70787 --- [ main] liquibase.executor.jvm.JdbcExecutor : DELETE FROM github_demo.DATABASECHANGELOGLOCK 2020-07-04 20:25:23.045 INFO 70787 --- [ main] liquibase.executor.jvm.JdbcExecutor : INSERT INTO github_demo.DATABASECHANGELOGLOCK (ID, `LOCKED`) VALUES (1, 0) 2020-07-04 20:25:23.062 INFO 70787 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT `LOCKED` FROM github_demo.DATABASECHANGELOGLOCK WHERE ID=1 2020-07-04 20:25:23.095 INFO 70787 --- [ main] l.lockservice.StandardLockService : Successfully acquired change log lock 2020-07-04 20:25:24.929 INFO 70787 --- [ main] l.c.StandardChangeLogHistoryService : Creating database history table with name: github_demo.DATABASECHANGELOG 2020-07-04 20:25:24.930 INFO 70787 --- [ main] liquibase.executor.jvm.JdbcExecutor : CREATE TABLE github_demo.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) 2020-07-04 20:25:25.007 INFO 70787 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT COUNT(*) FROM github_demo.DATABASECHANGELOG 2020-07-04 20:25:25.012 INFO 70787 --- [ main] l.c.StandardChangeLogHistoryService : Reading from github_demo.DATABASECHANGELOG 2020-07-04 20:25:25.013 INFO 70787 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT * FROM github_demo.DATABASECHANGELOG ORDER BY DATEEXECUTED ASC, ORDEREXECUTED ASC 2020-07-04 20:25:25.019 INFO 70787 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT COUNT(*) FROM github_demo.DATABASECHANGELOGLOCK 2020-07-04 20:25:25.048 INFO 70787 --- [ main] liquibase.changelog.ChangeSet : ChangeSet classpath:/db/changelog/db_changelog.yaml::0::yizhuo ran successfully in 7ms 2020-07-04 20:25:25.050 INFO 70787 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT MAX(ORDEREXECUTED) FROM github_demo.DATABASECHANGELOG 2020-07-04 20:25:25.054 INFO 70787 --- [ main] liquibase.executor.jvm.JdbcExecutor : INSERT INTO github_demo.DATABASECHANGELOG (ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, MD5SUM, `DESCRIPTION`, COMMENTS, EXECTYPE, CONTEXTS, LABELS, LIQUIBASE, DEPLOYMENT_ID) VALUES ('0', 'yizhuo', 'classpath:/db/changelog/db_changelog.yaml', NOW(), 1, '8:d41d8cd98f00b204e9800998ecf8427e', 'empty', '', 'EXECUTED', NULL, NULL, '3.8.9', '3865525034') 2020-07-04 20:25:25.101 INFO 70787 --- [ main] l.lockservice.StandardLockService : Successfully released change log lock ... 2020-07-04 20:25:25.387 INFO 70787 --- [ main] c.g.z.liquibase.LiquibaseApplication : Started LiquibaseApplication in 8.936 seconds (JVM running for 10.127)
在启动的日志中,我们看到 Liquibase 会自动创建两张表:
DATABASECHANGELOG
:数据库变更日志。每一条记录,对应记录每个变更集合( Change Set ) 的执行日志。表结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 CREATE TABLE `DATABASECHANGELOG` ( `ID` varchar (255 ) NOT NULL , `AUTHOR` varchar (255 ) NOT NULL , `FILENAME` varchar (255 ) NOT NULL , `DATEEXECUTED` datetime NOT NULL , `ORDEREXECUTED` int (11 ) NOT NULL , `EXECTYPE` varchar (10 ) NOT NULL , `MD5SUM` varchar (35 ) DEFAULT NULL , `DESCRIPTION` varchar (255 ) DEFAULT NULL , `COMMENTS` varchar (255 ) DEFAULT NULL , `TAG` varchar (255 ) DEFAULT NULL , `LIQUIBASE` varchar (20 ) DEFAULT NULL , `CONTEXTS` varchar (255 ) DEFAULT NULL , `LABELS` varchar (255 ) DEFAULT NULL , `DEPLOYMENT_ID` varchar (10 ) DEFAULT NULL ) ENGINE =InnoDB DEFAULT CHARSET =utf8mb4;
DATABASECHANGELOGLOCK
:锁记录表,用于确保多个 JVM 进程同时只能有一个可以修改数据库。表结构如下:
1 2 3 4 5 6 7 CREATE TABLE `DATABASECHANGELOGLOCK` ( `ID` int (11 ) NOT NULL , `LOCKED` bit (1 ) NOT NULL , `LOCKGRANTED` datetime DEFAULT NULL , `LOCKEDBY` varchar (255 ) DEFAULT NULL , PRIMARY KEY (`ID` ) ) ENGINE =InnoDB DEFAULT CHARSET =utf8mb4;
我们去查询一下 Mysql,如下:
并且 DATABASECHANGELOG
表多了一条数据如下(部分字段截图)
修改 db_changelog.yaml
文件,增加 ID = 1
的变更集合( Change Set ) ,如下: 1 2 3 4 5 6 7 8 - changeSet: id: 1 author: yizhuo comments: 初始化数据库表 changes: - sqlFile: encoding: utf8 path: classpath:db/changelog/sql/CHANGE_LOG_INIT_DB.sql
在 src/main/resources
下创建目录 db/changelog/sql/
,并创建 CHANGE_LOG_INIT_DB.sql
文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 CREATE TABLE `users` ( `id` int (11 ) NOT NULL AUTO_INCREMENT COMMENT '用户编号' , `username` varchar (64 ) DEFAULT NULL COMMENT '账号' , `password` varchar (32 ) DEFAULT NULL COMMENT '密码' , `create_time` datetime DEFAULT NULL COMMENT '创建时间' , PRIMARY KEY (`id` ), UNIQUE KEY `idx_username` (`username` ) ) ENGINE =InnoDB DEFAULT CHARSET =utf8mb4; INSERT INTO `users` (username, password , create_time) VALUES ('zhuo' , 'password' , now ());
再次启动项目,日志如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 2020-07-04 21:21:49.694 INFO 72084 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT COUNT(*) FROM github_demo.DATABASECHANGELOGLOCK 2020-07-04 21:21:49.746 INFO 72084 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT COUNT(*) FROM github_demo.DATABASECHANGELOGLOCK 2020-07-04 21:21:49.777 INFO 72084 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT `LOCKED` FROM github_demo.DATABASECHANGELOGLOCK WHERE ID=1 2020-07-04 21:21:49.844 INFO 72084 --- [ main] l.lockservice.StandardLockService : Successfully acquired change log lock 2020-07-04 21:21:51.881 INFO 72084 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT MD5SUM FROM github_demo.DATABASECHANGELOG WHERE MD5SUM IS NOT NULL LIMIT 1 2020-07-04 21:21:51.889 INFO 72084 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT COUNT(*) FROM github_demo.DATABASECHANGELOG 2020-07-04 21:21:51.921 INFO 72084 --- [ main] l.c.StandardChangeLogHistoryService : Reading from github_demo.DATABASECHANGELOG 2020-07-04 21:21:51.921 INFO 72084 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT * FROM github_demo.DATABASECHANGELOG ORDER BY DATEEXECUTED ASC, ORDEREXECUTED ASC 2020-07-04 21:21:52.058 INFO 72084 --- [ main] liquibase.executor.jvm.JdbcExecutor : -- 创建用户表 CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户编号', `username` varchar(64) DEFAULT NULL COMMENT '账号', `password` varchar(32) DEFAULT NULL COMMENT '密码', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 2020-07-04 21:21:52.260 INFO 72084 --- [ main] liquibase.executor.jvm.JdbcExecutor : -- 插入一条数据 INSERT INTO `users`(username, password, create_time) VALUES('zhuo', 'password', now()) 2020-07-04 21:21:52.283 INFO 72084 --- [ main] liquibase.changelog.ChangeSet : SQL in file classpath:db/changelog/sql/CHANGE_LOG_INIT_DB.sql executed 2020-07-04 21:21:52.309 INFO 72084 --- [ main] liquibase.changelog.ChangeSet : ChangeSet classpath:/db/changelog/db_changelog.yaml::1::yizhuo ran successfully in 364ms 2020-07-04 21:21:52.310 INFO 72084 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT MAX(ORDEREXECUTED) FROM github_demo.DATABASECHANGELOG 2020-07-04 21:21:52.321 INFO 72084 --- [ main] liquibase.executor.jvm.JdbcExecutor : INSERT INTO github_demo.DATABASECHANGELOG (ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, MD5SUM, `DESCRIPTION`, COMMENTS, EXECTYPE, CONTEXTS, LABELS, LIQUIBASE, DEPLOYMENT_ID) VALUES ('1', 'yizhuo', 'classpath:/db/changelog/db_changelog.yaml', NOW(), 2, '8:88a193289a4da5d24c9fde1ae442fb3d', 'sqlFile', '', 'EXECUTED', NULL, NULL, '3.8.9', '3868911933') 2020-07-04 21:21:52.377 INFO 72084 --- [ main] l.lockservice.StandardLockService : Successfully released change log lock ... 2020-07-04 21:21:52.615 INFO 72084 --- [ main] c.g.z.liquibase.LiquibaseApplication : Started LiquibaseApplication in 8.401 seconds (JVM running for 10.018) Disconnected from the target VM, address: '127.0.0.1:54078', transport: 'socket' Process finished with exit code 0
可以看到日志里执行了我们配置的初始化 SQL ,去数据库查看发现 users 表已经创建,并且 DATABASECHANGELOG
表又多了一条数据如下
我们修改一下 CHANGE_LOG_INIT_DB.sql
文件内容,新增一行注释如下
再次启动项目,会发现日志报错:
1 2 3 2020-07-04 21:34:55.789 WARN 72294 --- [ main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'liquibase' defined in class path resource [org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration$LiquibaseConfiguration.class]: Invocation of init method failed; nested exception is liquibase.exception.ValidationFailedException: Validation Failed: 1 change sets check sum classpath:/db/changelog/db_changelog.yaml::1::yizhuo was: 8:88a193289a4da5d24c9fde1ae442fb3d but is now: 8:1b6bc2489cce8400e6aa8cfd2de6725f
Liquibase 会给每个变更集合( Change Set ),计算出一个 MD5SUM
字段。这样,每次启动时,都会校验已经执行的变更集合( Change Set ),是否发生了改变。如果是,抛出异常。这样,保证不会因为 变更集合( Change Set ) 变更,导致出现问题。
测试完成,将 CHANGE_LOG_INIT_DB.sql
文件恢复原样。
再次启动项目,日志如下
1 2 3 4 5 6 7 8 9 10 11 2020-07-04 21:55:43.289 INFO 72969 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT COUNT(*) FROM github_demo.DATABASECHANGELOGLOCK 2020-07-04 21:55:43.319 INFO 72969 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT COUNT(*) FROM github_demo.DATABASECHANGELOGLOCK 2020-07-04 21:55:43.337 INFO 72969 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT `LOCKED` FROM github_demo.DATABASECHANGELOGLOCK WHERE ID=1 2020-07-04 21:55:43.371 INFO 72969 --- [ main] l.lockservice.StandardLockService : Successfully acquired change log lock 2020-07-04 21:55:45.062 INFO 72969 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT MD5SUM FROM github_demo.DATABASECHANGELOG WHERE MD5SUM IS NOT NULL LIMIT 1 2020-07-04 21:55:45.066 INFO 72969 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT COUNT(*) FROM github_demo.DATABASECHANGELOG 2020-07-04 21:55:45.080 INFO 72969 --- [ main] l.c.StandardChangeLogHistoryService : Reading from github_demo.DATABASECHANGELOG 2020-07-04 21:55:45.080 INFO 72969 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT * FROM github_demo.DATABASECHANGELOG ORDER BY DATEEXECUTED ASC, ORDEREXECUTED ASC 2020-07-04 21:55:45.113 INFO 72969 --- [ main] l.lockservice.StandardLockService : Successfully released change log lock ... 2020-07-04 21:55:45.204 INFO 72969 --- [ main] c.g.z.liquibase.LiquibaseApplication : Started LiquibaseApplication in 6.208 seconds (JVM running for 6.875)
在整个日志中,我可以看到 ID = 1
的变更集合( Change Set )不会重复执行。
基于 SQL 的版本控制介绍完毕,大部分情况下我们都只使用基于 SQL 的版本控制,但是遇到复杂的业务需要根据业务逻辑处理数据,这时基于 SQL 的版本控制可能就不够用了,下面来介绍基于 Java 类的版本控制
扩展:基于 Java 类的版本控制 在 com.github.zhuyizhuo.liquibase.sample
目录下创建 FixUsername.java
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class FixUsername implements CustomTaskChange { @Override public void execute (Database database) throws CustomChangeException { System.out.println("执行业务逻辑。。" ); } @Override public String getConfirmationMessage () { return null ; } @Override public void setUp () throws SetupException { } @Override public void setFileOpener (ResourceAccessor resourceAccessor) { } @Override public ValidationErrors validate (Database database) { return null ; } }
修改 db_changelog.yaml
变更日志,增加 ID = 2
的变更集合( Change Set ) 。代码如下:
1 2 3 4 5 6 - changeSet: id: 2 author: yizhuo comments: 自定义业务逻辑类 changes: - customChange: {class: com.github.zhuyizhuo.liquibase.sample.FixUsername}
再次启动项目。执行日志如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 2020-07-04 22:09:33.489 INFO 73234 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT COUNT(*) FROM github_demo.DATABASECHANGELOGLOCK 2020-07-04 22:09:33.537 INFO 73234 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT COUNT(*) FROM github_demo.DATABASECHANGELOGLOCK 2020-07-04 22:09:33.548 INFO 73234 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT `LOCKED` FROM github_demo.DATABASECHANGELOGLOCK WHERE ID=1 2020-07-04 22:09:33.589 INFO 73234 --- [ main] l.lockservice.StandardLockService : Successfully acquired change log lock 2020-07-04 22:09:34.875 INFO 73234 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT MD5SUM FROM github_demo.DATABASECHANGELOG WHERE MD5SUM IS NOT NULL LIMIT 1 2020-07-04 22:09:34.879 INFO 73234 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT COUNT(*) FROM github_demo.DATABASECHANGELOG 2020-07-04 22:09:34.886 INFO 73234 --- [ main] l.c.StandardChangeLogHistoryService : Reading from github_demo.DATABASECHANGELOG 2020-07-04 22:09:34.886 INFO 73234 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT * FROM github_demo.DATABASECHANGELOG ORDER BY DATEEXECUTED ASC, ORDEREXECUTED ASC 执行业务逻辑。。 2020-07-04 22:09:34.905 INFO 73234 --- [ main] liquibase.changelog.ChangeSet : null 2020-07-04 22:09:34.908 INFO 73234 --- [ main] liquibase.changelog.ChangeSet : ChangeSet classpath:/db/changelog/db_changelog.yaml::2::yizhuo ran successfully in 7ms 2020-07-04 22:09:34.909 INFO 73234 --- [ main] liquibase.executor.jvm.JdbcExecutor : SELECT MAX(ORDEREXECUTED) FROM github_demo.DATABASECHANGELOG 2020-07-04 22:09:34.912 INFO 73234 --- [ main] liquibase.executor.jvm.JdbcExecutor : INSERT INTO github_demo.DATABASECHANGELOG (ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, MD5SUM, `DESCRIPTION`, COMMENTS, EXECTYPE, CONTEXTS, LABELS, LIQUIBASE, DEPLOYMENT_ID) VALUES ('2', 'yizhuo', 'classpath:/db/changelog/db_changelog.yaml', NOW(), 3, '8:39259f2c0bad8428733b5b6c30d30a0a', 'customChange', '', 'EXECUTED', NULL, NULL, '3.8.9', '3871774892') 2020-07-04 22:09:34.943 INFO 73234 --- [ main] l.lockservice.StandardLockService : Successfully released change log lock ...
我们可以看到,ID = 2 的变更集合( Change Set )完成了执行。
此时,我们去查询下 MySQL 。如下:
代码示例 本文的完整工程可以查看下面仓库中的spring-boot-samples-liquibase
目录:
如果您觉得本文不错,欢迎Star
支持,您的关注是我坚持的动力!