首页 >Java >java教程 >多模块 Gradle 项目中的 Flyway 迁移(干净架构)

多模块 Gradle 项目中的 Flyway 迁移(干净架构)

Barbara Streisand
Barbara Streisand原创
2025-01-19 08:05:08582浏览

使用 Flyway 在 Java 中自动执行数据库迁移

数据库迁移是软件开发的一个重要方面,特别是在持续集成和交付 (CI/CD) 为标准实践的环境中。随着应用程序的增长和发展,它所依赖的数据库模式也必须如此。手动管理这些架构更改可能会导致错误并消耗大量时间。

Flyway 登场,这是一款专为简化数据库迁移而定制的宝贵开源工具。 Flyway 为您的数据库引入了版本控制,使您能够安全可靠地迁移架构。在本文中,我们将探索如何使用 Flyway 在多模块 gragle java 项目中自动化数据库迁移,确保管理数据库更改成为一个简化的、防错的过程。

有关飞行路线的更多详细信息

了解 Gradle 中的多项目构建

虽然一些较小的项目或整体应用程序可能仅使用一个构建文件和统一的源结构进行管理,但较大的项目经常被组织成多个相互依赖的模块。术语“相互依赖”是这里的关键,强调了通过单一构建过程连接这些模块的需要。

Gradle 以其多项目构建功能(通常称为多模块项目)来满足这种设置。在 Gradle 的术语中,这些模块称为子项目。

多项目构建是围绕一个根项目构建的,并且可以包含其下的多个子项目。

gradle project

目录结构应如下所示:

├── .gradle
│   └── ⋮
├── gradle
│   ├── libs.versions.toml
│   └── wrapper
├── gradlew
├── gradlew.bat
├── settings.gradle.kts (1)
├── sub-project-1
│   └── build.gradle.kts (2) 
├── sub-project-2
│   └── build.gradle.kts (2) 
└── sub-project-3
    └── build.gradle.kts (2)

(1) settings.gradle.kts 文件应包含所有子项目。
(2) 每个子项目应该有自己的build.gradle.kts文件。

利用 Gradle 子模块实现简洁的架构

清洁架构是一种强调关注点分离的设计模式,使软件更易于维护和测试。在项目中实现此架构的实用方法之一是使用 Gradle 的子模块结构来组织代码库。以下是如何将 Clean Architecture 与 Gradle 子模块结合起来:

干净的架构层:
核心:

  • 包含业务逻辑、领域模型和应用程序规则。 不依赖于外部或网络。
  • 应尽可能独立于特定于框架的实现。

外部:

  • 处理外部操作或集成,例如数据库迁移或第三方服务交互。
  • 业务逻辑可能依赖于 Core,但不应依赖于 Web。

网页:

  • 入口点,公开 REST API 并处理 HTTP 请求。
  • 业务逻辑依赖于核心,集成可能依赖于外部。
├── .gradle
│   └── ⋮
├── gradle
│   ├── libs.versions.toml
│   └── wrapper
├── gradlew
├── gradlew.bat
├── settings.gradle.kts (1)
├── sub-project-1
│   └── build.gradle.kts (2) 
├── sub-project-2
│   └── build.gradle.kts (2) 
└── sub-project-3
    └── build.gradle.kts (2)

第 1 步: 创建一个基于 Java 的 Gradle 项目并将其命名为“SchoolStaff”。

第 2 步: 转到 Spring Initializr 并生成一个名为 Web.

的 REST API 项目

第 3 步: 创建一个基于 Java 的 Gradle 项目并将其命名为 External.

第 4 步: 创建一个基于 Java 的 Gradle 项目并将其命名为 Core.

根目录 build.gradle.kts

SchoolStaff/
├── Core/
│   ├── src/
│   │   └── main/
│   │       ├── java/         # Business logic and domain objects
│   │       └── resources/    # Core-specific resources (if any)
│   └── build.gradle.kts
├── External/
│   ├── src/
│   │   └── main/
│   │       ├── java/         # External integration code
│   │       └── resources/    # db/migration and other external resources
│   └── build.gradle.kts
├── Web/
│   ├── src/
│   │   └── main/
│   │       ├── java/         # REST controllers and entry-point logic
│   │       └── resources/    # Application-specific configuration
│   └── build.gradle.kts
├── build.gradle.kts          # Root Gradle build
└── settings.gradle.kts       # Project module settings

settings.gradle.kts

plugins {
    id("java")
}

allprojects {
    group = "school.staff"
    version = "1.0.0"

    repositories {
        mavenLocal()
        mavenCentral()
    }
}

subprojects {
    apply(plugin = "java")

    dependencies {
        testImplementation(platform("org.junit:junit-bom:5.10.0"))
        testImplementation("org.junit.jupiter:junit-jupiter")
    }

    tasks.test {
        useJUnitPlatform()
    }
}

“Web”项目所需的依赖项。

rootProject.name = "SchoolStaff"

include("Core", "External", "Web")

“核心”项目所需的依赖项。

dependencies {
    implementation(project(":Core"))
    implementation(project(":External"))
}

“外部”项目所需的依赖项。

dependencies {
    runtimeOnly(project(":External"))
}

我们使用以下插件进行 Flyway 迁移:

import java.sql.DriverManager
import java.util.Properties
// Function to load properties based on the environment
fun loadProperties(env: String): Properties {
    val properties = Properties()
    val propsFile = file("../web/src/main/resources/application-$env.properties")

    if (propsFile.exists()) {
        propsFile.inputStream().use { properties.load(it) }
    } else {
        throw GradleException("Properties file for environment '$env' not found: ${propsFile.absolutePath}")
    }

    return properties
}
// Set the environment (default is 'dev' if no argument is passed)
val env = project.findProperty("env")?.toString() ?: "dev"

// Load properties for the chosen environment
val dbProps = loadProperties(env)
buildscript {
    dependencies {
        classpath("org.flywaydb:flyway-database-postgresql:11.1.0") // This is required for the flyway plugin to work on the migration, otherwise it will throw an error as No Database found
        classpath("org.postgresql:postgresql:42.7.4")
    }
}
plugins {
    id("java-library")
    id("org.flywaydb.flyway") version "11.0.1"
}

group = "school.staff"
version = "unspecified"

repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-jpa:3.4.0")
    implementation("org.postgresql:postgresql:42.7.4")
    implementation("org.flywaydb:flyway-core:11.0.1")
    implementation("org.flywaydb:flyway-database-postgresql:11.0.1")
    implementation("org.flywaydb:flyway-gradle-plugin:11.0.1")

    implementation (project(":Core"))
    testImplementation(platform("org.junit:junit-bom:5.10.0"))
    testImplementation("org.junit.jupiter:junit-jupiter")
}

tasks.test {
    useJUnitPlatform()
}

// Task to create the database if it doesn't exist
tasks.register("createDatabase") {
    doLast {
        val dbUrl = dbProps["spring.datasource.url"] as String
        val dbUsername = dbProps["spring.datasource.username"] as String
        val dbPassword = dbProps["spring.datasource.password"] as String

        // Extract the base URL and database name
        val baseDbUrl = dbUrl.substringBeforeLast("/")+ "/"
        val dbName = dbUrl.substringAfterLast("/")

        // Connect to the PostgreSQL server (without the specific database)
        DriverManager.getConnection(baseDbUrl, dbUsername, dbPassword).use { connection ->
            val stmt = connection.createStatement()
            val resultSet = stmt.executeQuery("SELECT 1 FROM pg_database WHERE datname = '$dbName'")
            if (!resultSet.next()) {
                println("Database '$dbName' does not exist. Creating it...")
                stmt.executeUpdate("CREATE DATABASE \"$dbName\"")
                println("Database '$dbName' created successfully.")
            } else {
                println("Database '$dbName' already exists.")
            }
        }
    }
}

flyway {
    url = dbProps["spring.datasource.url"] as String
    user = dbProps["spring.datasource.username"] as String
    password = dbProps["spring.datasource.password"] as String
    locations = arrayOf("classpath:db/migration")
    baselineOnMigrate = true
}

 //Ensure classes are built before migration
tasks.named("flywayMigrate").configure {
    dependsOn(tasks.named("createDatabase"))
    dependsOn(tasks.named("classes"))
}

这种方法非常适合生产环境,因为它确保了受控且可靠的迁移。我们不会在每次应用程序启动时自动运行迁移,而是仅在必要时执行迁移,从而提供更大的灵活性和控制力。

我们还利用 Spring 应用程序中的 application.properties 文件来管理数据库连接和凭据。 BaselineOnMigrate = true 设置可确保将初始迁移用作未来迁移的基线。

 plugins {
    id("org.flywaydb.flyway") version "11.0.1"
}

我们可以使用 JPA Buddy 生成外部项目的 resources/db/migration 目录中的所有迁移文件。

V1__Initial_Migration

flyway {
    url = dbProps["spring.datasource.url"] as String
    user = dbProps["spring.datasource.username"] as String
    password = dbProps["spring.datasource.password"] as String
    locations = arrayOf("classpath:db/migration")
    baselineOnMigrate = true
}

从根项目中,我们可以使用以下命令执行 Flyway 迁移:

CREATE TABLE _user
(
    id                 UUID NOT NULL,
    created_by         UUID,
    created_date       TIMESTAMP WITH TIME ZONE,
    last_modified_by   UUID,
    last_modified_date TIMESTAMP WITH TIME ZONE,
    first_name         VARCHAR(255),
    last_name          VARCHAR(255),
    email              VARCHAR(255),
    password           VARCHAR(255),
    tenant_id          UUID,
    CONSTRAINT pk__user PRIMARY KEY (id)
);

这会将所有迁移文件应用到数据库。

结论

我们探索了如何在 Gradle 多模块项目中使用 Flyway 自动化数据库迁移,这对于在 CI/CD 环境中维护架构一致性至关重要。

我们还介绍了 Gradle 如何支持多项目构建,将复杂的项目组织成可管理的子项目,每个子项目都有自己的构建配置,统一在根构建脚本下。

最后,我们将 Clean Architecture 与 Gradle 模块结合起来,将项目构建为核心层、外部层和 Web 层,促进关注点和依赖管理的清晰分离。

这些实践增强了模块化、自动化和可维护性,为可扩展、无错误的软件开发奠定了基础。

以上是多模块 Gradle 项目中的 Flyway 迁移(干净架构)的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn