如何从flyway 3直接升级到flyway 5

How to upgrade from flyway 3 directly to flyway 5

正在开发一种由许多客户部署在许多生产环境中的产品。它包括至少一个 Spring 启动应用程序。

我们使用 flyway 进行数据库架构迁移。从 Spring 引导 1.5.x 升级到 2.0.x 已将我们的飞路版本从 3.x 提高到 5.x.

Spring 引导迁移指南只是说在引导升级之前升级到 flyway 4。但是,这需要我们所有的客户在能够升级到最新版本之前进行中间升级。

所以,问题是:如何从飞路 3 直接升级到飞路 5?

我也尝试过跳过 v4,但没有成功。 运行 从 3 到 5 的修复将使校验和正确,但不会更改 schema_version 格式。这也发生了变化。

看来你需要先升级到v4。哪怕暂时只是为了运行mvn flyway:validate,这也会修复schema_version.

我在这个 repo 上做了这个:https://github.com/fabiofalci/flyway-from-3-to-5/commits/5.0.7

第一次提交是 v3,第二次提交是 v4(我 运行 验证)然后第三次提交 v5 模式是正确的。

以防我不是地球上最后一个仍在从 3 升级到 5 的人。

问题:

我希望升级对项目中的其他开发人员是透明的,并且在实时应用程序上升级时不需要任何特殊的部署说明,因此我执行了以下操作。

我查看了版本 4 如何处理升级:

  • 在Flyway.java中调用MetaDataTableImpl.upgradeIfNecessary
  • upgradeIfNecessary 检查 version_rank 列是否仍然存在,如果存在,运行s 一个名为 upgradeMetaDataTable.sql 的迁移脚本来自 org/flywaydb/core/internal/dbsupport/YOUR_DB/
  • 如果 upgradeIfNecessary 已执行,则 Flyway.java 运行 调用 repairChecksumsAndDescriptions
  • 的 DbRepair

这很容易手动完成,但要使其透明。该应用程序是一个 spring 应用程序,但不是 spring 启动应用程序,所以当时我通过让 LocalContainerEntityManager bean 构造依赖于 flyway bean,在应用程序启动时自动进行 flyway 运行ning 迁移,它将调用 migrate 作为它的 init 方法(在此处解释 ),因此引导顺序为:

Flyway bean created -> Flyway migrate called -> LocalContainerEntityManager created

解法:

我将引导顺序更改为:

Flyway bean created -> Flyway3To4Migrator -> LocalContainerEntityManager created

如果需要,Flyway3To4Migrator 将执行 schema_table 更改,如果发生升级,运行 修复,然后总是 运行 flyway.migrate 继续迁移。

@Configuration
public class AppConfiguration {

    @Bean
    // Previously: @DependsOn("flyway")
    @DependsOn("flyway3To4Migrator")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
        ...
    }

    // Previously: @Bean(initMethod = "migrate")
    @Bean
    public Flyway flyway(DataSource dataSource) {
        ...
    }
}

@Component
@DependsOn("flyway")
public class Flyway3To4Migrator {
    private final Log logger = LogFactory.getLog(getClass());
    private Flyway flyway;

    @Autowired
    public Flyway3To4Migrator(Flyway flyway) {
        this.flyway = flyway;
    }

    @PostConstruct
    public void migrate() throws SQLException, MetaDataAccessException {
        DataSource dataSource = flyway.getDataSource();

        boolean versionRankColumnExists = checkColumnExists(dataSource);
        if (versionRankColumnExists) {
            logger.info("Upgrading metadata table to the Flyway 4.0 format ...");
            Resource resource = new ClassPathResource("upgradeMetaDataTable.sql", getClass().getClassLoader());
            ScriptUtils.executeSqlScript(dataSource.getConnection(), resource);
            logger.info("Metadata table successfully upgraded to the Flyway 4.0 format.");

            logger.info("Running flyway:repair for Flyway upgrade.");
            flyway.repair();
            logger.info("Complete flyway:repair.");
        }

        logger.info("Continuing with normal Flyway migration.");
        flyway.migrate();
    }

    private boolean checkColumnExists(DataSource dataSource) throws MetaDataAccessException {
        return (Boolean) JdbcUtils.extractDatabaseMetaData(
            dataSource, dbmd -> {
                ResultSet rs = dbmd.getColumns(
                        null, null,
                        "schema_version",
                        "version_rank");
                return rs.next();
            });
    }
}

