Spring Boot + Hibernate + Flyway:不要 运行 在新数据库上迁移

Spring Boot + Hibernate + Flyway: don't run migrations on new database

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

如果我 运行 具有较旧模式版本的数据库上的应用程序,Flyway 会按预期执行更新脚本。但是,如果我 运行 一个新的(空)数据库上的应用程序,flyway 会尝试执行更新脚本,但它找不到任何 tables(因为 Hibernate 还没有创建它们) ,应用程序因错误而终止。

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

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

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

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

正如评论已经提到的,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 进行并排比较。这将帮助您识别这两个文件之间的差异。根据这些差异,您可以生成一个新的迁移文件,我们称之为V2__update.sql.
  • 对您的数据库执行 flyway migrate

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

这里是 Spring 引导配置:

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);
        }
    }

}

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