将liquibase添加到现有Spring Boot项目的正确方法

时间:2019-01-22 09:54:09

标签: spring-boot database-migration liquibase

我有一个现有的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(目前只有一个空条目)< / p>

从终端生成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的功能是处理已执行的变更集,但是我想我错了。

我可以在没有include中的db.changelog-master.xml标记的情况下运行该应用程序,但是我想所有更改日志文件都需要在此处列出,因为如果我要运行此应用程序,则需要所有更改日志文件在另一台机器上从头开始创建整个数据库。

那么如何配置liquibase仅运行尚未执行的变更日志?

2 个答案:

答案 0 :(得分:1)

这里的问题是存储在filename表中的DATABASECHANGELOG字段。

Liquibase不仅通过changeSetidid来确定author的身份,而是根据值来确定changeSet的身份所有三个字段中的一个idauthorfilename

运行mvn liquibase:updatemvn liquibase:changelogSync时,变更日志的filenamesrc/main/resources/db/changelog/changes/demodb-changelog.xml。这是由liquibase存储在每个表行的DATABASECHANGELOG.FILENAME字段中的值。

但是,当您启动spring-boot应用程序时,更改日志的文件名是classpath:db/changelog/db.changelog-master.xml,包含在其中的更改日志的文件名是db/changelog/changes/demodb-changelog.xml

然后,Liquibase检查已经应用了哪些变更集。

它检查id表中每个变更集的authorfilenameDATABASECHANGELOG以及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插件运行时,它们通常位于本地文件系统上(不是真的,请参见我的“ Update 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。并且logicalFilePath标记上设置的<databaseChangeLog>也不会被继承。

更新2

我找到了一个更强大的解决方案。

只需不在maven插件的src/main/resources属性中指定前缀changeLogFile。这样,maven将在类路径中而不是文件系统中搜索变更日志。因此,您必须先执行mvn process-resources,然后再执行liquibase maven插件。

因此,您的liquibase maven插件(changeLogFile文件)的liquibase.properties属性应如下所示:

changeLogFile=db/changelog/changes/demodb-changelog.xml

然后,当您运行

mvn process-resources liquibase:changelogSync

Liquibase将在DATABASECHANGELOG表中创建记录,其中FILENAME字段等于db/changelog/changes/demodb-changelog.xml,该字段等于filename,由spring-boot在启动时运行迁移时使用({{1 }}前缀在比较文件名时由Liquibase自动删除)。最后,这使Liquibase能够理解,这些是同一组变更集,并且已经应用​​。

答案 1 :(得分:0)

MySQL将在表中维护liquibase的日志,

Liquibase使用DATABASECHANGELOG表来跟踪已运行哪些changeSet。

无论何时运行变更日志,对于每个变更日志,表都会添加一行,根据ID,我们可以知道运行了哪些变更日志,

有些标记,例如runAlwaysrunOnChange,这些标记将帮助我们执行/不执行。

您可以参考:

For understanding flags:

https://www.liquibase.org/documentation/changeset.html

For understanding DATABASECHANGELOG Table: 

https://www.liquibase.org/documentation/databasechangelog_table.html