Java Flyway 实战指南:用 SQL 脚本管理数据库版本

简介

Flyway 是一个数据库迁移工具。

它解决的问题和 Liquibase 类似:

text 复制代码
数据库结构怎么跟着项目版本一起演进。

不过 Flyway 的风格更简单直接。

它主要通过 SQL 文件管理数据库变更。

比如:

text 复制代码
V1__create_users_table.sql
V2__add_user_email_column.sql
V3__create_orders_table.sql
V4__insert_init_data.sql

应用启动或命令执行时,Flyway 会检查哪些脚本已经执行过,哪些还没执行,然后按版本顺序执行新的脚本。

一句话概括:

text 复制代码
Flyway 用 SQL 文件管理数据库版本,让表结构、索引、初始化数据跟着代码一起提交、发布和追踪。

Flyway 适合什么场景

常见场景有这些:

  • Spring Boot 项目需要初始化数据库结构
  • 团队希望直接用 SQL 管理表结构
  • 多个环境需要保持数据库结构一致
  • 发布时需要自动执行数据库变更
  • 数据库变更需要纳入 Git 管理
  • 不希望生产环境使用 Hibernate ddl-auto: update
  • 项目不需要 YAML/XML 这种抽象迁移格式

如果团队本来就习惯写 SQL,Flyway 上手成本很低。

Flyway 和 ORM 的关系

Flyway 不是 ORM。

它不负责:

  • 查询数据库
  • 保存 Java 对象
  • 映射实体关系
  • 生成业务 SQL

这些事情通常交给:

  • JdbcTemplate
  • MyBatis
  • MyBatis-Plus
  • Spring Data JPA

Flyway 只负责数据库结构和初始化数据的迁移。

常见搭配是:

text 复制代码
Flyway 管表结构
JPA / MyBatis / JdbcTemplate 管业务读写

工作原理

Flyway 的核心流程:

text 复制代码
扫描迁移脚本目录
  |
  v
检查 flyway_schema_history 表
  |
  v
找出未执行脚本
  |
  v
按版本顺序执行
  |
  v
记录执行结果和 checksum

第一次运行时,Flyway 会创建一张历史表。

默认表名是:

text 复制代码
flyway_schema_history

这张表记录:

text 复制代码
脚本版本
脚本描述
脚本文件名
checksum
执行时间
执行耗时
执行结果

后续启动时,Flyway 会通过这张表判断脚本是否执行过。

Maven 依赖

Spring Boot 项目中,核心依赖是:

xml 复制代码
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>

如果使用 MySQL,还需要数据库支持模块:

xml 复制代码
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-mysql</artifactId>
</dependency>

MySQL 驱动:

xml 复制代码
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

如果项目使用 JDBC:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

如果项目使用 JPA:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Spring Boot 检测到 flyway-core 后,会自动配置 Flyway,并在应用启动时执行迁移。

Spring Boot 配置

application.yml 示例:

yaml 复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/flyway_demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

  flyway:
    enabled: true
    locations: classpath:db/migration
    validate-on-migrate: true
    out-of-order: false

常见配置:

配置 作用
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

默认迁移目录是:

text 复制代码
classpath:db/migration

对应项目路径:

text 复制代码
src/main/resources/db/migration

推荐目录结构

text 复制代码
src/main/resources/
└── db/
    └── migration/
        ├── V1__create_users_table.sql
        ├── V2__create_orders_table.sql
        ├── V3__insert_init_data.sql
        ├── V4__add_user_status_column.sql
        └── R__create_user_order_summary_view.sql

V 开头的是版本迁移。

R 开头的是重复迁移。

文件命名规则

版本迁移格式:

text 复制代码
V版本号__描述.sql

注意中间是两个下划线:

text 复制代码
__

示例:

text 复制代码
V1__create_users_table.sql
V2__create_orders_table.sql
V3__insert_init_data.sql
V4__add_user_status_column.sql

也可以使用小版本:

text 复制代码
V1.0.0__init_schema.sql
V1.0.1__add_user_table.sql
V1.1.0__create_order_table.sql

多人协作时,也可以使用时间戳版本:

text 复制代码
V202606070001__create_users_table.sql
V202606070002__create_orders_table.sql
V202606070003__add_user_status_column.sql

时间戳版本不容易和其他分支撞版本号。

第一个迁移脚本

V1__create_users_table.sql

sql 复制代码
create table users (
    id bigint primary key auto_increment,
    username varchar(50) not null,
    email varchar(100) not null,
    age int not null,
    created_at datetime not null,
    constraint uk_users_email unique (email)
) engine = InnoDB default charset = utf8mb4;

