「数据库初始化与表结构」使用 schema.sql 和 data.sql 从空数据库创建表,但这种方式不会记录数据库已经执行过哪些结构变更。「事务」章节又增加了历史表,接下来只要修改列、约束或索引,就必须知道数据库当前处于哪个版本。
Flyway 会按版本执行迁移,并在数据库中保存执行记录。本章把已有结构交给 Flyway 管理,再用第二个迁移为历史查询增加索引。Spring Boot 对 Flyway 的集成方式可在 Database Initialization 中核对。
1. 迁移与初始化
初始化脚本描述的是「数据库最终应该有什么」,迁移脚本描述的是「数据库怎样从一个版本变成下一个版本」。两者解决的问题不同:
| 方式 | 执行依据 | 适合场景 | 主要限制 |
|---|---|---|---|
schema.sql | 应用每次启动时尝试执行 | 空数据库、简单原型 | 不记录历史,无法可靠升级已有结构 |
| Flyway 版本迁移 | 只执行尚未登记的版本 | 持续演进的数据库 | 已执行脚本必须保持不变 |
假设数据库已经执行过 V1,应用发布时又包含 V2 和 V3,Flyway 会先校验已有记录,再依次执行缺失的版本。应用实例看到的不是一份无法追踪的最终 SQL,而是一条明确的演进路径:
1空数据库2-> V1:创建当前状态表和历史表3-> V2:创建历史查询索引4-> V3:以后新增的结构变化
迁移脚本应该与应用代码一起进入版本控制。部署新版本时,数据库结构和依赖它的代码才能沿着同一条发布历史前进。
2. 添加依赖
在已有 JDBC 和 PostgreSQL 驱动依赖旁边增加 Flyway Starter 与 PostgreSQL 模块:
1<dependency>2<groupId>org.springframework.boot</groupId>3<artifactId>spring-boot-starter-flyway</artifactId>4</dependency>56<dependency>7<groupId>org.flywaydb</groupId>8<artifactId>flyway-database-postgresql</artifactId>9</dependency>
spring-boot-starter-flyway 提供 Flyway 与 Spring Boot 的启动集成,flyway-database-postgresql 提供 PostgreSQL 数据库支持。项目使用 Spring Boot Parent 管理兼容版本,因此这里不单独填写 <version>。
PostgreSQL JDBC 驱动仍然需要保留:Flyway 负责迁移流程,真正建立 JDBC 连接的仍然是数据库驱动。spring-boot-starter-jdbc 也继续为 JdbcClient、事务和连接池提供支持。
应用启动时,Spring Boot 会使用主 DataSource 自动配置 Flyway,并在依赖数据库结构的 Repository 开始工作前执行迁移。不需要在启动类中手动调用 Flyway.migrate()。
3. 创建初始版本
Flyway 默认从 src/main/resources/db/migration 读取迁移。创建 V1__create_health_tables.sql,把原有的两张表和初始数据放入第一个版本:
01create table health_status (02service varchar(50) primary key,03status varchar(20) not null,04checked_at timestamptz not null default current_timestamp05);0607create table health_check_history (08id bigint generated always as identity primary key,09service varchar(50) not null,10status varchar(20) not null,11checked_at timestamptz not null default current_timestamp12);1314insert into health_status (service, status)15values ('spring', 'UP');
文件名分为三个部分:
| 部分 | 示例 | 含义 |
|---|---|---|
| 前缀 | V | 这是一次有版本的迁移 |
| 版本 | 1 | 决定执行顺序,后续也可以使用 1_1 |
| 描述 | create_health_tables | 双下划线之后的人类可读说明 |
版本与描述之间必须是两个下划线。描述中的下划线会在迁移记录中显示为空格,不影响执行顺序。
迁移中不使用 IF NOT EXISTS 和 ON CONFLICT DO NOTHING。Flyway 保证同一版本只执行一次;如果目标对象意外提前存在,应让迁移失败并调查数据库状态,而不是跳过冲突后留下无法确认的结构。
4. 增加第二个版本
历史查询通常按检查时间倒序排列,并且可能只查询某个服务。为这两种访问方式建立索引,新建第二个迁移:
1create index idx_health_history_checked_at2on health_check_history (checked_at desc, id desc);34create index idx_health_history_service_checked_at5on health_check_history (service, checked_at desc, id desc);
第一个索引服务于全部历史的时间倒序查询,第二个索引先按 service 定位,再按时间和 id 排序。id 作为第二排序键,可以在多条记录时间相同时保持稳定顺序。
不要回到 V1 直接补写索引。某些数据库已经执行过 V1,另一些数据库还没有;修改旧文件会让同一个版本在不同环境代表不同内容。正确做法是始终增加新的、更高版本迁移。
迁移目录此时包含:
5. 切换管理机制
Flyway 和 Spring Boot 基础 SQL 初始化不应该同时管理相同表。删除原有 src/main/resources/schema.sql 与 data.sql,并把配置调整为:
1spring:2sql:3init:4mode: never5flyway:6enabled: true
spring.flyway.enabled 在依赖存在时默认就是 true,这里显式写出是为了说明当前由谁管理结构。spring.sql.init.mode: never 则明确关闭旧机制。
本教程前面已经使用 schema.sql 创建过本地表,而这个数据库还没有 Flyway 历史。对可丢弃的本地学习数据,最清晰的切换方式是重建 Docker 数据卷:
1docker compose down --volumes2docker compose up -d3DB_PASSWORD='spring' ./mvnw spring-boot:run
docker compose down --volumes 会永久删除当前 Compose 项目的数据库卷及其中数据,只能用于确认可以丢弃的本地环境。执行前应先停止应用,并确认没有需要保留的数据。
已经保存业务数据的数据库不能这样处理,也不应该直接开启 baseline-on-migrate 碰运气。应先备份并核对实际结构,再设计与现状匹配的基线版本,或通过受控的 Flyway 基线操作登记起点;从哪一个版本开始登记取决于数据库已经具备哪些结构。
6. 执行记录与校验
启动应用后,Flyway 会创建 flyway_schema_history 表。进入 psql 查询:
1select installed_rank, version, description, type, success2from flyway_schema_history3order by installed_rank;
应该看到 1 / create health tables 和 2 / add history query indexes 两条成功记录。再次启动应用时,这两个版本不会重复执行,Flyway 只进行校验并查找更高版本。
Flyway 会为版本迁移保存校验和。如果已经执行过的脚本后来被修改,启动校验会失败。这不是需要绕过的障碍,而是在提醒当前代码中的迁移历史与数据库登记的历史不一致。
此时应先判断原因:开发者是否错误修改了旧脚本、数据库是否连接到了错误环境,或迁移记录是否遭到人工改动。正常的结构变化应放入新的迁移;repair 会修改历史记录,只能在已经理解并修复实际数据库状态后谨慎使用,不能作为通用的启动失败处理命令。
PostgreSQL 支持在事务中执行大多数常见 DDL,Flyway 可以在失败时回滚相应迁移,但不同数据库和特殊语句的事务能力并不完全相同。迁移脚本仍要保持小而明确,并在发布前使用与生产相同的数据库类型验证。
下一章会让测试自动启动独立的 PostgreSQL 容器。测试连接建立后,Flyway 将对这个全新的数据库执行同一组迁移,从而验证测试环境和实际运行环境使用的是同一份表结构来源。