Liquibase 具有执行锁,已经执行过的内容不会重复执行。在执行 changeSet 时,由于改动的内容可以通过 Liquibase 提供的标签编写,所以无关具体的数据库产品(MySQL、Oracle 等),Liquibase 底层会根据实际使用的数据库类型转化为对应的 SQL。
优雅哥 SpringBoot 2.7 .2 实战基础 - 05 -使用 Liquibase 管理数据库版本
在企业开发中,数据库版本管理好像是一个伪命题,大多项目都是通过 Power Designer 之类的工具建模、生成 SQL 语句,然后去数据库中执行。在开发过程中如果遇到修改表结构,再补充修改表结构的语句,大家依次去执行,在本地及各个环境中同步表结构。但这种模式,在我参与过的项目中或多或少都出现过问题:忘记同步表结构,导致在服务启动或运行时出错。
1 Liquibase 介绍
SpringBoot 官方文档中推荐了两款工具来管理数据库版本:
Flyway
和
Liquibase
。前者我没有在项目中使用过,所以本文就只讨论 Liquibase。
使用 Liquibase 需要定义一堆 XML 文件,这些 XML 称为 changelog 文件。每个 changelog 文件中又包含多个变化集合 changeSet,每个 changeSet 记录了作者、改变的内容。changeSet 中要修改的内容,通过
createTable
、
addColumn
等标签进行操作。通过这种 XML 文件的方式,就可以将代码版本与数据库版本关联在一起。项目启动,会自动执行 changelog XML 文件。Liquibase 具有执行锁,已经执行过的内容不会重复执行。在执行 changeSet 时,由于改动的内容可以通过 Liquibase 提供的标签编写,所以无关具体的数据库产品(MySQL、Oracle 等),Liquibase 底层会根据实际使用的数据库类型转化为对应的 SQL。
通过上面的描述,可以看出 Liquibase 带来的几个好处:
支持多类型的数据库产品,无需维护 SQL 脚本;
项目启动可以自动升级数据库;
代码版本与数据库版本关联在一起。
2 在老项目中使用 Liquibase
在咱们的 demo
hero-springboot-demo
中,之前已经手动通过 SQL 语句创建了数据库表
computer
,现在想通过 Liquibase 来管理数据库版本和维护表结构,该怎么办呢?本节就通过这个案例来说明已存在的老项目中如何引用 Liquibase。
2.1 配置 Maven 插件
Liquibase 提供了 Maven 插件,使用该插件可以根据数据库逆向生成 changlog 文件。在 pom.xml 的
plugins
下添加 Liquibase 插件:
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>4.9.1</version>
<configuration>
<propertyFileWillOverride>true</propertyFileWillOverride>
<outputChangeLogFile>temp/temp-changelog.xml</outputChangeLogFile>
<driver>com.mysql.cj.jdbc.Driver</driver>
<url>jdbc:mysql://127.0.0.1:3306/hero_springboot_demo?useUnicode=true&characterEncoding=utf8&useSSL=true</url>
<username>root</username>
<password>Mysql.123</password>
<outputFileEncoding>UTF-8</outputFileEncoding>
<verbose>true</verbose>
<diffTypes>tables, views, columns, indexs,foreignkeys, primarykeys, uniqueconstraints, data</diffTypes>
</configuration>
</plugin>
上面配置 Liquibase 的 Maven 插件:数据库连接信息和Liquibase生成规则配置。生成的文件路径为 temp 目录下的 temp-changelog.xml,Liquibase 不会自动生成文件夹,需要手动在项目根目录下创建 temp 目录。
2.2 逆向生成 changelog
在控制台执行 mvn liquibase:generateChangeLog
或者在界面上执行 generateChangeLog:
执行后查看 temp/temp-changelog.xml 文件:
生成的这个文件就是 changelog 文件,该文件中包括两个 changeSet:第一个是创建表结构;第二个初始化数据。
在第一个 changeSet 的 createTable 中,对 id 字段配置了自增属性 autoIncrement:
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true"/>
</column>
不知道为啥,我这不生效。要使该字段自增,要用 addAutoIncrement
标签。第一个 changeSet 需要添加上该标签,添加后如下:
<changeSet id="T100-20220801-yyg-001" author="yyg">
<createTable remarks="电脑" tableName="computer">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="size" remarks="尺寸" type="DECIMAL(4, 1)"/>
<column name="operation" remarks="操作系统" type="VARCHAR(32)"/>
<column name="year" remarks="年份" type="VARCHAR(4)"/>
</createTable>
<addAutoIncrement tableName="computer" columnName="id" columnDataType="BIGINT"/>
</changeSet>
获取到历史表结构及数据的 changelog 文件后,接下来的步骤与 SpringBoot 整合 Liquibase 一致。
3 在 SpringBoot 中使用 Liquibase
3.1 添加依赖
在 pom.xml
文件中添加 liquibase 依赖 liquibase-core
,该依赖版本号在 spring-boot-dependencies
中已定义,直接添加依赖即可。
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
3.2 添加 changelog
在 src/main/resources
创建目录 db
, db
目录用来存放 Liquibase 相关的 changelog 文件。
db
目录下还可以按模块创建其他目录,由于我们这里只有一个 computer 类,属于 demo 演示,故就在 db
目录下创建子目录 demo
, db/demo/
目录就存放 demo 演示的所有 changelog 配置文件。将前面 Liquibase 逆向生成的 temp-changelog.xml 文件移动到 db/demo/
目录下,并重命名为 demo-changelog-v1.xml
。
在 db
目录下创建 changelog-master.xml
文件,该文件为主配置文件,作用就是引入所有模块的 changelog 文件:
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<includeAll path="demo" relativeToChangelogFile="true"/>
</databaseChangeLog>
除了根节点,里面就一句话,表示将当前路径下 demo
目录中的 changelog 文件都引入。假设 db
目录下还有其他模块(目录),继续通过 <includeAll>
元素引入即可。
src/main/resources 中文件目录结构如下:
src/main/resources/
|- db/
|- demo/
|- demo-changelog-v1.xml
|- changelog-master.xml
3.3 changeSet
回过头来看 demo-changelog-v1.xml
文件,里面的内容是之前生成的,前面讲过包括两个 changeSet
。changeSet
用来定义对数据库的表更操作,包括:
表结构的操作:创建表、删除表、修改表结构(添加列、删除列等)
表数据的操作:表中数据的增删改查
视图的操作、索引的操作等
几乎只要是对数据库的操作,都可以写在 changeSet 中。当服务启动的时候,会自动执行主配置文件中包含的所有 changeSet。需要特别注意:changeSet 一经执行,就不能修改!! 虽然 changeSet
元素有一个属性 runOnChange
,非常不建议乱用。如果要修改 changeSet里面的内容怎么办呢?重新写一个 changeSet,在里面编写要修改的内容。例如在第一个 changeSet 中使用 <createTable>
创建表,表中有一个列名为 field
,在该 changeSet 执行后(成功启动过服务),想将该列列名 field 修改为 f,这时候不能直接修改第一个 changeSet,而是要写第二个 changeSet,通过 <renameColumn>
来修改列名,然后重启服务。
changeSet
元素有两个必填的属性 author
和 id
。author
表示作者,当前的是谁定义的这个changeSet,就填谁的名字,这样便于追溯。 id
要求唯一,我在项目开发中,id一般按照这个规则:[任务ID]-[日期]-[作者]-[序号]
,如 T100-20220801-yyg-001
。
通常小版本更新(如字段级别的变更),就在同一个文件中追加新的 changeSet 即可。一个 changelog 文件可以包括多个 changeSet,每个 changeSet 中可以包括多个语句,如多个 createTable、insert 等。
大版本更新,就重新编写 changeLog 文件,如 demo-changelog-v2.xml
。
按照上面所讲,我们修改一下 demo-changelog-v1.xml 中的两个 changeSet 的 id 和 author:
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
1. id使用[任务ID]-[日期]-[作者]-[序号],如 T100-20220801-yyg-001
2. 必须填写author
3. 所有 表、列 必须加remarks进行注释
4. 已经执行过的ChangeSet严禁修改
<changeSet id="T100-20220801-yyg-001" author="yyg">
<createTable remarks="电脑" tableName="computer">
</createTable>
</changeSet>
<changeSet id="T100-20220801-yyg-002" author="yyg">
</changeSet>
</databaseChangeLog>
3.4 添加配置
在 application.yml 中添加 liquibase 的配置:
spring:
liquibase:
enabled: true
drop-first: false
change-log: classpath:/db/changelog-master.xml
上述配置开启 liquibase,并指定主文件的路径。
3.5 启动服务测试
现在启动服务,会发现服务启动失败,而且控制台会出现如下提示:
Reason: liquibase.exception.DatabaseException: Table 'computer' already exists
此时需要删除数据库中的表,然后重新启动服务。
服务启动成功后,会自动创建两张 liquibase 相关的表:DATABASECHANGELOG
和 DATABASECHANGELOGLOCK
表。
现在尝试在 demo-changelog-v1.xml
中编写第三个 changeSet:
<changeSet id="T100-20220801-yyg-003" author="yyg">
<addColumn tableName="computer">
<column name="color" type="VARCHAR(8)" defaultValue="red" remarks="颜色"/>
</addColumn>
</changeSet>
这个 changeSet 为 computer 表新增列 color,默认值为 red。重启服务,服务启动后查看 computer 表:
已新增列 color
color 默认值已设置为 red
这样便成功在 SpringBoot 中使用 liquibase。
4 常见问题
4.1 Waiting for changelog lock
如果启动服务时,控制台提示如下信息:
Liquibase - Waiting for changelog lock
Waiting for changelog lock....
通常是由于 Liquibase 在重构数据库时使数据库死锁。解决方法如下:
1 查看锁住数据库的id:
SELECT * FROM DATABASECHANGELOGLOCK where LOCKED = true;
2 解锁:
UPDATE DATABASECHANGELOGLOCK
SET locked=0, lockgranted=null, lockedby=null
WHERE id={id}
{id} 为第一步中查询出来对应记录的id。
4.2 主键自增无效
前面已经谈到,需要配置 addAutoIncrement
标签
<addAutoIncrement tableName="xxx" columnName="xx" columnDataType="BIGINT"/>
4.3 默认值无效
与主键自增无效类似,为 column
设置默认值 defaultValue
、defaultValueNumeric
也无效。
设置默认值需要使用标签 addDefaultValue
:
<addDefaultValue tableName="xxx" columnName="xx" defaultValueNumeric="0"/>
Liquibase 还有很多强大的功能,就留给大家在使用过程中一步一步探索吧。
今日程序员优雅哥(/ youyacoder;heroyyg@126.com)学习到此结束~~~