【问题标题】:Spring Boot + Hibernate + Flyway: don't run migrations on new databaseSpring Boot + Hibernate + Flyway:不要在新数据库上运行迁移
【发布时间】:2019-02-06 14:41:36
【问题描述】:

我正在使用 Flyway 更新数据库架构。目前最新版本的schema是3(最新的迁移文件名为V3__postgres.sql)。

如果我在具有旧架构版本的数据库上运行应用程序,Flyway 会按预期执行更新脚本。但是,如果我在新的(空)数据库上运行应用程序,flyway 会尝试执行更新脚本,但它没有找到任何表(因为 Hibernate 尚未创建它们),并且应用程序因错误而终止。

我希望 Flyway在空数据库上执行更新脚本,因为当 Hibernate 创建表时,它们无论如何都会是最新版本。

如果我理解正确,我应该可以为此使用参数flyway.baseline-version。我的理论是,如果 Flyway 没有找到表 schema_version,它应该创建它并插入一条记录,说明数据库是版本 3。但即使我设置了 flyway.baseline-version=3,Flyway 仍然会执行脚本。我还尝试设置参数flyway.baseline-on-migrate=true 及其不同的组合,但我无法让它工作。

我是否正确理解了baseline-version 参数,还是我遗漏了什么?

注意:我知道自 Spring Boot 2 起参数命名空间已更改为 spring.flyway.*,但我使用的是 Spring Boot 1,所以这不是问题。

【问题讨论】:

  • 这里的主要问题是您有两种相互竞争的机制来管理您的数据库模式。这就是给你带来麻烦的原因。同样让 Hibernate 创建(并因此管理)您的架构在生产中通常是一个坏主意(您的架构(键、索引等)也可能与使用 flyway 的架构不同)。
  • 就像@M.Deinum 所说的那样,不要让Hibernate 创建表。我们使用诸如 V0_1__Tables.sql、V0_2__Sequences.sql、V03__Data.sql 之类的 Flyway 脚本来初始设置必要的数据库对象并填充它们,然后使用 1 向上的版本来发展我们的数据库。
  • 多年来,我们一直在多个项目和多个环境中使用 Hibernate 生成数据库结构,但从未遇到过问题。但是,虽然这种方法非常适合创建新内容(表、列、索引等),但它不能用于修改或删除现有内容。这就是我们开始使用 Flyway 的原因(用于修改现有结构)。如果我们开始完全通过 Flyway(即手动)管理模式,这将为我们带来更多的工作。使用 Flyway 的重点应该是让我们(开发人员)的生活更轻松,而不是更艰难......

标签: java hibernate spring-boot flyway


【解决方案1】:

已解决:我创建了一个自定义 FlywayMigrationStrategy bean,在其中手动检查 Flyway 是否已被引入数据库(通过检查迁移表是否存在)。如果没有,我运行baseline 命令。然后我像往常一样调用migrate 命令。

这是 Spring Boot 的配置:

import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.flyway.FlywayMigrationStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FlywayConfig {

    @Autowired
    private DataSource dataSource;

    @Value("${flyway.table}")
    private String flywayTableName;

    @Value("${flyway.baselineVersionAsString}")
    private String baselineVersion;

    @Bean
    public FlywayMigrationStrategy flywayMigrationStrategy() {
        return flyway -> {
            if (!isFlywayInitialized()) {
                flyway.setBaselineVersionAsString(baselineVersion);
                flyway.baseline();
            }
            flyway.migrate();
        };
    }

    private boolean isFlywayInitialized() {

        try (Connection connection = dataSource.getConnection()) {
            DatabaseMetaData metadata = connection.getMetaData();
            ResultSet result = metadata.getTables(null, null, flywayTableName, null);
            return result.next();
        } catch (SQLException e) {
            throw new RuntimeException("Failed to check if Flyway is initialized", e);
        }
    }

}

【讨论】:

    【解决方案2】:

    正如 cmets 已经提到的,flyway 和 hibernate 不应该一起使用来更新架构,但这并不意味着您根本不能使用 Hibernate 来帮助您维护架构。

    基线: 此功能绝对不是为了防止在空数据库上执行迁移而设计的。这应该在您的数据库已经存在时使用(即它已经有表和数据,并且您想要保留这些数据)。对于空数据库,它是无用的。

    示例: 假设您有一个现有的数据库,由 2 个脚本生成:

    V1__create_tables.sql
    V2__create_constraints.sql
    

    现在您想使用 flyway 管理进一步的架构更新:

    V3__First_update.sql
    V4__Second_update.sql
    

    V2 是您的基线,这意味着数据库迁移不会执行迁移 V1 和 V2,因为它们已经存在。

    如果要结合Spring Boot + Hibernate + Flyway

    • 禁用休眠模式自动更新 - 如前所述,这可能很危险 (hbm2ddl.auto=false)
    • 使用 Hibernate SchemaGenerator 生成一个大的 SQL 文件,我们称之为 V1__initial_schema.sql,就像在这个 old-but-still-valid article 中一样。
    • 如果您碰巧在 V1 中有一个带有 data you want to keep, you should baseline it 的数据库。否则,只需从空模式开始。 (flyway clean) 和迁移 (flyway migrate)。 警告:flyway clean 将删除所有表格!

    现在您已准备好使用 Hibernate 和 Flyway 进行每次即将进行的架构修改。 假设您刚刚更新了模型:

    • 使用 Hibernate SchemaGenerator 生成相同的大 SQL 文件,我们称之为generator-output.sql
    • V1__initial_schema.sqlgenerator-output.sql 进行并排比较。这将帮助您识别 2 个文件之间的差异。根据这些差异,您可以生成一个新的迁移文件,我们称之为V2__update.sql
    • 在您的数据库上创建flyway migrate

    【讨论】:

    • 正如您所说:V2 是您的基线,这意味着数据库的迁移不会执行迁移 V1 和 V2,因为它们已经存在。 这正是我正在尝试的达到。我正在尝试将基线版本设置为我的最新版本,以便 Flyway 不会执行迁移脚本。但即使我这样做了,Flyway 仍然会尝试执行脚本。我想我最初的问题是我是否在 Flyway 中遇到错误,或者我是否不正确理解基线是如何工作的(或者它如何与 Spring Boot 一起工作)。根据您的回答,似乎前者是正确的。
    • @Jardo 我不知道这是一个错误还是一个预期的功能 - 我宁愿将我的答案集中在展示如何将 Hibernate 模式创建功能和 Flyway 一起使用。恐怕hbm2dll.auto=trueflyway 永远不会一起正常工作。
    • 如果我能让 Flyway 的基线功能按照所提倡的那样工作,那么它应该可以工作......如果你考虑一下,这与将 Flyway 引入现有数据库的问题相同......无论是数据库已由 Hibernate 创建或其他方式无关...
    • 问题是当你启动你的spring-boot应用程序时,flyway在休眠模式更新功能之前运行,这是正常的:Flyway通常用于创建/更新模式。我想这就是你有这个“错误”的原因。当您在预先存在的架构上使用基线功能时(我的意思是它甚至在您首次启动 spring-boot 之前就已经存在),这是基线的预期目的,它确实可以正常工作。
    【解决方案3】:

    顺便说一下,当我尝试插入新版本时,我发现区分大小写的文件名很重要。 另外每次记得设置这个属性:spring.flyway.ignore-future-migrations=false 我用V100__script.sql 代替v100__script.sql

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-07-07
      • 2020-10-17
      • 2018-07-29
      • 2021-05-23
      • 1970-01-01
      • 2021-05-25
      • 2018-10-17
      • 2018-08-16
      相关资源
      最近更新 更多