简介
Flyway 是一个数据库迁移工具。
它解决的问题和 Liquibase 类似:
1数据库结构怎么跟着项目版本一起演进。 2
不过 Flyway 的风格更简单直接。
它主要通过 SQL 文件管理数据库变更。
比如:
1V1__create_users_table.sql 2V2__add_user_email_column.sql 3V3__create_orders_table.sql 4V4__insert_init_data.sql 5
应用启动或命令执行时,Flyway 会检查哪些脚本已经执行过,哪些还没执行,然后按版本顺序执行新的脚本。
一句话概括:
1Flyway 用 SQL 文件管理数据库版本,让表结构、索引、初始化数据跟着代码一起提交、发布和追踪。 2
Flyway 适合什么场景
常见场景有这些:
- Spring Boot 项目需要初始化数据库结构
- 团队希望直接用 SQL 管理表结构
- 多个环境需要保持数据库结构一致
- 发布时需要自动执行数据库变更
- 数据库变更需要纳入 Git 管理
- 不希望生产环境使用 Hibernate
ddl-auto: update - 项目不需要 YAML/XML 这种抽象迁移格式
如果团队本来就习惯写 SQL,Flyway 上手成本很低。
Flyway 和 ORM 的关系
Flyway 不是 ORM。
它不负责:
- 查询数据库
- 保存 Java 对象
- 映射实体关系
- 生成业务 SQL
这些事情通常交给:
JdbcTemplateMyBatisMyBatis-PlusSpring Data JPA
Flyway 只负责数据库结构和初始化数据的迁移。
常见搭配是:
1Flyway 管表结构 2JPA / MyBatis / JdbcTemplate 管业务读写 3
工作原理
Flyway 的核心流程:
1扫描迁移脚本目录 2 | 3 v 4检查 flyway_schema_history 表 5 | 6 v 7找出未执行脚本 8 | 9 v 10按版本顺序执行 11 | 12 v 13记录执行结果和 checksum 14
第一次运行时,Flyway 会创建一张历史表。
默认表名是:
1flyway_schema_history 2
这张表记录:
1脚本版本 2脚本描述 3脚本文件名 4checksum 5执行时间 6执行耗时 7执行结果 8
后续启动时,Flyway 会通过这张表判断脚本是否执行过。
Maven 依赖
Spring Boot 项目中,核心依赖是:
1<dependency> 2 <groupId>org.flywaydb</groupId> 3 <artifactId>flyway-core</artifactId> 4</dependency> 5
如果使用 MySQL,还需要数据库支持模块:
1<dependency> 2 <groupId>org.flywaydb</groupId> 3 <artifactId>flyway-mysql</artifactId> 4</dependency> 5
MySQL 驱动:
1<dependency> 2 <groupId>com.mysql</groupId> 3 <artifactId>mysql-connector-j</artifactId> 4 <scope>runtime</scope> 5</dependency> 6
如果项目使用 JDBC:
1<dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-jdbc</artifactId> 4</dependency> 5
如果项目使用 JPA:
1<dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-data-jpa</artifactId> 4</dependency> 5
Spring Boot 检测到 flyway-core 后,会自动配置 Flyway,并在应用启动时执行迁移。
Spring Boot 配置
application.yml 示例:
1spring: 2 datasource: 3 url: jdbc:mysql://localhost:3306/flyway_demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8 4 username: root 5 password: 123456 6 driver-class-name: com.mysql.cj.jdbc.Driver 7 8 flyway: 9 enabled: true 10 locations: classpath:db/migration 11 validate-on-migrate: true 12 out-of-order: false 13
常见配置:
| 配置 | 作用 |
|---|---|
| spring.flyway.enabled | 是否启用 Flyway |
| spring.flyway.locations | 迁移脚本目录 |
| spring.flyway.table | 历史表名 |
| spring.flyway.baseline-on-migrate | 非空库首次接入时是否自动 baseline |
| spring.flyway.baseline-version | baseline 版本 |
| spring.flyway.validate-on-migrate | 迁移前是否校验脚本 |
| spring.flyway.out-of-order | 是否允许乱序迁移 |
| spring.flyway.clean-disabled | 是否禁用 clean |
默认迁移目录是:
1classpath:db/migration 2
对应项目路径:
1src/main/resources/db/migration 2
推荐目录结构
1src/main/resources/ 2└── db/ 3 └── migration/ 4 ├── V1__create_users_table.sql 5 ├── V2__create_orders_table.sql 6 ├── V3__insert_init_data.sql 7 ├── V4__add_user_status_column.sql 8 └── R__create_user_order_summary_view.sql 9
V 开头的是版本迁移。
R 开头的是重复迁移。
文件命名规则
版本迁移格式:
1V版本号__描述.sql 2
注意中间是两个下划线:
1__ 2
示例:
1V1__create_users_table.sql 2V2__create_orders_table.sql 3V3__insert_init_data.sql 4V4__add_user_status_column.sql 5
也可以使用小版本:
1V1.0.0__init_schema.sql 2V1.0.1__add_user_table.sql 3V1.1.0__create_order_table.sql 4
多人协作时,也可以使用时间戳版本:
1V202606070001__create_users_table.sql 2V202606070002__create_orders_table.sql 3V202606070003__add_user_status_column.sql 4
时间戳版本不容易和其他分支撞版本号。
第一个迁移脚本
V1__create_users_table.sql:
1create table users ( 2 id bigint primary key auto_increment, 3 username varchar(50) not null, 4 email varchar(100) not null, 5 age int not null, 6 created_at datetime not null, 7 constraint uk_users_email unique (email) 8) engine = InnoDB default charset = utf8mb4; 9
启动 Spring Boot 后,Flyway 会执行这个脚本。
执行成功后,flyway_schema_history 里会记录:
1version: 1 2description: create users table 3script: V1__create_users_table.sql 4success: true 5
再次启动应用时,这个脚本不会重复执行。
创建订单表
V2__create_orders_table.sql:
1create table orders ( 2 id bigint primary key auto_increment, 3 user_id bigint not null, 4 order_no varchar(50) not null, 5 amount decimal(10, 2) not null, 6 status varchar(20) not null, 7 created_at datetime not null, 8 index idx_orders_user_id (user_id), 9 constraint fk_orders_user_id foreign key (user_id) references users (id) 10) engine = InnoDB default charset = utf8mb4; 11
这类脚本适合放结构变更。
例如:
- 建表
- 新增字段
- 创建索引
- 修改字段类型
- 创建约束
插入初始化数据
V3__insert_init_data.sql:
1insert into users (username, email, age, created_at) 2values 3('张三', 'zhangsan@example.com', 20, '2026-01-01 10:00:00'), 4('李四', 'lisi@example.com', 25, '2026-01-02 10:00:00'); 5 6insert into orders (user_id, order_no, amount, status, created_at) 7values 8(1, 'A001', 99.00, 'PAID', '2026-02-01 10:00:00'), 9(1, 'A002', 260.00, 'PAID', '2026-02-02 10:00:00'); 10
初始化数据也可以交给 Flyway。
但测试数据和生产数据要区分。
如果只是开发环境用的测试数据,可以放到单独目录,再通过 profile 控制执行。
新增字段
V4__add_user_status_column.sql:
1alter table users 2add column status varchar(20) not null default 'ACTIVE'; 3
已有大表加非空字段时要谨慎。
常见拆分方式:
1先加可空字段 2分批回填数据 3再加非空约束 4
Flyway 负责记录每一步。
具体执行策略要结合表数据量和业务窗口。
重复迁移
重复迁移文件以 R__ 开头。
示例:
1R__create_user_order_summary_view.sql 2
R__create_user_order_summary_view.sql:
1create or replace view v_user_order_summary as 2select 3 u.id as user_id, 4 u.username, 5 count(o.id) as order_count, 6 coalesce(sum(o.amount), 0) as total_amount 7from users u 8left join orders o on o.user_id = u.id 9group by u.id, u.username; 10
重复迁移的特点:
1没有版本号 2脚本 checksum 改变时会重新执行 3版本迁移执行完后再执行 4按描述排序执行 5
它适合管理:
- 视图
- 存储过程
- 函数
- 触发器
- 可重复刷新的参考数据
重复迁移脚本最好写成可重复执行。
例如:
1create or replace view ... 2
而不是:
1create view ... 2
schema history 表
默认历史表名是:
1flyway_schema_history 2
可以配置:
1spring: 2 flyway: 3 table: flyway_schema_history 4
这张表很重要。
它记录了哪些脚本执行过。
常见字段有:
1installed_rank 2version 3description 4type 5script 6checksum 7installed_by 8installed_on 9execution_time 10success 11
checksum 用来检测已执行脚本是否被修改。
如果某个已执行的 V1__create_users_table.sql 后来被改了,Flyway 校验时会发现 checksum 不一致,并阻止迁移继续执行。
已执行脚本保持稳定
版本迁移脚本执行后,不建议直接修改。
比如 V1__create_users_table.sql 已经在测试环境或生产环境执行过。
此时发现少了一个字段,更合适的方式是新增脚本:
1V5__add_user_phone_column.sql 2
内容:
1alter table users 2add column phone varchar(30); 3
这样所有环境都能按同样顺序执行。
baseline
baseline 用来把一个已经存在的数据库接入 Flyway。
比如数据库里已经有很多表,但还没有 flyway_schema_history 表。
如果直接启用 Flyway,可能会出现非空 schema 没有历史表的问题。
配置:
1spring: 2 flyway: 3 baseline-on-migrate: true 4 baseline-version: 1 5
含义是:
1第一次迁移时,把当前数据库标记为 baseline version 1。 2
后续只执行高于 baseline 的版本脚本。
例如:
1V1__init_existing_schema.sql 2V2__add_user_status_column.sql 3
baseline 为 1 时,V1 不会执行,V2 会继续执行。
已有库接入时,baseline 很有用。
新项目空库一般不需要打开 baseline-on-migrate。
validate
validate 用来校验迁移脚本和历史记录是否一致。
常见校验内容:
- 已执行脚本是否还存在
- 已执行脚本 checksum 是否变化
- 是否存在版本冲突
- 是否存在未按规则命名的脚本
Spring Boot 默认迁移时通常会进行校验。
也可以显式配置:
1spring: 2 flyway: 3 validate-on-migrate: true 4
手动执行:
1flyway validate 2
repair
repair 用来修复 schema history 表里的某些状态。
常见场景:
- 开发环境里脚本 checksum 改过,需要同步历史表
- 删除了已经不再存在的失败记录
- 修复已删除迁移脚本对应的记录状态
命令:
1flyway repair 2
repair 不会自动修改业务表结构。
它主要修复的是 flyway_schema_history。
生产环境使用前需要先确认原因和影响。
clean
clean 会删除 schema 里的数据库对象。
包括:
- 表
- 视图
- 存储过程
- 函数
- 触发器
命令:
1flyway clean 2
它适合本地开发、集成测试里快速重置数据库。
生产环境通常会禁用:
1spring: 2 flyway: 3 clean-disabled: true 4
out-of-order
默认情况下,Flyway 按版本顺序执行。
如果数据库已经执行到 V5,后来又出现一个 V4,默认不会继续执行这个较低版本。
配置:
1spring: 2 flyway: 3 out-of-order: false 4
多人协作时,建议提前统一版本号规则。
常见方案:
1使用时间戳版本号 2每个分支合并前检查 migration 文件 3发布前统一整理版本顺序 4
常用命令
如果使用 Flyway CLI,常见命令如下。
执行迁移:
1flyway migrate 2
查看状态:
1flyway info 2
校验:
1flyway validate 2
修复历史表:
1flyway repair 2
建立基线:
1flyway baseline 2
清理数据库:
1flyway clean 2
Maven 插件
可以使用 Maven 插件执行 Flyway 命令。
1<plugin> 2 <groupId>org.flywaydb</groupId> 3 <artifactId>flyway-maven-plugin</artifactId> 4 <configuration> 5 <url>jdbc:mysql://localhost:3306/flyway_demo</url> 6 <user>root</user> 7 <password>123456</password> 8 </configuration> 9</plugin> 10
常用命令:
1mvn flyway:migrate 2mvn flyway:info 3mvn flyway:validate 4mvn flyway:repair 5mvn flyway:baseline 6mvn flyway:clean 7
Spring Boot 启动自动迁移和 Maven/CLI 迁移二选一即可。
团队规模较大时,迁移经常放到 CI/CD 流水线里执行。
配合 Spring Data JPA
如果项目使用 JPA,建议让 Flyway 管表结构。
JPA 只做校验:
1spring: 2 jpa: 3 hibernate: 4 ddl-auto: validate 5
实体类:
1package com.example.demo.entity; 2 3import jakarta.persistence.Column; 4import jakarta.persistence.Entity; 5import jakarta.persistence.GeneratedValue; 6import jakarta.persistence.GenerationType; 7import jakarta.persistence.Id; 8import jakarta.persistence.Table; 9 10import java.time.LocalDateTime; 11 12@Entity 13@Table(name = "users") 14public class User { 15 16 @Id 17 @GeneratedValue(strategy = GenerationType.IDENTITY) 18 private Long id; 19 20 @Column(nullable = false, length = 50) 21 private String username; 22 23 @Column(nullable = false, unique = true, length = 100) 24 private String email; 25 26 @Column(nullable = false) 27 private Integer age; 28 29 @Column(nullable = false, length = 20) 30 private String status; 31 32 @Column(name = "created_at", nullable = false) 33 private LocalDateTime createdAt; 34 35 // getter setter 36} 37
Repository:
1package com.example.demo.repository; 2 3import com.example.demo.entity.User; 4import org.springframework.data.jpa.repository.JpaRepository; 5 6import java.util.Optional; 7 8public interface UserRepository extends JpaRepository<User, Long> { 9 Optional<User> findByEmail(String email); 10} 11
Service:
1package com.example.demo.service; 2 3import com.example.demo.entity.User; 4import com.example.demo.repository.UserRepository; 5import org.springframework.stereotype.Service; 6import org.springframework.transaction.annotation.Transactional; 7 8import java.time.LocalDateTime; 9 10@Service 11public class UserService { 12 13 private final UserRepository userRepository; 14 15 public UserService(UserRepository userRepository) { 16 this.userRepository = userRepository; 17 } 18 19 @Transactional 20 public Long create(String username, String email, Integer age) { 21 User user = new User(); 22 user.setUsername(username); 23 user.setEmail(email); 24 user.setAge(age); 25 user.setStatus("ACTIVE"); 26 user.setCreatedAt(LocalDateTime.now()); 27 28 User saved = userRepository.save(user); 29 return saved.getId(); 30 } 31} 32
这里的分工是:
1Flyway 创建 users 表 2JPA 实体映射 users 表 3Repository 负责业务读写 4
多环境脚本
可以按环境配置不同目录。
开发环境:
1spring: 2 flyway: 3 locations: classpath:db/migration,classpath:db/dev 4
生产环境:
1spring: 2 flyway: 3 locations: classpath:db/migration 4
目录:
1src/main/resources/ 2└── db/ 3 ├── migration/ 4 │ ├── V1__create_users_table.sql 5 │ └── V2__create_orders_table.sql 6 └── dev/ 7 └── V1000__insert_dev_test_data.sql 8
这样开发测试数据不会进入生产环境。
Java Migration
除了 SQL,Flyway 也支持 Java 迁移。
适合这些场景:
- 复杂数据转换
- 需要调用 Java 逻辑
- 处理大字段或文件
- SQL 很难表达的迁移
示例:
1package db.migration; 2 3import org.flywaydb.core.api.migration.BaseJavaMigration; 4import org.flywaydb.core.api.migration.Context; 5 6import java.sql.PreparedStatement; 7 8public class V5__normalize_user_email extends BaseJavaMigration { 9 10 @Override 11 public void migrate(Context context) throws Exception { 12 try (PreparedStatement statement = context.getConnection() 13 .prepareStatement("update users set email = lower(email)")) { 14 statement.executeUpdate(); 15 } 16 } 17} 18
类名也遵守版本迁移命名规则:
1V5__normalize_user_email 2
常规 DDL 优先用 SQL。
复杂数据转换再考虑 Java Migration。
和 Liquibase 的区别
| 对比项 | Flyway | Liquibase |
|---|---|---|
| 主要格式 | SQL | YAML、XML、JSON、SQL |
| 上手成本 | 较低 | 中等 |
| 变更模型 | 按版本脚本执行 | changeSet 模型 |
| 回滚 | 常见做法是写新迁移向前修复 | 支持 rollback 定义 |
| 适合场景 | SQL 优先、简单直接 | 复杂流程、多格式、强元数据 |
| 历史表 | flyway_schema_history | DATABASECHANGELOG、DATABASECHANGELOGLOCK |
粗略理解:
1Flyway 更像按顺序执行 SQL 文件 2Liquibase 更像用 changeSet 描述数据库变更 3
如果项目以 SQL 为主,Flyway 很顺手。
如果需要 YAML/XML、precondition、context、label、rollback 等能力,Liquibase 更合适。
常见使用建议
已执行的版本脚本保持稳定
版本迁移脚本执行后,尽量保持稳定。
如果线上已经执行:
1V1__create_users_table.sql 2
后续需要加字段,就新增:
1V2__add_user_status_column.sql 2
这样所有环境都能按同样顺序演进。
版本号规则提前统一
小项目可以使用:
1V1 2V2 3V3 4
多人协作项目更适合:
1V202606070001 2V202606070002 3V202606070003 4
版本号冲突会少很多。
生产环境禁用 clean
clean 会删除数据库对象。
生产环境建议配置:
1spring: 2 flyway: 3 clean-disabled: true 4
本地环境可以按需打开。
先在测试库验证迁移
发布前建议至少执行:
1flyway validate 2flyway migrate 3
测试库通过后,再进入生产发布流程。
如果迁移涉及大表,还需要评估锁表时间、执行耗时和回滚方案。
初始化数据保持可重复
版本迁移只会执行一次。
如果初始化数据未来可能调整,可以考虑:
1使用新的 V 脚本修正数据 2或把视图、函数、配置刷新放到 R 脚本 3
重复迁移适合可重复执行的对象。
常用配置汇总
| 配置 | 作用 |
|---|---|
| spring.flyway.enabled | 是否启用 |
| spring.flyway.locations | 脚本目录 |
| spring.flyway.table | 历史表名 |
| spring.flyway.baseline-on-migrate | 非空库是否自动 baseline |
| spring.flyway.baseline-version | baseline 版本 |
| spring.flyway.validate-on-migrate | 迁移前是否校验 |
| spring.flyway.out-of-order | 是否允许乱序执行 |
| spring.flyway.clean-disabled | 是否禁用 clean |
| spring.flyway.schemas | 指定 schema |
| spring.flyway.default-schema | 默认 schema |
常用命令汇总
| 命令 | 作用 |
|---|---|
| migrate | 执行未执行迁移 |
| info | 查看迁移状态 |
| validate | 校验脚本和历史记录 |
| repair | 修复历史表状态 |
| baseline | 建立基线 |
| clean | 清理数据库对象 |
总结
Flyway 的核心非常简单:
1把数据库变更写成 SQL 文件 2按 V 版本号排序执行 3用 flyway_schema_history 记录执行历史 4用 checksum 防止已执行脚本被悄悄修改 5用 R 脚本管理可重复刷新的对象 6
它适合这些场景:
- 团队偏好直接写 SQL
- 数据库变更希望纳入 Git
- Spring Boot 启动时自动迁移
- 多环境结构需要保持一致
- 不希望 JPA 自动改表
- 希望工具简单、规则清楚、维护成本低
落地时重点关注:
- 脚本命名规范
- 版本号规则
- 已执行脚本保持稳定
- baseline 的使用边界
- clean 的环境隔离
- 大表迁移的执行窗口
掌握这些内容后,Flyway 已经可以覆盖大多数 Java 项目的数据库版本管理需求。
《Java Flyway 实战指南:用 SQL 脚本管理数据库版本》 是转载文章,点击查看原文。