【问题标题】:How to upgrade from flyway 3 directly to flyway 5如何从flyway 3直接升级到flyway 5
【发布时间】:2018-10-01 09:00:08
【问题描述】:

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

我们使用 flyway 进行数据库架构迁移。从 Spring Boot 1.5.x 升级到 2.0.x 将我们的 flyway 版本从 3.x 升级到 5.x。

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

那么,问题是:如何从 flyway 3 直接升级到 flyway 5?

【问题讨论】:

  • 有点讽刺和令人沮丧的是,flyway 的唯一目的是平滑 db 模式版本迁移,但无法顺利处理自己的模式版本迁移。为什么flyway不使用自己的工具来处理升级?

标签: flyway


【解决方案1】:

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

问题:

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

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

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

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

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 类并将配置恢复为原来的方式。
  • 我从 v4 Flyway jar 中为我的数据库复制了相关的 upgradeMetaDataTable.sql 文件,并将其简化为我的表名等。如果需要,您可以从 flyway 中获取架构和表名。
  • SQL 脚本周围没有事务管理,您可能需要添加它
  • Flyway3To4Migrator 调用 flyway.repair(),它的作用比 DbRepair.repairChecksumsAndDescriptions() 多一点,但我们很高兴接受数据库在运行前必须处于良好状态

【讨论】:

    【解决方案2】:

    第 0 步。

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

    第 1 步。

    由于 schema_version 在 flyway 3.x 中使用,让新的 flyway 版本知道他们应该继续使用此表。:

    # 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,用于根据您使用的方言升级元表。

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

    这是 postgres 的一个,假设 flyway 表名称是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 步。

    运行弹簧靴。

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

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

    学分

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

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

    【讨论】:

    • 感谢您的回答。我对 flyway 和 Spring 菜鸟完全陌生,所以我不太确定从第 3 步到第 4 步该怎么做。你能更详细地解释一下吗?我必须在哪里创建这个 Java 文件?我必须在运行应用程序之前执行 maven 吗?
    • 这个答案是非常详细的正确解决方案,但请寻找以下与不同版本的 FlyWay 相关的答案
    【解决方案3】:

    如果您使用的是 Spring Boot,则可以在 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()。

    【讨论】:

    • BaseFlywayCallback 在 flyway 5.x.x 中被弃用。
    【解决方案4】:

    上面的代码与版本 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();
            }
        }
    }
    

    【讨论】:

    • 我使用了 Event.BEFORE_VALIDATE,因为在迁移之前调用了验证,因此当我使用 BEFORE_MIGRATE 时它对我来说失败了。
    • 我可以确认此解决方案可以完美运行(使用 BEFORE_VALIDATE)将 Flyway 3.x 升级到 6.4.4。除了上面的代码,还需要准备这里提到的SQL脚本。从flyway-4.2.0 分支(查找upgradeMetaDataTable.sql)中找到与您的数据库技术相匹配的脚本,将里面的变量(架构和表名)替换为您的,然后将其放入db/migration/flyway_upgradeMetaDataTable_V3_to_V4.sql
    【解决方案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 上进行第三次提交,架构是正确的。

    【讨论】:

    • 感谢法比奥!不幸的是,要使其正常工作,需要先部署从第二次提交构建的应用程序,然后才能部署从第三次提交构建的应用程序。
    【解决方案6】:

    它对我有用,除了我必须再次输入 Event.BEFORE_VALIDATE 而不是 FlywayUpdate3To4CallbackEvent.BEFORE_MIGRATE /strong> 类。这是因为我的校验和在已运行的迁移中无效,因此需要在验证之前而不是在迁移之前修复它。谢谢。

    【讨论】:

      猜你喜欢
      • 2016-07-07
      • 2016-05-24
      • 2012-02-19
      • 2017-02-24
      • 2021-08-12
      • 1970-01-01
      • 2021-01-18
      • 2020-09-11
      • 2016-07-02
      相关资源
      最近更新 更多