Spring Boot 中使用 Flyway 实现多数据库迁移

在使用微服务或复杂的单体应用时,通常需要使用多个数据库来处理不同的领域,例如用户、产品等。Spring Boot 为管理此类多数据库设置提供了强大的支持。然而,管理多个数据库的架构迁移可能比较棘手。

在本教程中,我们将探讨如何将Flyway 与 Spring Boot集成,以便在单个应用程序中支持多个数据库。我们将使用两个独立的 H2 内存数据库(一个用于用户,一个用于产品),并为每个数据库分别使用 Flyway 进行迁移。

Maven配置
 在深入研究之前,我们将通过 Spring Initializr使用 Maven设置一个简单的 Spring Boot应用程序。

首先,我们需要配置所需的依赖项。我们包含了用于数据 JPA、Flyway、H2和测试的Spring Boot 启动器。这些依赖项将允许我们使用 Flyway 管理模式版本,并使用 JPA 与数据库层交互:



    org.springframework.boot
    spring-boot-starter-data-jpa
    3.2.3


    org.flywaydb
    flyway-core
    9.22.3


    com.h2database
    h2
    2.2.224
    runtime


    org.springframework.boot
    spring-boot-starter-test
    3.2.3
    test

这些依赖关系对于启用模式迁移、设置内存数据库和编写单元测试进行验证至关重要。

多数据库配置
在本节中,我们将定义数据源、Flyway Bean、JPA 配置以及支持多数据库连接和模式迁移所需的所有组件。Spring Boot 支持单个数据源的自动配置,因此对于多数据库,我们需要手动提供自定义配置。这包括为每个数据库创建单独的数据源 Bean、实体管理器工厂和事务管理器。

我们还需要单独配置 Flyway,以便每个数据库都能运行其迁移脚本。这些配置确保职责清晰分离,并防止冲突。

1. 应用程序属性
application.yml定义了两个不同的数据源,一个用于userdb,另一个用于productdb。这些设置允许 Spring 分别识别并连接每个数据库。 此外,我们将禁用 Flyway 的默认自动配置,以防止其仅将迁移应用于主数据源。此设置确保我们完全控制迁移的执行方式和位置:


spring:
  flyway:
    enabled: false
userdb:
  datasource:
    url: jdbc:h2:mem:userdb;DB_CLOSE_DELAY=-1
    username: sa
    password:
    driver-class-name: org.h2.Driver
productdb:
  datasource:
    url: jdbc:h2:mem:productdb;DB_CLOSE_DELAY=-1
    username: sa
    password:
    driver-class-name: org.h2.Driver

2. 用户实体
User实体是一个简单的 JPA 模型,用于表示userdb数据库中与用户相关的数据。它包含两个基本字段:id和name,均以列的形式存储:


@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

3. 产品实体
Product实体用于建模存储在productdb数据库中的产品相关数据。它由两个主要字段组成:id和title 。与User实体类似,它使用标准 JPA 注解来定义持久化行为:


@Entity
@Table(name = "products")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_seq")
    private Long id;
    private String name;
}
两个实体都支持所有 CRUD 操作,并与配置的相应数据源无缝集成 以执行 ORM 映射。

4. 存储库
存储库接口负责封装访问数据源所需的逻辑。对于每个实体(User和Product),我们定义一个单独的存储库接口,该接口扩展了JpaRepository 接口。此扩展使 Spring Data JPA 能够自动生成标准数据访问方法,例如findById、save、deleteById和findAll ,而无需样板代码:


public interface UserRepository extends JpaRepository {}
public interface ProductRepository extends JpaRepository {}

UserRepository与userdb交互,而ProductRepository则根据各自的配置连接到productdb 。这些接口在保持代码简洁、可读以及与实际持久化逻辑解耦方面发挥着至关重要的作用。

5. 配置类
在多数据库设置中,Spring Boot 不会自动管理多个数据源或迁移脚本。因此,我们需要显式配置每个数据库的连接、实体扫描、事务管理和 Flyway 迁移设置。

我们为每个数据库userdb和productdb定义了单独的配置类,以保持设置的模块化和可维护性。这种显式配置确保每个数据库都能运行各自的迁移并管理各自的事务边界,从而避免冲突或运行时歧义。

这是userdb的配置:


