使用 Flyway 和 Spring Boot 迁移具有不同生命周期的多个模式

Posted

技术标签:

【中文标题】使用 Flyway 和 Spring Boot 迁移具有不同生命周期的多个模式【英文标题】:Migrating multiple schemas with distinct life-cycle using Flyway and Spring Boot 【发布时间】:2018-10-16 11:48:07 【问题描述】:

Flyway FAQ 将多个模式的三种情况分开:

    多个相同的架构 架构不同,但具有相同的生命周期 架构具有不同的生命周期,或者必须是自治的并且完全分离

我们正在使用 Maven 构建一个多模块 Spring Boot 4.5.9 项目。每个模块都是完全独立的,并且有自己的数据库模式。所有模式都驻留在一个数据库中,因此只有一个 Spring 数据源。

由于模块是独立的,我们希望分别管理它们各自的架构迁移,所以上面的选项(3)是最合适的。

但是,我找不到按照 Flyway 常见问题解答建议的方式配置 Spring Boot 的 Flyway 集成的方法:

使用多个 Flyway 实例。每个实例管理自己的模式并引用自己的模式历史表。将每个架构的迁移放在不同的位置。

理想情况下,每个模块都有自己的db/migration 文件夹和自己的迁移SQL 脚本。每个模块脚本的版本应该独立于其他模块中的脚本版本,并且每个模块的迁移历史应该存储在该模块架构中的一个表中。

如果我将迁移脚本放在每个模块的 resources/db/migration 文件夹中,flyway 会检测到它们,但随后会抱怨:

org.flywaydb.core.api.FlywayException: Found more than one migration with version 0

有人知道如何完成所需的设置吗?

附:所有这一切的最终目标是能够(有一天,当系统扩展时)将这些模块拉入单独的服务中,而无需将数据库分解为多个部分。

【问题讨论】:

你找到解决办法了吗? 很遗憾没有 哦,那你的实现是什么? 为所有组件执行迁移的单个 flyway 模块。一旦我们将模块分离为单独的服务,我们将不得不接受打击。 【参考方案1】:

这是一个有两种可行方法的冗长答案。

基于约定的版本控制,无序执行 多种模式

基于约定的方法

可以做到这一点的一种方法是遵循为每个模块分配一个版本号的约定,然后让所有模块迁移成为主要版本的次要版本。例如假设您具有以下模块结构。

config 包含常见 Spring Boot 配置的模块,所有其他模块都从该模块继承。这是将保留 application.yml 的模块 user 包含用户注册模块的模块,user 依赖于config email 包含后台发送邮件代码的模块,email 依赖于config

用户模块将有一个db/migration 文件夹,其中包含以下文件。

V2.001__create_users_schema.sql V2.002__create_account_tables.sql V2.003__create_x_tables.sql

email 模块将有一个 db/migration 文件夹,其中包含以下文件

V3.001__create_email_schema.sql V3.002__create_outbox_table.sql

使用上述版本控制约定,您始终可以返回并添加新的模块特定迁移。例如,应用上述迁移后,您可以添加V2.004__create_y_table.sql,flyway 将填写V2.003V3.0001 之间的迁移

您需要配置 flyway 以允许外订单迁移,否则您将收到错误消息。在开机时可以设置。

spring:
  flyway:
    out-of-order: true

关键是每个模块的第一个迁移文件首先发出 CREATE SCHEMA 语句,然后后续迁移在所有 DDL 语句或对象引用中包含架构名称。

例如V2.001__create_users_schema.sql包含

CREATE SCHEMA users;

V2.002__create_account_tables.sql 包含

CREATE TABLE users.login(
    username text
);

请注意,传递给CREATE TABLE 的名称是users.login,其中包括架构名称。

通过为每个模块使用单独的架构,将来可以更轻松地将模块及其数据库架构提取到单独的二进制文件中。由于 spring boot 使用单个数据库连接池,因此您必须有 1 个数据库用户可以访问所有模块的模式。这需要保持警惕,以确保不会意外发生以下事情。