注意几点:

  • 在某些时候,我们将删除额外的 Flyway3To4Migrator class 并将配置恢复到原来的状态。
  • 我从 v4 Flyway jar 中为我的数据库复制了相关的 upgradeMetaDataTable.sql 文件,并将其简化为我的 table 名称等。您可以从 flyway 中获取架构和 table 名称如果你需要的话。
  • SQL 脚本没有事务管理,您可能需要添加
  • Flyway3To4Migrator 调用 flyway.repair(),它比 DbRepair.repairChecksumsAndDescriptions() 做的多一点,但我们很高兴接受数据库必须在其 运行 之前处于良好状态

如果您使用的是 Spring 启动,您可以注册一个回调函数来执行 beforeMigrate() 上的升级。代码类似于@trf,看起来像这样:

@Component
@Order(HIGHEST_PRECEDENCE)
@Slf4j
public class FlywayUpdate3To4Callback extends BaseFlywayCallback {
    private final Flyway flyway;

    public FlywayUpdate3To4Callback(@Lazy Flyway flyway) {
        this.flyway = flyway;
    }

    @Override
    public void beforeMigrate(Connection connection) {
        boolean versionRankColumnExists = false;
        try {
            versionRankColumnExists = checkColumnExists(flywayConfiguration);
        } catch (MetaDataAccessException e) {
            log.error("Cannot obtain flyway metadata");
            return;
        }
        if (versionRankColumnExists) {
            log.info("Upgrading metadata table the Flyway 4.0 format ...");
            Resource resource = new ClassPathResource("upgradeMetaDataTable.sql",
                    Thread.currentThread().getContextClassLoader());
            ScriptUtils.executeSqlScript(connection, resource);
            log.info("Flyway metadata table updated successfully.");
            // recalculate checksums
            flyway.repair();
        }
    }

    private boolean checkColumnExists(FlywayConfiguration flywayConfiguration) throws MetaDataAccessException {
        return (boolean) JdbcUtils.extractDatabaseMetaData(flywayConfiguration.getDataSource(),
                callback -> callback
                        .getColumns(null, null, flywayConfiguration.getTable(), "version_rank")
                        .next());
    }

请注意,您无需在此处手动调用 flyway.migrate()。

上面的代码与版本 5 不兼容。它使用了已弃用的 类。 这是一个更新版本。

import lombok.extern.slf4j.Slf4j;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.callback.Callback;
import org.flywaydb.core.api.callback.Context;
import org.flywaydb.core.api.callback.Event;
import org.flywaydb.core.api.configuration.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.stereotype.Component;

import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;

@Component
@Order(HIGHEST_PRECEDENCE)
@Slf4j
public class FlywayUpdate3To4Callback implements Callback {
    private final Flyway flyway;

    public FlywayUpdate3To4Callback(@Lazy Flyway flyway) {
        this.flyway = flyway;
    }

    private boolean checkColumnExists(Configuration flywayConfiguration) throws MetaDataAccessException {
        return (boolean) JdbcUtils.extractDatabaseMetaData(flywayConfiguration.getDataSource(),
                callback -> callback
                        .getColumns(null, null, flywayConfiguration.getTable(), "version_rank")
                        .next());
    }

    @Override
    public boolean supports(Event event, Context context) {
        return event == Event.BEFORE_VALIDATE;
    }

    @Override
    public boolean canHandleInTransaction(Event event, Context context) {
        return false;
    }