启动 Spring Boot 后,Flyway 会执行这个脚本。

执行成功后,flyway_schema_history 里会记录:

text 复制代码
version: 1
description: create users table
script: V1__create_users_table.sql
success: true

再次启动应用时,这个脚本不会重复执行。

创建订单表

V2__create_orders_table.sql

sql 复制代码
create table orders (
    id bigint primary key auto_increment,
    user_id bigint not null,
    order_no varchar(50) not null,
    amount decimal(10, 2) not null,
    status varchar(20) not null,
    created_at datetime not null,
    index idx_orders_user_id (user_id),
    constraint fk_orders_user_id foreign key (user_id) references users (id)
) engine = InnoDB default charset = utf8mb4;

这类脚本适合放结构变更。

例如:

  • 建表
  • 新增字段
  • 创建索引
  • 修改字段类型
  • 创建约束

插入初始化数据

V3__insert_init_data.sql

sql 复制代码
insert into users (username, email, age, created_at)
values
('张三', 'zhangsan@example.com', 20, '2026-01-01 10:00:00'),
('李四', 'lisi@example.com', 25, '2026-01-02 10:00:00');

insert into orders (user_id, order_no, amount, status, created_at)
values
(1, 'A001', 99.00, 'PAID', '2026-02-01 10:00:00'),
(1, 'A002', 260.00, 'PAID', '2026-02-02 10:00:00');

初始化数据也可以交给 Flyway。

但测试数据和生产数据要区分。

如果只是开发环境用的测试数据,可以放到单独目录,再通过 profile 控制执行。

新增字段

V4__add_user_status_column.sql

sql 复制代码
alter table users
add column status varchar(20) not null default 'ACTIVE';

已有大表加非空字段时要谨慎。

常见拆分方式:

text 复制代码
先加可空字段
分批回填数据
再加非空约束

Flyway 负责记录每一步。

具体执行策略要结合表数据量和业务窗口。

重复迁移

重复迁移文件以 R__ 开头。

示例:

text 复制代码
R__create_user_order_summary_view.sql

R__create_user_order_summary_view.sql

sql 复制代码
create or replace view v_user_order_summary as
select
    u.id as user_id,
    u.username,
    count(o.id) as order_count,
    coalesce(sum(o.amount), 0) as total_amount
from users u
left join orders o on o.user_id = u.id
group by u.id, u.username;

重复迁移的特点:

text 复制代码
没有版本号
脚本 checksum 改变时会重新执行
版本迁移执行完后再执行
按描述排序执行

它适合管理:

  • 视图
  • 存储过程
  • 函数
  • 触发器
  • 可重复刷新的参考数据

重复迁移脚本最好写成可重复执行。

例如:

sql 复制代码
create or replace view ...

而不是:

sql 复制代码
create view ...

schema history 表

默认历史表名是:

text 复制代码
flyway_schema_history

可以配置:

yaml 复制代码
spring:
  flyway:
    table: flyway_schema_history

这张表很重要。

它记录了哪些脚本执行过。

常见字段有:

text 复制代码
installed_rank
version
description
type
script
checksum
installed_by
installed_on
execution_time
success

checksum 用来检测已执行脚本是否被修改。

如果某个已执行的 V1__create_users_table.sql 后来被改了,Flyway 校验时会发现 checksum 不一致,并阻止迁移继续执行。

已执行脚本保持稳定

版本迁移脚本执行后,不建议直接修改。

比如 V1__create_users_table.sql 已经在测试环境或生产环境执行过。

此时发现少了一个字段,更合适的方式是新增脚本:

text 复制代码
V5__add_user_phone_column.sql

内容:

sql 复制代码
alter table users
add column phone varchar(30);

这样所有环境都能按同样顺序执行。

baseline

baseline 用来把一个已经存在的数据库接入 Flyway。

比如数据库里已经有很多表,但还没有 flyway_schema_history 表。

如果直接启用 Flyway,可能会出现非空 schema 没有历史表的问题。

配置:

yaml 复制代码
spring:
  flyway:
    baseline-on-migrate: true
    baseline-version: 1

含义是:

text 复制代码
第一次迁移时,把当前数据库标记为 baseline version 1。

后续只执行高于 baseline 的版本脚本。

例如:

text 复制代码
V1__init_existing_schema.sql
V2__add_user_status_column.sql

baseline 为 1 时,V1 不会执行,V2 会继续执行。

已有库接入时,baseline 很有用。

新项目空库一般不需要打开 baseline-on-migrate

validate

validate 用来校验迁移脚本和历史记录是否一致。

常见校验内容:

  • 已执行脚本是否还存在
  • 已执行脚本 checksum 是否变化
  • 是否存在版本冲突
  • 是否存在未按规则命名的脚本

Spring Boot 默认迁移时通常会进行校验。

也可以显式配置:

yaml 复制代码
spring:
  flyway:
    validate-on-migrate: true

手动执行:

bash 复制代码
flyway validate

repair

repair 用来修复 schema history 表里的某些状态。

常见场景:

  • 开发环境里脚本 checksum 改过,需要同步历史表
  • 删除了已经不再存在的失败记录
  • 修复已删除迁移脚本对应的记录状态

命令:

bash 复制代码
flyway repair

repair 不会自动修改业务表结构。

它主要修复的是 flyway_schema_history

生产环境使用前需要先确认原因和影响。

clean

clean 会删除 schema 里的数据库对象。

包括:

  • 视图
  • 存储过程
  • 函数
  • 触发器

命令:

bash 复制代码
flyway clean

它适合本地开发、集成测试里快速重置数据库。

生产环境通常会禁用:

yaml 复制代码
spring:
  flyway:
    clean-disabled: true

out-of-order

默认情况下,Flyway 按版本顺序执行。

如果数据库已经执行到 V5,后来又出现一个 V4,默认不会继续执行这个较低版本。

配置:

yaml 复制代码
spring:
  flyway:
    out-of-order: false

多人协作时,建议提前统一版本号规则。

常见方案:

text 复制代码
使用时间戳版本号
每个分支合并前检查 migration 文件
发布前统一整理版本顺序

常用命令

如果使用 Flyway CLI,常见命令如下。

执行迁移:

bash 复制代码
flyway migrate

查看状态:

bash 复制代码
flyway info

校验:

bash 复制代码
flyway validate

修复历史表:

bash 复制代码
flyway repair

建立基线:

bash 复制代码
flyway baseline

清理数据库:

bash 复制代码
flyway clean

Maven 插件

可以使用 Maven 插件执行 Flyway 命令。

xml 复制代码
<plugin>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-maven-plugin</artifactId>
    <configuration>
        <url>jdbc:mysql://localhost:3306/flyway_demo</url>
        <user>root</user>
        <password>123456</password>
    </configuration>
</plugin>

常用命令:

bash 复制代码
mvn flyway:migrate
mvn flyway:info
mvn flyway:validate
mvn flyway:repair
mvn flyway:baseline
mvn flyway:clean

Spring Boot 启动自动迁移和 Maven/CLI 迁移二选一即可。

团队规模较大时,迁移经常放到 CI/CD 流水线里执行。

配合 Spring Data JPA

如果项目使用 JPA,建议让 Flyway 管表结构。

JPA 只做校验:

yaml 复制代码
spring:
  jpa:
    hibernate:
      ddl-auto: validate

实体类:

java 复制代码
package com.example.demo.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

import java.time.LocalDateTime;

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 50)
    private String username;

    @Column(nullable = false, unique = true, length = 100)
    private String email;

    @Column(nullable = false)
    private Integer age;

    @Column(nullable = false, length = 20)
    private String status;

    @Column(name = "created_at", nullable = false)
    private LocalDateTime createdAt;

    // getter setter
}

Repository:

java 复制代码
package com.example.demo.repository;

import com.example.demo.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

Service:

java 复制代码
package com.example.demo.service;

import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional
    public Long create(String username, String email, Integer age) {
        User user = new User();
        user.setUsername(username);
        user.setEmail(email);
        user.setAge(age);
        user.setStatus("ACTIVE");
        user.setCreatedAt(LocalDateTime.now());

        User saved = userRepository.save(user);
        return saved.getId();
    }
}

这里的分工是:

text 复制代码
Flyway 创建 users 表
JPA 实体映射 users 表
Repository 负责业务读写

多环境脚本

可以按环境配置不同目录。

开发环境:

yaml 复制代码
spring:
  flyway:
    locations: classpath:db/migration,classpath:db/dev

生产环境:

yaml 复制代码
spring:
  flyway:
    locations: classpath:db/migration

目录:

text 复制代码
src/main/resources/
└── db/
    ├── migration/
    │   ├── V1__create_users_table.sql
    │   └── V2__create_orders_table.sql
    └── dev/
        └── V1000__insert_dev_test_data.sql

这样开发测试数据不会进入生产环境。

Java Migration

除了 SQL,Flyway 也支持 Java 迁移。

适合这些场景:

  • 复杂数据转换
  • 需要调用 Java 逻辑
  • 处理大字段或文件
  • SQL 很难表达的迁移

示例:

java 复制代码
package db.migration;

import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;

import java.sql.PreparedStatement;

