Flyway:轻松实现数据库版本自动化管理

58 阅读8分钟

引言: 在微服务和敏捷开发时代,数据库结构的变更(如新增表、修改字段)必须与应用程序代码同步演进。手动执行 SQL 脚本不仅效率低下,还容易出错。Flyway 作为一个轻量级的数据库迁移工具,能够完美解决这一问题。而 Spring Boot 通过其强大的自动配置能力,让集成 Flyway 变得异常简单。

本文将手把手教你如何在 Spring Boot 项目中集成 Flyway,实现数据库迁移的自动化。

flyway github仓库地址 flyway 官方文档

为什么要使用 flyway

在多人开发的项目中,我们都习惯了使用SVN或者Git来对代码做版本控制,主要的目的就是为了解决多人开发代码冲突和版本回退的问题。

其实,数据库的变更也需要版本控制,在日常开发中,我们经常会遇到下面的问题:

自己写的SQL忘了在所有环境执行。

别人写的SQL我们不能确定是否都在所有环境执行过了。

有人修改了已经执行过的SQL,期望再次执行。

需要新增环境做数据迁移。

每次发版需要手动控制先发DB版本,再发布应用版本。

其它场景。

有了flyway,这些问题都能得到很好的解决。

flyway 工作流程

flyway工作流程如下:

项目启动,应用程序完成数据库连接池的建立后,Flyway自动运行。

初次使用时,flyway会创建一个 flyway_schema_history 表,用于记录sql执行记录。

Flyway会扫描项目指定路径下(默认是 classpath:db/migration )的所有sql脚本,与 flyway_schema_history 表脚本记录进行比对。如果数据库记录执行过的脚本记录,与项目中的sql脚本不一致,Flyway会报错并停止项目执行。

如果校验通过,则根据表中的sql记录最大版本号,忽略所有版本号不大于该版本的脚本。再按照版本号从小到大,逐个执行其余脚本。

使用 flyway

添加依赖

Maven

<properties>
  <flyway.version>11.20.3</flyway.version>
</properties>

<dependencies>
  <dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
    <version>${flyway.version}</version>
  </dependency>
  <!-- 添加对应的数据库依赖  这里以mysql为例 -->
    <dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-mysql</artifactId>
    <version>${flyway.version}</version>
  </dependency>
</dependencies>

Gradle

dependencies {
    implementation 'org.flywaydb:flyway-core'
    implementation 'org.flywaydb:flyway-mysql'
}

:::info 注意:从 Spring Boot 2.3+ 开始,<font style="color:rgb(6, 10, 38);">flyway-core</font> 通常不再需要单独指定版本号,Spring Boot 会管理兼容版本。如果使用较新版本 Flyway 特性,可能需要显式指定版本。

:::

在 application.yml 中添加相关配置

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/flyway-test?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
  flyway:
    # 是否启用flyway 默认true
    enabled: true
    # 编码格式,默认UTF-8
    encoding: UTF-8
    # 迁移sql脚本文件存放路径,默认db/migration 默认执行当前目录以及子目录下的所有文件
    locations: classpath:db/migration
    # 迁移sql脚本文件名称的前缀,默认V
    sql-migration-prefix: V
    # 重复执行的脚本文件名称的前缀,默认R
    repeatable-sql-migration-prefix: R
    # 迁移sql脚本文件名称的分隔符,默认2个下划线__
    sql-migration-separator: __
    # 迁移sql脚本文件名称的后缀
    sql-migration-suffixes: .sql
    # 迁移时是否进行校验,默认true
    validate-on-migrate: true
    # 当迁移发现数据库非空且存在没有元数据的表时,自动执行基准迁移,新建schema_version表
    baseline-on-migrate: true
    #设置基线版本
    baseline-version: 1.0.0
    # 忽略迁移规则的迁移脚本
    #配置规则为<状态>:<模式>
    #missing : 数据库中有记录但文件中找不到对应的文件 迁移
    #pending: 有对应文件但数据库中未执行的迁移
    #ignored: 已被标记为忽略的迁移
    #future: 未来的版本号  版本号比数据库中版本还高的迁移
    ignore-migration-patterns:
     - "repeatable:*"  #忽略所有重复执行的脚本

编写迁移脚本