    @Override
    public void handle(Event event, Context context) {
        boolean versionRankColumnExists = false;
        try {
            versionRankColumnExists = checkColumnExists(context.getConfiguration());
        } catch (MetaDataAccessException e) {
            log.error("Cannot obtain flyway metadata");
            return;
        }
        if (versionRankColumnExists) {
            log.info("Upgrading metadata table the Flyway 4.0 format ...");
            Resource resource = new ClassPathResource("db/migration/flyway_upgradeMetaDataTable_V3_to_V4.sql",
                    Thread.currentThread().getContextClassLoader());
            ScriptUtils.executeSqlScript(context.getConnection(), resource);
            log.info("Flyway metadata table updated successfully.");
            // recalculate checksums
            flyway.repair();
        }
    }
}

步骤 0.

升级到 spring boot v2.1(然后隐式升级到 flyway 5)。

步骤 1.

由于 schema_version 被用于 flyway 3.x 让新的 flyway 版本知道他们应该继续使用这个 table.:

# application.yml
spring.flyway.table: schema_version # prior flyway version used this table and we keep it

步骤 2.

创建文件 src/main/ressources/db/migration/flyway_upgradeMetaDataTable_V3_to_V4.sql 以根据您使用的方言升级元 table。

几种方言的更新脚本见https://github.com/flyway/flyway/commit/cea8526d7d0a9b0ec35bffa5cb43ae08ea5849e4#diff-b9cb194749ffef15acc9969b90488d98

这是用于 postgres 的,假设 flyway table 名称是 schema_version:

-- src/main/ressources/db/migration/flyway_upgradeMetaDataTable_V3_to_V4.sql
DROP INDEX "schema_version_vr_idx";
DROP INDEX "schema_version_ir_idx";
ALTER TABLE "schema_version" DROP COLUMN "version_rank";
ALTER TABLE "schema_version" DROP CONSTRAINT "schema_version_pk";
ALTER TABLE "schema_version" ALTER COLUMN "version" DROP NOT NULL;
ALTER TABLE "schema_version" ADD CONSTRAINT "schema_version_pk" PRIMARY KEY ("installed_rank");
UPDATE "schema_version" SET "type"='BASELINE' WHERE "type"='INIT';

步骤 3.

创建Java 文件your.package/FlywayUpdate3To4Callback.java

请注意,这会执行以下操作:

  • 运行 来自第 2 步的 sql 脚本
  • 致电Flyway.repair()
// FlywayUpdate3To4Callback.java
package your.package;

import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;

import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.callback.Callback;
import org.flywaydb.core.api.callback.Context;
import org.flywaydb.core.api.callback.Event;
import org.flywaydb.core.api.configuration.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Component
@Order(HIGHEST_PRECEDENCE)
@Slf4j
public class FlywayUpdate3To4Callback implements Callback {
    private final Flyway flyway;

    public FlywayUpdate3To4Callback(@Lazy Flyway flyway) {
        this.flyway = flyway;
    }

    private boolean checkColumnExists(Configuration flywayConfiguration) throws MetaDataAccessException {
        return (boolean) JdbcUtils.extractDatabaseMetaData(flywayConfiguration.getDataSource(),
                callback -> callback
                        .getColumns(null, null, flywayConfiguration.getTable(), "version_rank")
                        .next());
    }

    @Override
    public boolean supports(Event event, Context context) {
        return event == Event.BEFORE_VALIDATE;
    }

    @Override
    public boolean canHandleInTransaction(Event event, Context context) {
        return false;
    }

    @Override
    public void handle(Event event, Context context) {
        boolean versionRankColumnExists = false;
        try {
            versionRankColumnExists = checkColumnExists(context.getConfiguration());
        } catch (MetaDataAccessException e) {
            log.error("Cannot obtain flyway metadata");
            return;
        }
        if (versionRankColumnExists) {
            log.info("Upgrading metadata table the Flyway 4.0 format ...");
            Resource resource = new ClassPathResource("db/migration/common/flyway_upgradeMetaDataTable_V3_to_V4.sql",
                    Thread.currentThread().getContextClassLoader());
            ScriptUtils.executeSqlScript(context.getConnection(), resource);
            log.info("Flyway metadata table updated successfully.");
            // recalculate checksums
            flyway.repair();
        }
    }
}

步骤 4.

运行spring开机.

日志应显示类似于以下内容的信息消息:

...FlywayUpdate3To4Callback      : Upgrading metadata table the Flyway 4.0 format 
...FlywayUpdate3To4Callback      : Flyway metadata table updated successfully.

学分

此答案基于 Eduardo Rodrigues 的答案,更改为:

  • 使用Event.BEFORE_VALIDATE触发飞路回调,将飞路3升级到4。
  • 有关 application.yml 设置的更多信息
  • 提供升级sql迁移脚本

它对我有用,除了我不得不再次输入 Event.BEFORE_VALIDATE 而不是 Event.BEFORE_MIGRATE存在于 FlywayUpdate3To4Callback Class 的最新版本中。这是因为我的校验和在迁移上已经无效 运行,因此需要在验证之前而不是在迁移之前修复它。谢谢。