@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
  basePackages = "com.baeldung.repository.user",
  entityManagerFactoryRef = "userEntityManagerFactory",
  transactionManagerRef = "userTransactionManager"
)
public class UserDbConfig {
    @Bean
    @Primary
    public DataSource userDataSource() {
        return DataSourceBuilder.create()
          .url("jdbc:h2:mem:userdb")
          .username("sa")
          .password("")
          .driverClassName("org.h2.Driver")
          .build();
    }
    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean userEntityManagerFactory(
      EntityManagerFactoryBuilder builder) {
        return builder
          .dataSource(userDataSource())
          .packages("com.baeldung.entity")
          .persistenceUnit("userPU")
          .properties(Map.of("hibernate.hbm2ddl.auto", "none"))
          .build();
    }
    @Bean
    @Primary
    public PlatformTransactionManager userTransactionManager(
      EntityManagerFactory userEntityManagerFactory) {
        return new JpaTransactionManager(userEntityManagerFactory);
    }
    @PostConstruct
    public void migrateUserDb() {
        Flyway.configure()
          .dataSource(userDataSource())
          .locations("classpath:db/migration/userdb")
          .load()
          .migrate();
    }
}
productdb需要 精确的配置,但具有不同的包和迁移路径:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
  basePackages = "com.baeldung.repository.product",
  entityManagerFactoryRef = "productEntityManagerFactory",
  transactionManagerRef = "productTransactionManager"
)
public class ProductDbConfig {
    @Bean
    public DataSource productDataSource() {
        return DataSourceBuilder.create()
          .url("jdbc:h2:mem:productdb")
          .username("sa")
          .password("")
          .driverClassName("org.h2.Driver")
          .build();
    }
    @Bean
    public LocalContainerEntityManagerFactoryBean productEntityManagerFactory(
      EntityManagerFactoryBuilder builder) {
        return builder
          .dataSource(productDataSource())
          .packages("com.baeldung.entity")
          .persistenceUnit("productPU")
          .properties(Map.of("hibernate.hbm2ddl.auto", "none"))
          .build();
    }
    @Bean
    public PlatformTransactionManager productTransactionManager(
      EntityManagerFactory productEntityManagerFactory) {
        return new JpaTransactionManager(productEntityManagerFactory);
    }
    @PostConstruct
    public void migrateProductDb() {
        Flyway.configure()
          .dataSource(productDataSource())
          .locations("classpath:db/migration/productdb")
          .load();
          .migrate();
    }
}

6. SQL 迁移
Flyway 使用版本化 SQL 脚本以可预测和可重复的方式管理数据库模式更改。在多数据库设置中,我们将这些迁移脚本组织在单独的目录中,例如db/migration/userdb和db/migration/productdb,以防止版本冲突并确保模式之间的隔离:

以下配置针对userdb数据库:


-- V1__create_users_table.sql
CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    name VARCHAR(255)
);
以下配置针对productdb数据库:

-- V1__create_products_table.sql
CREATE TABLE products (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255)
);
这种结构对于避免错误至关重要,并有助于清晰地管理每个数据域的模式变化。

7. 单元测试
在这里,我们将在多数据库设置中验证服务层逻辑和数据库交互的正确性。该测试确保实体在各自的数据库中正确保存和检索。它还验证了配置连接、Bean 初始化和 Flyway 迁移。


@Test
void givenUsersAndProducts_whenSaved_thenFoundById() {
    User user = new User();
    user.setName("John");
    userRepository.save(user);
    Product product = new Product();
    product.setName("Laptop");
    productRepository.save(product);
    assertTrue(userRepository.findById(user.getId()).isPresent());
    assertTrue(productRepository.findById(product.getId()).isPresent());
}
该测试确保两个数据库独立工作并正确保存数据。

结论
在本教程中,我们探讨了如何在 Spring Boot 中配置两个数据库,分别管理每个数据库的 Flyway 迁移,以及如何使用单元测试验证设置。通过相应地修改数据源属性,可以轻松扩展此模式以支持其他数据库或其他数据库引擎,例如 PostgreSQL 或 MySQL。

在 Spring Boot 应用程序中管理多个数据库可能颇具挑战性,尤其是在涉及模式版本控制的情况下。通过为每个数据源明确定义配置、存储库和 Flyway 设置,我们可以清晰地分离关注点并实现平滑迁移。这种方法不仅提高了可维护性,而且与企业应用程序中经常需要的模块化架构完美契合。