引言: 在微服务和敏捷开发时代,数据库结构的变更(如新增表、修改字段)必须与应用程序代码同步演进。手动执行 SQL 脚本不仅效率低下,还容易出错。Flyway 作为一个轻量级的数据库迁移工具,能够完美解决这一问题。而 Spring Boot 通过其强大的自动配置能力,让集成 Flyway 变得异常简单。
本文将手把手教你如何在 Spring Boot 项目中集成 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> 下(需在配置中指定或确保在扫描路径内)。
:::
验证与运行
- 确保数据库
<font style="color:rgb(6, 10, 38);">my_database</font>已存在(Flyway 默认不会创建数据库,只会管理库内的表)。 - 启动 Spring Boot 应用 (
<font style="color:rgb(6, 10, 38);">mvn spring-boot:run</font>或运行 Main 类)。 - 观察控制台日志:
- 你会看到 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_rank | version | description | type | script | checksum | installed_by | installed_on | execution_time | success |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 1 | create user table | SQL | V1__create_user_table.sql | 123456789 | root | 2026-03-06 09:00:00 | 120 | 1 |
| 2 | 2 | add phone column to users | SQL | V2__add_phone_column_to_users.sql | 987654321 | root | 2026-03-06 09:00:01 | 85 | 1 |
| 3 | 3 | MigrateData | JAVA | V3__MigrateData | 112233445 | root | 2026-03-06 09:00:02 | 210 | 1 |
高级配置与最佳实践
基线化 (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>)。版本号必须全局唯一。
- A: 检查
- Q: 脚本执行失败,应用启动终止?
- A: Flyway 默认在事务中执行每个脚本。如果 SQL 语法错误或约束冲突,整个脚本会回滚,应用启动失败。检查日志中的具体 SQL 错误信息。
- Q: 如何跳过某个迁移?
- A: 不推荐跳过。如果脚本有问题,应修复脚本并创建一个新版本号的新脚本来修正,或者在极端情况下手动修改
<font style="color:rgb(6, 10, 38);">flyway_schema_history</font>表(风险高)。
- A: 不推荐跳过。如果脚本有问题,应修复脚本并创建一个新版本号的新脚本来修正,或者在极端情况下手动修改
- 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