Flyway 的核心是版本化的 SQL 脚本。如上配置所示,脚本放在 <font style="color:rgb(6, 10, 38);">src/main/resources/db/migration</font> 目录下(默认位置)。

命名规则至关重要<font style="color:rgb(6, 10, 38);">V<版本编号>__<描述>.sql</font>

  • <font style="color:rgb(6, 10, 38);">V</font>:代表 Version(版本)。前缀必须和上面配置(sql-migration-prefix)的前缀保持一致
  • <font style="color:rgb(6, 10, 38);"><版本编号></font>:必须是数字,可以包含点号(如 <font style="color:rgb(6, 10, 38);">1.0</font>, <font style="color:rgb(6, 10, 38);">1.1</font>, <font style="color:rgb(6, 10, 38);">2</font>),Flyway 会按数字大小排序执行。
  • <font style="color:rgb(6, 10, 38);">__</font>两个下划线,分隔版本号和描述。和sql-migration-separator 配置保持一致
  • <font style="color:rgb(6, 10, 38);"><描述></font>:脚本功能的简短描述,只能包含字母、数字和下划线。

使用 JAVA 代码进行复杂迁移

如果迁移逻辑非常复杂(如涉及数据清洗、调用外部 API),Flyway 也支持 Java 代码迁移。

创建文件 <font style="color:rgb(6, 10, 38);">src/main/resources/db/migration/V3__MigrateData.java</font>:

package db.migration;

import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

/**
 * Java 代码迁移示例:将旧格式的用户名转换为小写
 * 文件名必须以 V<版本>__ 开头,且类名必须与文件名(不含.java)一致
 */
public class V3__MigrateData extends BaseJavaMigration {

    @Override
    public void migrate(Context context) throws Exception {
        try (PreparedStatement selectStmt = context.getConnection().prepareStatement(
                "SELECT id, username FROM users WHERE username != LOWER(username)");
             ResultSet rs = selectStmt.executeQuery()) {

            try (PreparedStatement updateStmt = context.getConnection().prepareStatement(
                    "UPDATE users SET username = ? WHERE id = ?")) {

                while (rs.next()) {
                    Long id = rs.getLong("id");
                    String oldUsername = rs.getString("username");
                    String newUsername = oldUsername.toLowerCase();

                    updateStmt.setString(1, newUsername);
                    updateStmt.setLong(2, id);
                    updateStmt.executeUpdate();

                    System.out.println("Updated user " + id + " from " + oldUsername + " to " + newUsername);
                }
            }
        }
    }
}

:::info 注意:Java 迁移类必须继承 <font style="color:rgb(6, 10, 38);">BaseJavaMigration</font>,且包路径通常放在 <font style="color:rgb(6, 10, 38);">db.migration</font> 下(需在配置中指定或确保在扫描路径内)。

:::

验证与运行

  1. 确保数据库 <font style="color:rgb(6, 10, 38);">my_database</font> 已存在(Flyway 默认不会创建数据库,只会管理库内的表)。
  2. 启动 Spring Boot 应用 (<font style="color:rgb(6, 10, 38);">mvn spring-boot:run</font> 或运行 Main 类)。
  3. 观察控制台日志
    • 你会看到 Flyway 的启动日志,类似:
Flying start...
Successfully validated 3 migrations (execution time 00:00.123s)
Creating Schema History table: `my_database`.`flyway_schema_history` ...
Current version of schema `my_database`: << Empty Schema >>
Migrating schema `my_database` to version "1 - create user table"
Migrating schema `my_database` to version "2 - add phone column to users"
Migrating schema `my_database` to version "3 - MigrateData"
Successfully applied 3 migrations to schema `my_database` (execution time 00:00.456s)

验证数据库

  • 连接数据库,确认相关表结构和数据是否符合预期。
  • 查看 Flyway 自动创建的元数据表 <font style="color:rgb(6, 10, 38);">flyway_schema_history</font>。这张表记录了所有已执行的迁移脚本的版本、描述、执行时间、校验和等信息,是 Flyway 追踪进度的核心。
installed_rankversiondescriptiontypescriptchecksuminstalled_byinstalled_onexecution_timesuccess
11create user tableSQLV1__create_user_table.sql123456789root2026-03-06 09:00:001201
22add phone column to usersSQLV2__add_phone_column_to_users.sql987654321root2026-03-06 09:00:01851
33MigrateDataJAVAV3__MigrateData112233445root2026-03-06 09:00:022101

高级配置与最佳实践

基线化 (Baseline)

如果你的数据库已经存在表结构(非空库),直接引入 Flyway 会报错。此时需要开启基线化:

spring.flyway.baseline-on-migrate=true
spring.flyway.baseline-version=1 # 将当前状态视为版本 1

这会让 Flyway 跳过现有序列,直接从下一个版本开始管理。

清理数据库 (Clean)

警告<font style="color:rgb(6, 10, 38);">flyway.clean</font> 会删除指定 schema 下的所有对象!默认在生产环境是禁用的。

# 仅在开发/测试环境开启
spring.flyway.clean-disabled=false 

可通过 Actuator 端点或命令行触发清理。

占位符 (Placeholders)

在 SQL 脚本中使用占位符,便于不同环境(dev/test/prod)复用脚本。

INSERT INTO config (env, value) VALUES ('${flyway.env}', '${flyway.default_value}');

配置

spring.flyway.placeholders.env=production
spring.flyway.placeholders.default_value=default_config

常见问题排查 (FAQ)

  • Q: 启动报错 "Found more than one migration with version X"?
    • A: 检查 <font style="color:rgb(6, 10, 38);">db/migration</font> 目录下是否有重复版本号的脚本(如两个 <font style="color:rgb(6, 10, 38);">V1__...sql</font>)。版本号必须全局唯一。
  • Q: 脚本执行失败,应用启动终止?
    • A: Flyway 默认在事务中执行每个脚本。如果 SQL 语法错误或约束冲突,整个脚本会回滚,应用启动失败。检查日志中的具体 SQL 错误信息。
  • Q: 如何跳过某个迁移?
    • A: 不推荐跳过。如果脚本有问题,应修复脚本并创建一个新版本号的新脚本来修正,或者在极端情况下手动修改 <font style="color:rgb(6, 10, 38);">flyway_schema_history</font> 表(风险高)。
  • Q: Flyway 和 Liquibase 选哪个?
    • A: Flyway 更简单,基于纯 SQL,学习曲线低,适合大多数场景。Liquibase 功能更强大(支持 XML/YAML/JSON 定义,复杂回滚),但配置更复杂。Spring Boot 对两者都有良好支持。

flyway 配置清单

flyway 更多配置详情以及解释如下:

flyway.baseline-description对执行迁移时基准版本的描述.
flyway.baseline-on-migrate当迁移时发现目标schema非空,而且带有没有元数据的表时,是否自动执行基准迁移,默认false.
flyway.baseline-version开始执行基准迁移时对现有的schema的版本打标签,默认值为1.
flyway.check-location检查迁移脚本的位置是否存在,默认false.
flyway.clean-on-validation-error当发现校验错误时是否自动调用clean,默认false.
flyway.enabled是否开启flywary,默认true.
flyway.encoding设置迁移时的编码,默认UTF-8.
flyway.ignore-failed-future-migration当读取元数据表时是否忽略错误的迁移,默认false.
flyway.init-sqls当初始化好连接时要执行的SQL.
flyway.locations迁移脚本的位置,默认db/migration.
flyway.out-of-order是否允许无序的迁移,默认false.
flyway.password目标数据库的密码.
flyway.placeholder-prefix设置每个placeholder的前缀,默认${.
flyway.placeholder-replacementplaceholders是否要被替换,默认true.
flyway.placeholder-suffix设置每个placeholder的后缀,默认}.
flyway.placeholders.[placeholder name]设置placeholder的value
flyway.schemas设定需要flywary迁移的schema,大小写敏感,默认为连接默认的schema.
flyway.sql-migration-prefix迁移文件的前缀,默认为V.
flyway.sql-migration-separator迁移脚本的文件名分隔符,默认__
flyway.sql-migration-suffix迁移脚本的后缀,默认为.sql
flyway.tableflyway使用的元数据表名,默认为schema_version
flyway.target迁移时使用的目标版本,默认为latest version
flyway.url迁移时使用的JDBC URL,如果没有指定的话,将使用配置的主数据源
flyway.user迁移数据库的用户名
flyway.validate-on-migrate迁移时是否校验,默认为true