Flyway!你AI开发Java项目时不错的数据库版本管理工具

Flyway!你AI开发Java项目时不错的数据库版本管理工具

在后端项目里,数据库变更常常是最容易失控的一环。

代码有 Git,配置有环境变量,接口有文档,但数据库结构有时还靠一句“你把这条 SQL 在测试库执行一下”。项目小的时候,这么做似乎没什么问题;一旦团队变大、环境变多、上线频率变高,问题就会不断冒出来:

  • 本地库有字段,测试库没有字段;
  • 开发同学改了表结构,但忘了同步给运维;
  • 线上临时修了一条 SQL,代码仓库里没有记录;
  • 新同事启动项目时,不知道数据库应该建成什么样;
  • 回滚应用时,发现数据库已经被改到另一个状态。

Flyway 解决的就是这个问题:把数据库变更纳入版本管理,让数据库结构像代码一样可追踪、可审计、可重复执行。

本文会从零开始讲清楚 Flyway 的核心概念,并给出一套 Spring Boot 项目中的完整集成方式。示例以 MySQL 为主,但迁移思路同样适用于 PostgreSQL、Oracle、SQL Server 等关系型数据库。


1. Flyway 是什么?

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

所谓“数据库迁移”,不是把 MySQL 迁移到 PostgreSQL,而是指数据库结构和基础数据的版本演进。比如:

  • 第一次建表;
  • 新增字段;
  • 修改字段长度;
  • 新增索引;
  • 创建视图;
  • 初始化系统字典;
  • 修复历史脏数据;
  • 更新存储过程或函数。

这些变更如果靠人工执行,很难保证每个环境都一致。Flyway 的做法是把每次变更写成一个迁移脚本,然后按版本顺序执行。

一个典型的 Flyway 脚本长这样:

V1__create_user_table.sql
V2__add_email_to_user.sql
V3__create_order_table.sql
R__refresh_user_summary_view.sql

Spring Boot 启动时,Flyway 会自动检查数据库当前已经执行到哪个版本,然后只执行还没执行过的脚本。


2. Flyway 的工作原理

Flyway 会在数据库里维护一张历史表,默认表名是:

flyway_schema_history

这张表记录了数据库迁移的执行历史,包括:

  • 脚本版本;
  • 脚本名称;
  • 脚本类型;
  • checksum;
  • 执行人;
  • 执行时间;
  • 执行耗时;
  • 执行状态。

当应用启动并执行迁移时,Flyway 会做几件事:

  1. 扫描项目里的迁移脚本;
  2. 查询 flyway_schema_history
  3. 判断哪些脚本已经执行过,哪些还没有执行;
  4. 按版本顺序执行待执行脚本;
  5. 执行成功后,把结果写入历史表。

这个机制带来的最大好处是:数据库变更从“口头通知 + 手工执行”,变成了“随代码提交 + 自动执行 + 有历史记录”。


3. 为什么 Spring Boot 项目适合用 Flyway?

Spring Boot 项目通常会部署到多个环境:

  • 本地开发环境;
  • 测试环境;
  • 预发环境;
  • 生产环境;
  • CI 自动化测试环境。

如果数据库结构不是自动化管理的,那么每个环境都可能变成不同的状态。Flyway 和 Spring Boot 的集成非常自然:只要引入依赖,并把 SQL 文件放到约定目录,应用启动时就会自动执行数据库迁移。

Spring Boot 官方也建议:如果使用 Flyway 或 Liquibase 这类高级数据库迁移工具,就应该使用它们来创建和初始化 schema,不推荐再同时混用 schema.sqldata.sql 这类基础初始化脚本。


4. 版本说明:Spring Boot 3.x 和 4.x 的依赖差异

这里先把一个容易踩坑的点说清楚。

在 Spring Boot 3.5 文档中,官方说明自动运行 Flyway 迁移时,需要把合适的 Flyway 模块加入 classpath。对于内存数据库和文件型数据库,可以使用:

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>

如果是 PostgreSQL、MySQL 这类数据库,还需要额外加入数据库专用模块。例如:

<!-- PostgreSQL -->
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-database-postgresql</artifactId>
</dependency>

<!-- MySQL -->
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-mysql</artifactId>
</dependency>

到了 Spring Boot 4.1 文档,官方开始提到 spring-boot-starter-flyway。对于内存数据库和文件型数据库,可以使用这个 starter;其他数据库场景仍然需要数据库专用 Flyway 模块。

也就是说:

  • Spring Boot 3.x 项目:常见写法是 flyway-core + 数据库专用模块;
  • Spring Boot 4.x 项目:可以优先看是否使用 spring-boot-starter-flyway,再补数据库专用模块;
  • 不要手写版本号,优先让 Spring Boot 的依赖管理来控制版本。

本文下面的实战示例以 Spring Boot 3.x 的通用写法为主,因为目前很多生产项目仍在 3.x 版本线上运行。使用 Spring Boot 4.x 的同学,把 flyway-core 换成对应 starter 即可,其他配置和脚本组织方式基本一致。


5. 创建一个示例 Spring Boot 项目

假设我们要做一个简单的订单系统,数据库里需要两张表:

  • sys_user:用户表;
  • orders:订单表。

项目技术栈:

  • Spring Boot 3.x;
  • MySQL 8.x;
  • Maven;
  • Spring Web;
  • Spring Data JPA;
  • Flyway。

项目结构大致如下:

flyway-demo
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com/example/flywaydemo
        │       └── FlywayDemoApplication.java
        └── resources
            ├── application.yml
            └── db
                └── migration
                    ├── V1__create_sys_user_table.sql
                    ├── V2__create_orders_table.sql
                    └── R__refresh_user_order_summary_view.sql

Flyway 默认会扫描:

classpath:db/migration

所以在 Spring Boot 项目里,默认目录就是:

src/main/resources/db/migration

6. 引入 Maven 依赖

pom.xml 示例:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>org.flywaydb</groupId>
        <artifactId>flyway-core</artifactId>
    </dependency>

    <dependency>
        <groupId>org.flywaydb</groupId>
        <artifactId>flyway-mysql</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

如果你使用的是 PostgreSQL,可以把 MySQL 相关依赖换成:

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-database-postgresql</artifactId>
</dependency>

如果你使用 Spring Boot 4.1 或更新版本,可以参考官方文档使用:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-flyway</artifactId>
</dependency>

然后根据数据库类型补充 flyway-mysqlflyway-database-postgresql 等模块。


7. 配置数据库和 Flyway

application.yml 示例:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/flyway_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: true

  flyway:
    enabled: true
    locations: classpath:db/migration
    baseline-on-migrate: false
    validate-on-migrate: true
    clean-disabled: true

这里重点解释几个配置。

spring.flyway.enabled

是否启用 Flyway。

spring:
  flyway:
    enabled: true

默认就是 true。如果你某个环境不想让应用启动时自动迁移,可以设置为 false

spring.flyway.locations

迁移脚本所在位置。

spring:
  flyway:
    locations: classpath:db/migration

也可以配置多个位置:

spring:
  flyway:
    locations: classpath:db/migration,filesystem:/opt/migration

classpath: 表示从应用 classpath 里找,通常就是 resources 目录。filesystem: 表示从服务器文件系统里找。

spring.flyway.validate-on-migrate

执行迁移前是否校验历史脚本。

spring:
  flyway:
    validate-on-migrate: true

建议保持开启。它可以帮你发现“已经执行过的脚本被人改了”这类危险操作。

spring.flyway.clean-disabled

是否禁用 clean

spring:
  flyway:
    clean-disabled: true

clean 会删除 configured schemas 里的对象,非常危险。生产环境一定要禁用。

Spring Boot 当前默认 clean-disabled 就是 true,但在团队项目里建议显式写出来,让配置意图更清楚。

spring.jpa.hibernate.ddl-auto

使用 Flyway 后,不建议再让 Hibernate 自动改表。

推荐:

spring:
  jpa:
    hibernate:
      ddl-auto: validate

validate 表示 Hibernate 只校验实体和表结构是否匹配,不主动创建或修改表。

不推荐在生产环境使用:

spring:
  jpa:
    hibernate:
      ddl-auto: update

因为 update 会让 Hibernate 根据实体自动改库,这和 Flyway 的版本化迁移思路冲突。


8. 编写第一个迁移脚本

在目录:

src/main/resources/db/migration

创建文件:

V1__create_sys_user_table.sql

内容如下:

CREATE TABLE sys_user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(64) NOT NULL,
    email VARCHAR(128) DEFAULT NULL,
    status TINYINT NOT NULL DEFAULT 1,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_sys_user_username (username),
    KEY idx_sys_user_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

文件名含义:

V1__create_sys_user_table.sql
  • V:Versioned migration,版本迁移;
  • 1:版本号;
  • __:两个下划线,固定分隔符;
  • create_sys_user_table:描述;
  • .sql:SQL 脚本。

注意:中间必须是两个下划线,不是一个。


9. 创建第二个迁移脚本

继续创建:

V2__create_orders_table.sql

内容:

CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_no VARCHAR(64) NOT NULL,
    user_id BIGINT NOT NULL,
    amount DECIMAL(12, 2) NOT NULL,
    status VARCHAR(32) NOT NULL DEFAULT 'CREATED',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_orders_order_no (order_no),
    KEY idx_orders_user_id (user_id),
    CONSTRAINT fk_orders_user_id FOREIGN KEY (user_id) REFERENCES sys_user(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

当应用启动时,Flyway 会先执行 V1,再执行 V2


10. 启动应用后会发生什么?

确保数据库已经存在:

CREATE DATABASE flyway_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

然后启动 Spring Boot 应用。

你会在日志中看到类似信息:

Migrating schema `flyway_demo` to version "1 - create sys user table"
Migrating schema `flyway_demo` to version "2 - create orders table"
Successfully applied 2 migrations

执行完成后,数据库里会出现三张表:

flyway_schema_history
sys_user
orders

可以查询迁移历史:

SELECT installed_rank, version, description, type, script, checksum, success
FROM flyway_schema_history
ORDER BY installed_rank;

结果大致如下:

+----------------+---------+------------------------+------+----------------------------------+------------+---------+
| installed_rank | version | description            | type | script                           | checksum   | success |
+----------------+---------+------------------------+------+----------------------------------+------------+---------+
| 1              | 1       | create sys user table  | SQL  | V1__create_sys_user_table.sql    | ...        | 1       |
| 2              | 2       | create orders table    | SQL  | V2__create_orders_table.sql      | ...        | 1       |
+----------------+---------+------------------------+------+----------------------------------+------------+---------+

以后再次启动应用时,Flyway 会发现 V1V2 已经执行过,不会重复执行。


11. 新增字段应该怎么做?

假设业务后来要求给订单表增加支付时间字段。

不要修改已经执行过的:

V2__create_orders_table.sql

正确做法是新增一个迁移脚本:

V3__add_paid_at_to_orders.sql

内容:

ALTER TABLE orders
    ADD COLUMN paid_at DATETIME DEFAULT NULL COMMENT '支付时间';

为什么不能改旧脚本?

因为 V2 已经在某些环境执行过了。Flyway 已经把它的 checksum 记录到了 flyway_schema_history。如果你修改旧脚本,下一次启动时 Flyway 会发现本地脚本 checksum 和数据库历史记录不一致,然后报错。

这不是 Flyway 麻烦,而是它在保护你:已经发布过的数据库历史,不应该被悄悄改写。

团队里要形成一个规则:

已经合并、已经部署、已经在共享环境执行过的 V 脚本,不要再修改。后续变更新增脚本继续往前走。


12. Versioned Migration:版本迁移

V 开头的是版本迁移。

常见文件名:

V1__init_schema.sql
V2__create_user_table.sql
V3__add_user_email.sql
V4__create_order_table.sql
V202607161030__add_order_paid_at.sql

版本迁移的特点:

  • 每个版本只执行一次;
  • 按版本号顺序执行;
  • 每个版本号必须唯一;
  • 执行后 checksum 会被记录;
  • 适合建表、加字段、加索引、改数据。

版本号可以用简单数字:

V1__init.sql
V2__add_user.sql
V3__add_order.sql

也可以用带下划线的版本:

V1_1__add_user_email.sql
V1_2__add_user_phone.sql

多人协作时,更推荐使用时间戳,减少版本冲突:

V202607161030__add_user_email.sql
V202607161145__create_order_table.sql

13. Repeatable Migration:可重复迁移

R 开头的是可重复迁移。

例如:

R__refresh_user_order_summary_view.sql

内容:

CREATE OR REPLACE VIEW 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 sys_user u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id, u.username;

Repeatable migration 的特点:

  • 没有版本号;
  • 文件内容变化时会重新执行;
  • checksum 变化是触发重新执行的依据;
  • 总是在所有待执行的版本迁移之后执行;
  • 同一次迁移中,多个 R 脚本按描述排序执行。

适合放在 R 脚本里的内容:

  • 视图;
  • 函数;
  • 存储过程;
  • package;
  • 可整体重刷的基础数据。

可重复迁移一定要保证“重复执行不出错”。所以这类脚本通常会写成:

CREATE OR REPLACE VIEW ...

或者先删除再创建:

DROP VIEW IF EXISTS user_order_summary;

CREATE VIEW user_order_summary AS
SELECT ...

14. 初始化基础数据怎么写?

Flyway 不只能改表结构,也可以写 DML。

比如系统需要初始化角色数据,可以创建:

V4__init_role_data.sql

内容:

CREATE TABLE sys_role (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_code VARCHAR(64) NOT NULL,
    role_name VARCHAR(64) NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_sys_role_code (role_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';

INSERT INTO sys_role (role_code, role_name)
VALUES
    ('ADMIN', '管理员'),
    ('USER', '普通用户');

如果这类数据会经常被整体刷新,也可以使用 R 脚本。但要注意,R 脚本重新执行时可能会重复插入数据,所以要处理幂等。

例如 MySQL 可以这样写:

INSERT INTO sys_role (role_code, role_name)
VALUES
    ('ADMIN', '管理员'),
    ('USER', '普通用户')
ON DUPLICATE KEY UPDATE
    role_name = VALUES(role_name);

15. 多环境配置:开发、测试、生产怎么区分?

生产环境和开发环境通常不应该执行完全一样的数据脚本。

比如开发环境可能需要一些测试账号:

src/main/resources/dev/db/migration/V999__insert_dev_users.sql

然后在 application-dev.yml 中配置:

spring:
  flyway:
    locations: classpath:db/migration,classpath:dev/db/migration

这样:

  • 公共迁移脚本放在 classpath:db/migration
  • 开发环境专用脚本放在 classpath:dev/db/migration
  • 只有启用 dev profile 时,开发脚本才会执行。

生产环境不要配置 dev/db/migration

也可以使用 {vendor} 占位符做数据库类型隔离:

spring:
  flyway:
    locations: classpath:db/migration/{vendor}

例如 MySQL 会使用:

db/migration/mysql

这适合一个项目需要同时兼容不同数据库的场景。


16. 测试环境中的 Flyway

如果你写集成测试,可以把测试专用迁移放到:

src/test/resources/db/migration

例如:

src/test/resources/db/migration/V9999__test_data.sql

测试启动时,这类脚本只会在测试 classpath 中出现,不会被打包到正式应用中。

这很适合准备集成测试数据:

INSERT INTO sys_user (username, email, status)
VALUES
    ('test_user_1', 'test1@example.com', 1),
    ('test_user_2', 'test2@example.com', 1);

注意测试脚本版本号要避免和主迁移冲突。常见做法是使用较大的版本号,例如 V9000V9999


17. 旧项目如何接入 Flyway?

新项目接入 Flyway 很简单,从 V1 开始写就行。

麻烦的是旧项目:数据库里已经有几十张表,但之前没有 Flyway 历史。

这种场景要用 baseline。

假设当前生产数据库已经是一个稳定状态,我们希望从这个状态开始让 Flyway 接管,后续新增脚本从 V2 开始。

可以配置:

spring:
  flyway:
    baseline-on-migrate: true
    baseline-version: 1

含义是:

  • 如果 Flyway 发现数据库不是空的;
  • 并且还没有 flyway_schema_history
  • 就自动创建历史表;
  • 并把当前数据库标记为 baseline version 1
  • 之后 V1 及以下版本不会再执行;
  • 后续从 V2V3 继续迁移。

更稳妥的做法是不要长期打开 baseline-on-migrate。第一次接入时打开,完成后关闭:

spring:
  flyway:
    baseline-on-migrate: false

对于生产库,建议由发布流程显式执行 baseline,而不是每次应用启动都允许自动 baseline。


18. 常用 Flyway 命令

Spring Boot 集成后,日常大多依赖应用启动自动执行。但理解 Flyway 命令仍然很重要。

migrate

执行迁移:

flyway migrate

它会把数据库迁移到最新版本。如果历史表不存在,Flyway 会自动创建。

info

查看迁移状态:

flyway info

可以看到哪些脚本已执行、哪些待执行、哪些失败、哪些缺失。

validate

校验已执行迁移和本地脚本是否一致:

flyway validate

如果有人修改了已经执行过的脚本,通常会在这里暴露。

baseline

给已有数据库打基线:

flyway baseline

适合老项目首次接入 Flyway。

repair

修复 schema history 表:

flyway repair

它可以做几类事情:

  • 移除失败迁移记录;
  • 重新对齐 checksum、description、type;
  • 把缺失的迁移标记为 deleted。

repair 要谨慎使用。它不是日常解决问题的第一选择,更不能用来掩盖随意修改历史脚本的问题。

clean

清理数据库对象:

flyway clean

这个命令会删除 configured schemas 中的对象。官方也明确提醒不要对生产数据库使用。

本地开发有时会用它重置数据库,但生产环境必须禁用。


19. Spring Boot Actuator 查看 Flyway 状态

如果项目引入了 Actuator,可以通过 /actuator/flyway 查看 Flyway 迁移信息。

引入依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

暴露 endpoint:

management:
  endpoints:
    web:
      exposure:
        include: health,info,flyway

请求:

curl http://localhost:8080/actuator/flyway

返回结果里可以看到:

  • script;
  • version;
  • description;
  • checksum;
  • installedOn;
  • executionTime;
  • state。

这在排查线上环境版本时很有用。

不过要注意,Actuator endpoint 暴露了应用内部信息,生产环境需要做好权限控制,不要无保护地暴露到公网。


20. 常用配置速查

下面是 Spring Boot 中常用的 Flyway 配置。

spring:
  flyway:
    enabled: true
    locations: classpath:db/migration
    table: flyway_schema_history
    baseline-on-migrate: false
    baseline-version: 1
    validate-on-migrate: true
    clean-disabled: true
    out-of-order: false
    placeholder-replacement: true
    placeholders:
      app_user: flyway_demo

table

指定 Flyway 历史表名称。

默认:

flyway_schema_history

一般不需要改。

out-of-order

是否允许乱序执行。

默认:

spring:
  flyway:
    out-of-order: false

举个例子:

数据库已经执行到:

V1
V3

这时你又补了一个:

V2

默认情况下,Flyway 不会允许这种“插队”迁移。生产环境通常也不建议打开 out-of-order,否则不同环境的执行顺序可能不一致。

placeholder-replacement

是否启用占位符替换。

默认是 true

配置:

spring:
  flyway:
    placeholders:
      default_status: ACTIVE

SQL 中使用:

INSERT INTO config_item (config_key, config_value)
VALUES ('user.default.status', '${default_status}');

启动时,Flyway 会把 ${default_status} 替换为 ACTIVE


21. Flyway 和 JPA 怎么配合?

一个很常见的问题是:用了 JPA,还需要 Flyway 吗?

答案是:生产项目建议需要。

JPA 的实体类描述的是当前模型,而 Flyway 描述的是数据库从旧状态演进到新状态的过程。

比如当前实体是:

@Entity
public class User {
    @Id
    private Long id;

    private String username;

    private String email;
}

JPA 能告诉你“现在应该有 email 字段”,但它不适合表达:

  • 这个字段什么时候加;
  • 是否需要补历史数据;
  • 是否需要建索引;
  • 字段默认值怎么处理;
  • 老数据怎么迁移;
  • 多环境怎么按顺序升级。

这些事情应该交给 Flyway。

推荐组合是:

spring:
  jpa:
    hibernate:
      ddl-auto: validate
  flyway:
    enabled: true

含义是:

  • Flyway 负责建表、改表、迁移数据;
  • Hibernate 只负责校验实体和数据库是否匹配;
  • 如果不匹配,应用启动失败,提醒你补迁移脚本。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值