编写引用多个架构的视图、查询、存储过程 让一个模块将数据插入另一个模块的架构中 有@Transactional不同模块中的方法相互调用。这是最容易搞砸的,因为模块通常会相互调用。您可以尝试通过两种方式解决此问题。 选项 1 所有@Service 类都受包保护,模块仅通过 HTTP 相互调用。 选项 2 使用的所有 @Service 类方法都需要新的传输传播 @Transactional(propagation = Propagation.REQUIRES_NEW)。如果目标是最终提取到微服务架构,我认为选项 1 更好。

这种方法适用于 PostgreSQL,不确定它与其他数据库的效果如何,并且基于常见问题解答 https://flywaydb.org/documentation/faq#hot-fixes

多个 Flyway 实例

SpringBoot 在执行迁移时会查找 FlywayMigrationStrategy 类型的 bean。您可以实现此接口忽略应用程序级别的迁移并创建几个特定于模块的迁移,下面的代码有效。

import org.flywaydb.core.Flyway;
import org.springframework.boot.autoconfigure.flyway.FlywayMigrationStrategy;
import org.springframework.stereotype.Component;

@Component
public class MultiModuleFlywayMigrationStrategy implements FlywayMigrationStrategy 


  @Override
  public void migrate(Flyway flyway) 
    var  dataSource = flyway.getConfiguration().getDataSource();
    Flyway testModule = Flyway.configure()
        .schemas("test")
        .locations("db/test")
        .dataSource(dataSource).load();

    Flyway ratingsModule = Flyway.configure()
        .schemas("rating")
        .locations("db/ratings")
        .dataSource(dataSource).load();

    // don't call flyway.migrate() since we don't want any migrations in db/migration
    testModule.migrate();
    ratingsModule.migrate();

  

【讨论】:

【参考方案2】:

我找到了一个相当简单的解决方案,即在同一个数据库上拥有多个具有自己的迁移历史记录的模块:为每个模块配置不同的 flyway 迁移历史记录表,同时使用相同的DataSource

不幸的是,你不能拥有多个 FlywayMigrationStrategy bean,所以我将我的 maven 模块分开的做法是:

1) 在我的每个模块中将 Flyway 的 FluentBuilder 实例配置为 bean:

@Configuration
public class FlywayModuleAMigrationConfig 

    @Bean
    public FluentConfiguration moduleAFlywayMigrationConfig() 
        return Flyway.configure()
                .table("flyway-module-a-schema-history")
                .locations("db/migration/module/a");
    

2) 在我的应用程序的主 SpringBoot 模块中有一个 FlywayMigrationStrategy 为所有模块的构建器进行配置:

@Component
public class FlywayModuleMigrationStrategy implements FlywayMigrationStrategy 

    @Autowired
    private List<FluentConfiguration> migrations;

    @Override
    public void migrate(Flyway flyway) 
        this.migrations.forEach(mig -> mig.
                baselineOnMigrate(true).
                baselineVersion("0").
                dataSource(flyway.getConfiguration().getDataSource()).
                load().
                migrate());
    

您需要baselineOnMigrate,因为flyway 会注意到其他模块的现有表,因此必须将其作为基线忽略。将 baselineVersion 设置为 0 可以让您从脚本 V1 开始。

【讨论】:

以上是关于使用 Flyway 和 Spring Boot 迁移具有不同生命周期的多个模式的主要内容,如果未能解决你的问题,请参考以下文章

使用 Flyway 和 Spring Boot 迁移基线

Spring boot、JOOQ和Flyway如何一起使用?

Spring Boot:使用 @DataJpaTest 和 Flyway 设置 Hibernate 命名策略

使用 Flyway 和 Spring Boot 在 docker testcontainers 环境中迁移模式

使用 Flyway 和 Spring Boot 迁移具有不同生命周期的多个模式

Spring-Boot 不适用于 Flyway