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

时间:2018-05-06 15:07:17

标签: spring-boot flyway

Flyway FAQ分隔了多个模式的三种情况:

  1. 多个相同的架构
  2. 模式是不同的,但具有相同的生命周期
  3. 模式具有独特的生命周期,或者必须是自主且完全分离的
  4. 我们正在使用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
    

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

    P.S。所有这一切的最终目标是能够(有一天,当系统扩展时)将这些模块拉入单独的服务,而不会通过地狱将数据库分成多个部分。

2 个答案:

答案 0 :(得分:0)

这是一个长篇大论,有两种可行的方法。

  • 基于约定的版本控制,其中包含乱序执行
  • 多个架构

基于公约的方法

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

  • 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

之间的迁移

您将需要配置飞行通道以允许无序迁移,否则会出现错误。在启动时可以设置。

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

  }
}

答案 1 :(得分:0)

对于在同一数据库中拥有多个具有各自迁移历史的模块的情况,我找到了一个相当简单的解决方案:在使用相同的DataSource的情况下,为每个模块配置不同的flyway迁移历史表。

不幸的是,您不能有多个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开始。