public class V5__normalize_user_email extends BaseJavaMigration {

    @Override
    public void migrate(Context context) throws Exception {
        try (PreparedStatement statement = context.getConnection()
                .prepareStatement("update users set email = lower(email)")) {
            statement.executeUpdate();
        }
    }
}

类名也遵守版本迁移命名规则:

text 复制代码
V5__normalize_user_email

常规 DDL 优先用 SQL。

复杂数据转换再考虑 Java Migration。

和 Liquibase 的区别

对比项 Flyway Liquibase
主要格式 SQL YAML、XML、JSON、SQL
上手成本 较低 中等
变更模型 按版本脚本执行 changeSet 模型
回滚 常见做法是写新迁移向前修复 支持 rollback 定义
适合场景 SQL 优先、简单直接 复杂流程、多格式、强元数据
历史表 flyway_schema_history DATABASECHANGELOGDATABASECHANGELOGLOCK

粗略理解:

text 复制代码
Flyway 更像按顺序执行 SQL 文件
Liquibase 更像用 changeSet 描述数据库变更

如果项目以 SQL 为主,Flyway 很顺手。

如果需要 YAML/XML、precondition、context、label、rollback 等能力,Liquibase 更合适。

常见使用建议

已执行的版本脚本保持稳定

版本迁移脚本执行后,尽量保持稳定。

如果线上已经执行:

text 复制代码
V1__create_users_table.sql

后续需要加字段,就新增:

text 复制代码
V2__add_user_status_column.sql

这样所有环境都能按同样顺序演进。

版本号规则提前统一

小项目可以使用:

text 复制代码
V1
V2
V3

多人协作项目更适合:

text 复制代码
V202606070001
V202606070002
V202606070003

版本号冲突会少很多。

生产环境禁用 clean

clean 会删除数据库对象。

生产环境建议配置:

yaml 复制代码
spring:
  flyway:
    clean-disabled: true

本地环境可以按需打开。

先在测试库验证迁移

发布前建议至少执行:

bash 复制代码
flyway validate
flyway migrate

测试库通过后,再进入生产发布流程。

如果迁移涉及大表,还需要评估锁表时间、执行耗时和回滚方案。

初始化数据保持可重复

版本迁移只会执行一次。

如果初始化数据未来可能调整,可以考虑:

text 复制代码
使用新的 V 脚本修正数据
或把视图、函数、配置刷新放到 R 脚本

重复迁移适合可重复执行的对象。

常用配置汇总

配置 作用
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 的核心非常简单:

text 复制代码
把数据库变更写成 SQL 文件
按 V 版本号排序执行
用 flyway_schema_history 记录执行历史
用 checksum 防止已执行脚本被悄悄修改
用 R 脚本管理可重复刷新的对象

它适合这些场景:

  • 团队偏好直接写 SQL
  • 数据库变更希望纳入 Git
  • Spring Boot 启动时自动迁移
  • 多环境结构需要保持一致
  • 不希望 JPA 自动改表
  • 希望工具简单、规则清楚、维护成本低

落地时重点关注:

  • 脚本命名规范
  • 版本号规则
  • 已执行脚本保持稳定
  • baseline 的使用边界
  • clean 的环境隔离
  • 大表迁移的执行窗口

掌握这些内容后,Flyway 已经可以覆盖大多数 Java 项目的数据库版本管理需求。

相关推荐
huangdong_8 小时前
电商平台图片URL原图转换技术深度解析:从缩略图到高清原图的完整方案
java·后端·spring
記億揺晃着的那天9 小时前
Java 调用外部 Go 程序的实践:ProcessBuilder 在生产环境中的应用
java·golang·processbuilder
JAVA面经实录9179 小时前
Java 数据结构与算法 (终极完整学习文档)
java·数据结构·算法
JAVA面经实录9179 小时前
操作系统面试题
java·服务器·数据库·计算机网络·面试
一杯奶茶¥10 小时前
基于springboot的失物招领管理系统带万字文档 校园失物招领管理系统 失物认领管理系统java springboot vue
java·vue.js·spring boot·java项目
不能只会打代码10 小时前
边缘视频分析平台的架构设计与性能优化——从750ms到190ms的调优之路
java·spring boot·redis·性能优化·边缘计算·物联网竞赛
小刘|10 小时前
Spring AI Alibaba 集成和风天气 API 实战
java·服务器·前端
KANGBboy10 小时前
java知识五(继承)
java·开发语言
AI人工智能+电脑小能手10 小时前
【大白话说Java面试题 第117题】【并发篇】第17题:线程有几种状态,之间如何转换?
java·开发语言·面试