首页 > 解决方案 > 使用 Flyway 和 Spring Boot 迁移具有不同生命周期的多个模式

问题描述

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

  1. 多个相同的模式
  2. 模式是不同的,但具有相同的生命周期
  3. 模式具有不同的生命周期,或者必须是自治的并且完全分离

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

因为模块是独立的,我们想分别管理它们各自的模式迁移,所以上面的选项(3)是最合适的。

但是,我找不到按照 Flyway FAQ 建议的方式配置 Spring Boot 的 Flyway 集成的方法:

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

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

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

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

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

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

标签: spring-bootflyway

解决方案


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

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

基于约定的方法

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

  • 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.003迁移V3.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 TABLEisusers.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 实例

SpringBootFlywayMigrationStrategy在执行迁移时会查找类型的 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();

  }
}

推荐阅读