迁移(Flyway / Liquibase)
Laravel 用
php artisan make:migration+php artisan migrate管理表结构版本;Spring Boot 的标配是 Flyway 或 Liquibase。这篇主推 Flyway(更接近 Laravel 的「SQL 文件 + 版本号」心智模型)。
为什么需要迁移
Java 项目(尤其团队协作)不允许手动去数据库建表。理由和 Laravel 一样:
- 表结构随代码入库,可审查、可回滚
- 新同事 clone 项目 → 一条命令建出完整库
- 多环境(dev/staging/prod)结构一致
- 部署时自动升级表结构
Flyway 的工作方式
Flyway 扫描 src/main/resources/db/migration/ 下的 SQL 文件,按版本号顺序执行一次,并在 flyway_schema_history 表记录执行历史(类似 Laravel 的 migrations 表)。
db/migration/
├── V1__create_posts_table.sql
├── V2__add_status_to_posts.sql
└── V3__create_comments_table.sql文件名规范:V{版本号}__{描述}.sql
- 数字版本号:
V1__xxx.sql、V1_1__xxx.sql、V2__xxx.sql - 每个文件只会执行一次(已执行的版本号不会重跑)
快速上手
1. 依赖
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<!-- 不同数据库还要 flyway-mysql 等,见下文 --><!-- MySQL 8 需要额外加数据库模块 -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>2. 写第一个迁移文件
src/main/resources/db/migration/V1__create_posts_table.sql:
CREATE TABLE posts (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(100) NOT NULL,
content TEXT NOT NULL,
user_id BIGINT NOT NULL,
status TINYINT NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_posts_user_id (user_id)
);3. 启动即自动迁移
配置好数据源后,应用启动时 Flyway 自动执行未跑过的迁移。也可以手动:
./mvnw spring-boot:run # 启动时自动跑对照 Laravel
| Laravel | Flyway |
|---|---|
database/migrations/2026_01_01_create_posts_table.php | V1__create_posts_table.sql |
php artisan migrate | 应用启动自动执行 |
migrations 表 | flyway_schema_history 表 |
| 版本号按时间戳 | 版本号 V1、V2…… |
| 迁移文件用 PHP 类(可执行逻辑) | 迁移文件就是纯 SQL |
4. 修改已有表
改表不能改旧文件,要新增 V 文件(和 Laravel 一样):
-- V2__add_status_to_posts.sql
ALTER TABLE posts ADD COLUMN published_at TIMESTAMP NULL;常用配置
spring:
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true # 对已有数据的库,允许以当前状态为基线TIP
baseline-on-migrate: true 非常实用:已有的生产库(表是手动建的、没有 flyway 历史)第一次启用 Flyway 时,不会因为版本对不上而报错,而是以当前库状态为基线继续往后迁移。
回滚怎么办
Laravel 有 migrate:rollback;Flyway 的哲学是 「只前进,不回头」:
- 已经执行过的 V 文件不允许修改(校验和会变,启动直接报错)
- 回滚靠反向 SQL 文件(
U 版本)或新迁移
-- V2__add_status_to_posts.sql(如果已经发布)
-- 修正方式是 V3 再改,而不是改 V2WARNING
永远不要修改已经跑过的迁移文件。Flyway 会校验文件 checksum,改了就报 Migration checksum mismatch。正确做法:新增 V 版本文件。这和 Laravel「不要改动已发布的迁移」是同一纪律。
测试环境用什么
- 单元测试:H2 + Flyway 自动建 schema,秒级
- 本地开发:MySQL 本地实例 + Flyway
- CI:起个 MySQL 容器,Flyway 建表 → 跑测试
# application-test.yml
spring:
datasource:
url: jdbc:h2:mem:testdb;MODE=MySQL
flyway:
locations: classpath:db/migrationH2 兼容模式
MODE=MySQL 让 H2 尽量兼容 MySQL 语法,很多迁移 SQL 能在 H2 上跑。但复杂 SQL(如某些索引、字符集)可能不兼容,测试环境理想还是用真实 MySQL 容器。
Flyway vs Liquibase
| 维度 | Flyway | Liquibase |
|---|---|---|
| 迁移格式 | 纯 SQL 文件 | XML / YAML / SQL(框架能解析变更集) |
| 上手 | 简单直接 | 学习成本高 |
| 回滚 | 不支持自动回滚,需反向 SQL | 支持 changeset 回滚 |
| 心智模型 | 接近 Laravel | 更「框架化」 |
| 选谁 | 多数项目选它 | 需要精细变更管理的大型团队 |
总结
新项目直接 Flyway。db/migration 放 SQL 文件,和 Laravel database/migrations 的用法几乎一样,唯一差别是「启动自动执行,不用手动跑 artisan 命令」。
最佳实践清单
- 文件名:
V版本__驼峰描述.sql,版本号递增 - 已执行文件永不修改,改表加新 V 文件
- 一个迁移文件只做一件事
baseline-on-migrate: true兼容已有库- 生产部署前 review 迁移 SQL,DDL 加
IF NOT EXISTS防御