将 liquibase 添加到现有 Spring Boot 项目的正确方法
Posted
技术标签:
【中文标题】将 liquibase 添加到现有 Spring Boot 项目的正确方法【英文标题】:Correct way to add liquibase to an existing spring boot project 【发布时间】:2019-06-15 18:15:14 【问题描述】:我有一个现有的 spring boot 项目和一个相同的数据库。现在,我想添加 liquibase 来处理进一步的数据库迁移。执行此操作的正确步骤是什么?
我已关注this article 添加 liquibase 并生成更改日志。我发现的大多数文章都在谈论从头开始在项目中使用 liquibase,或者对实现没有太详细的描述。到目前为止,我已经完成了以下工作:
在 pom.xml 中添加了依赖和插件
<dependencies>
//..other dependencies
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>3.6.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>3.6.2</version>
<configuration>
<propertyFile>src/main/resources/liquibase.properties</propertyFile>
</configuration>
</plugin>
</plugins>
</build>
在 src/main/resources 下添加 liquibase.properties 文件
url=jdbc:mysql://localhost:3306/demodb
username=root
password=root
driver=com.mysql.jdbc.Driver
outputChangeLogFile=src/main/resources/db/changelog/changes/demodb-changelog.xml
更新了 src/main/resources 下的 application.properties 文件以处理变更日志
#Hibernate
spring.datasource.url=jdbc:mysql://localhost:3306/demodb
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
#Jpa
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
#Liquibase
spring.liquibase.change-log=classpath:db/changelog/db.changelog-master.xml
在 src/main/resources/db/changelog 下创建了db.changelog-master.xml
文件
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.liquibase.org/xml/ns/dbchangelog/1.9
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd">
</databaseChangeLog>
运行 spring boot 应用程序,以便在数据库中创建两个新表 - DATABASECHANGELOG
(此时为空)和 DATABASECHANGELOGLOCK
(此时只有一个空条目)
从终端生成demodb-changelog.xml
文件以创建数据库当前状态的更改日志
mvn liquibase:generateChangeLog
然后在执行时同步当前的更改日志,在liquibase.properties
中添加:
changeLogFile=src/main/resources/db/changelog/changes/demodb-changelog.xml
然后从终端跑:
mvn liquibase:changelogSync
现在,DATABASECHANGELOG
表包含执行的变更日志条目。
接下来在db.changelog-master.xml
文件中,添加生成的文件:
<include file="db/changelog/changes/demodb-changelog.xml"/>
现在,当我运行应用程序时,我得到了异常:
Caused by: liquibase.exception.MigrationFailedException:
Migration failed for change set db/changelog/changes/demodb-changelog.xml
Reason: liquibase.exception.DatabaseException: Table 'abc' already exists
因此,这是尝试再次运行更改日志文件。如何配置为仅运行那些尚未运行的变更集?我以为DATABASECHANGELOG
的作用是处理已经执行的changeset,但我想我这里错了。
我可以在没有db.changelog-master.xml
中的include
标记的情况下运行该应用程序,但我想所有变更日志文件都需要在此处列出,因为如果我要在另一台机器上运行此应用程序,我将需要所有变更日志文件从头开始创建整个数据库。
那么如何配置 liquibase 只运行尚未执行的变更日志?
【问题讨论】:
您是否尝试将生成的文件包含在 changelog-master 中,然后运行 changeLogSync? 【参考方案1】:这里的问题是filename
字段存储在DATABASECHANGELOG
表中。
Liquibase 不仅通过id
或id
和author
确定changeSet
的身份,而是根据所有三个字段的值确定changeSet
的身份:强>id
,author
,filename
。
当您运行mvn liquibase:update
或mvn liquibase:changelogSync
时,您的变更日志的filename
是src/main/resources/db/changelog/changes/demodb-changelog.xml
。这是 liquibase 存储在每个表行的 DATABASECHANGELOG.FILENAME
字段中的值。
但是,当你启动你的 spring-boot 应用程序时,你的 changelog 的文件名是classpath:db/changelog/db.changelog-master.xml
,并且包含在其中的 changelog 的文件名是db/changelog/changes/demodb-changelog.xml
。
然后,Liquibase 检查已经应用了哪些变更集。
它检查DATABASECHANGELOG
表中每个变更集的id
、author
和filename
以及db/changelog/changes/demodb-changelog.xml
文件中的每个变更集,发现没有匹配项,因为filename
字段不同。在数据库中,它的值为src/main/resources/db/changelog/changes/demodb-changelog.xml
。
src/main/resources/db/changelog/changes/demodb-changelog.xml != db/changelog/changes/demodb-changelog.xml
因此,Liquibase 认为这些是不同的变更集并尝试应用它们。它失败了,因为表已经存在。但是,如果您将变更集更改为可以多次应用的内容,您会看到 Liqibase 为 DATABASECHANGELOG
表中的行创建了一个新集,具有相同的 id
、相同的 author
但不同的 filename
...
老实说,我不知道如何解决这个问题。它看起来像是 Spring Boot 中的 Liquibase 如何工作的一个基本问题。当在 spring-boot 应用程序启动时读取更改日志文件时,它们必须在类路径中,尤其是在使用默认 fat-jar 时。但是当您将 Liquibase 作为 maven 插件运行时,它们通常位于本地文件系统上(这不是真的,请参阅我的“更新 2”)。
也许存在一些解决方法,请在评论中添加,我会更新答案。
更新
我发现的一种解决方法是使用<databaseChangeLog>
标记的logicalFilePath
属性。
用于在创建变更集的唯一标识符时覆盖文件名和路径。移动或重命名更改日志时需要。
如果您在所有变更日志/变更集中仔细设置它,它应该可以工作。
db/changelog/changes/demodb-changelog.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.8.xsd"
logicalFilePath="db/changelog/changes/demodb-changelog.xml">
</databaseChangeLog>
但请注意<include>
标签。它支持包含原始*.sql
文件。而且你不能为他们设置logicalFilePath
。并且在<databaseChangeLog>
标签上设置的logicalFilePath
也不会被继承。
更新 2
我找到了一个更强大的解决方案。
只是不要在maven插件的changeLogFile
属性中指定前缀src/main/resources
。这样 maven 将在类路径中搜索更改日志,而不是在文件系统中。因此,在执行 liquibase maven 插件之前,您必须先执行mvn process-resources
。
因此,您的 liquibase maven 插件(liquibase.properties
文件)的 changeLogFile
属性应如下所示:
changeLogFile=db/changelog/changes/demodb-changelog.xml
然后,当你运行时
mvn process-resources liquibase:changelogSync
Liquibase 将在DATABASECHANGELOG
表中创建记录,其中FILENAME
字段等于db/changelog/changes/demodb-changelog.xml
,等于文件名,spring-boot 在启动时运行迁移时使用(classpath:
前缀自动被 Liquibase 剥离时它比较文件名)。最后,这让 Liquibase 明白,这些是同一组变更集,并且已经应用了。
【讨论】:
您在 Update 2 中的解决方案有效,并且是我正在玩的一个测试项目中的解决方案。然而,有一个有趣的怪癖。仅在 Spring Boot 项目中使用 liquibase-core 会导致 liquibase 在 mvn process-resources 阶段自动在更改日志上运行,而不会输出任何表明它正在这样做的日志。【参考方案2】:MySQL 会在表中维护 liquibase 的日志,
Liquibase 使用 DATABASECHANGELOG 表来跟踪哪些变更集已经运行。
每当变更日志运行时,对于每个变更日志,都会在表中添加一行,根据 ids 我们可以知道运行了哪些变更日志,
有一些标志,如runAlways
,runOnChange
,这些将帮助我们执行/不执行。
可以参考:
For understanding flags:
https://www.liquibase.org/documentation/changeset.html
For understanding DATABASECHANGELOG Table:
https://www.liquibase.org/documentation/databasechangelog_table.html
【讨论】:
在生成变更日志时,我是否可以在所有变更集上设置 runAlways="false" 或 runOnChange="true"?还是应该手动完成? runOnChange 为真。有时你必须根据情况删除表中的行以上是关于将 liquibase 添加到现有 Spring Boot 项目的正确方法的主要内容,如果未能解决你的问题,请参考以下文章
如何将 Spring WebSecurityConfig 添加到现有项目
将 Spring Security 添加到现有 Spring AngularJS 应用程序