From d583bdc5c88b08cf11d911dbd7faf20521848abd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=85=E6=88=BF?= Date: Mon, 29 Dec 2025 14:07:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=9B=86=E6=88=90Flyway=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E8=BF=81=E7=A7=BB=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加Flyway依赖到pom.xml - 配置application.yml启用Flyway - 创建迁移脚本目录db/migration - 添加V1基线脚本和V2项目字段迁移脚本 - 添加DATABASE_MIGRATION.md使用文档 --- docs/DATABASE_MIGRATION.md | 224 ++++++++++++++++++ pom.xml | 10 + src/main/resources/application.yml | 21 ++ .../resources/db/migration/V1__baseline.sql | 12 + .../db/migration/V2__add_project_fields.sql | 42 ++++ 5 files changed, 309 insertions(+) create mode 100644 docs/DATABASE_MIGRATION.md create mode 100644 src/main/resources/db/migration/V1__baseline.sql create mode 100644 src/main/resources/db/migration/V2__add_project_fields.sql diff --git a/docs/DATABASE_MIGRATION.md b/docs/DATABASE_MIGRATION.md new file mode 100644 index 0000000..c54b9cd --- /dev/null +++ b/docs/DATABASE_MIGRATION.md @@ -0,0 +1,224 @@ +# 数据库迁移指南 + +本项目使用 **Flyway** 进行数据库版本管理和自动迁移。 + +## 概述 + +Flyway 是一个数据库迁移工具,它能够: +- 自动追踪数据库版本 +- 按顺序执行迁移脚本 +- 确保团队成员的数据库结构一致 +- 支持回滚和修复 + +## 工作原理 + +1. 应用启动时,Flyway 自动扫描 `src/main/resources/db/migration` 目录 +2. 检查 `flyway_schema_history` 表,确定已执行的版本 +3. 按版本号顺序执行未运行的迁移脚本 +4. 记录执行结果到历史表 + +## 迁移脚本命名规范 + +``` +V{版本号}__{描述}.sql +``` + +### 命名规则 + +| 规则 | 说明 | 示例 | +|------|------|------| +| 前缀 | 必须以 `V` 开头(大写) | V1, V2, V10 | +| 版本号 | 数字,支持小数点 | 1, 2, 2.1, 10 | +| 分隔符 | **两个下划线** | `__` | +| 描述 | 用下划线连接单词 | add_user_table | +| 后缀 | 必须是 `.sql` | .sql | + +### 正确示例 + +``` +V1__baseline.sql # 基线版本 +V2__add_project_fields.sql # 添加项目字段 +V3__create_order_table.sql # 创建订单表 +V4__add_index_to_user.sql # 添加用户索引 +V4.1__fix_user_column_type.sql # 修复用户列类型(小版本) +V10__major_refactor.sql # 大版本重构 +``` + +### 错误示例 + +``` +v1__init.sql # 错误:v 应该大写 +V1_init.sql # 错误:只有一个下划线 +V1-init.sql # 错误:使用了连字符 +V1__init.SQL # 错误:后缀应该小写 +init.sql # 错误:缺少版本前缀 +``` + +## 如何添加新的迁移 + +### 步骤 1:确定版本号 + +查看当前最新版本: +```bash +ls src/main/resources/db/migration/ +``` + +新版本号 = 最新版本号 + 1 + +### 步骤 2:创建迁移脚本 + +在 `src/main/resources/db/migration/` 目录创建新文件: + +```sql +-- ===================================================== +-- 迁移脚本: [描述] +-- 版本: V{版本号} +-- 描述: [详细说明] +-- 日期: YYYY-MM-DD +-- ===================================================== + +-- 你的 SQL 语句 +ALTER TABLE xxx ADD COLUMN yyy VARCHAR(100); +``` + +### 步骤 3:测试迁移 + +本地启动应用,观察日志: +``` +Flyway Community Edition 9.x.x +Successfully validated 3 migrations +Current version of schema: 2 +Migrating schema to version 3 - create_order_table +Successfully applied 1 migration +``` + +### 步骤 4:提交代码 + +```bash +git add src/main/resources/db/migration/V3__xxx.sql +git commit -m "db: 添加xxx迁移脚本" +git push +``` + +## 最佳实践 + +### 1. 幂等性脚本 + +编写可重复执行的脚本,避免重复执行报错: + +```sql +-- 添加列(如果不存在) +SET @exist := (SELECT COUNT(*) FROM information_schema.columns + WHERE table_schema = DATABASE() + AND table_name = 'your_table' + AND column_name = 'new_column'); +SET @sql := IF(@exist = 0, + 'ALTER TABLE your_table ADD COLUMN new_column VARCHAR(100)', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; +``` + +### 2. 不要修改已执行的脚本 + +一旦迁移脚本被执行(已提交到版本库),**永远不要修改它**。 + +如果需要修复,创建新的迁移脚本: +``` +V3__create_table.sql # 已执行,有错误 +V4__fix_v3_error.sql # 新建脚本修复错误 +``` + +### 3. 小步迁移 + +每个迁移脚本只做一件事: +- V2__add_user_email.sql +- V3__add_user_phone.sql +- 不要: V2__add_user_email_and_phone_and_address.sql + +### 4. 添加注释 + +```sql +-- ===================================================== +-- 迁移脚本: 添加用户邮箱字段 +-- 版本: V5 +-- 描述: 为用户表添加邮箱字段,用于接收通知 +-- 作者: 张三 +-- 日期: 2024-12-29 +-- 关联需求: JIRA-123 +-- ===================================================== +``` + +### 5. 备份数据 + +生产环境执行迁移前,务必备份数据库: +```bash +mysqldump -u root -p martial_db > backup_$(date +%Y%m%d).sql +``` + +## 常见问题 + +### Q1: 迁移失败怎么办? + +1. 查看错误日志,定位问题 +2. 修复数据库中的问题(手动) +3. 修复迁移脚本 +4. 执行 Flyway repair(如需要) + +### Q2: 如何跳过某个版本? + +不建议跳过版本。如果必须跳过,可以创建空脚本: +```sql +-- V3__placeholder.sql +-- 此版本跳过 +SELECT 1; +``` + +### Q3: 多人开发版本冲突怎么办? + +使用日期时间作为版本号前缀: +``` +V20241229001__add_field.sql +V20241229002__fix_bug.sql +``` + +### Q4: 如何查看迁移历史? + +```sql +SELECT * FROM flyway_schema_history ORDER BY installed_rank; +``` + +## 目录结构 + +``` +src/main/resources/ +└── db/ + └── migration/ + ├── V1__baseline.sql # 基线版本 + ├── V2__add_project_fields.sql # 添加项目字段 + └── V3__xxx.sql # 后续迁移... +``` + +## 配置说明 + +application.yml 中的 Flyway 配置: + +```yaml +spring: + flyway: + enabled: true # 启用 Flyway + locations: classpath:db/migration # 迁移脚本位置 + table: flyway_schema_history # 版本历史表名 + baseline-version: 0 # 基线版本号 + baseline-on-migrate: true # 自动执行基线 + validate-on-migrate: true # 校验迁移脚本 + encoding: UTF-8 # 脚本编码 + out-of-order: false # 禁止乱序执行 + clean-disabled: true # 禁用清理(生产安全) +``` + +## 参考资料 + +- [Flyway 官方文档](https://flywaydb.org/documentation/) +- [Spring Boot Flyway 集成](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-initialization.migration-tool.flyway) diff --git a/pom.xml b/pom.xml index 4b6d0ef..f29c360 100644 --- a/pom.xml +++ b/pom.xml @@ -228,6 +228,16 @@ lombok provided + + + + org.flywaydb + flyway-core + + + org.flywaydb + flyway-mysql + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index a843cf3..c351568 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -50,6 +50,27 @@ spring: session-stat-enable: true session-stat-max-count: 10 + +# Flyway 数据库迁移配置 + flyway: + enabled: true + # 迁移脚本位置 + locations: classpath:db/migration + # 版本表名 + table: flyway_schema_history + # 基线版本号(用于已有数据库) + baseline-version: 0 + # 如果数据库不为空,是否自动执行基线 + baseline-on-migrate: true + # 是否校验迁移脚本 + validate-on-migrate: true + # 编码 + encoding: UTF-8 + # 是否允许乱序执行 + out-of-order: false + # 是否清理数据库(生产环境务必设为false) + clean-disabled: true + # mybatis mybatis-plus: mapper-locations: classpath:org/springblade/**/mapper/*Mapper.xml diff --git a/src/main/resources/db/migration/V1__baseline.sql b/src/main/resources/db/migration/V1__baseline.sql new file mode 100644 index 0000000..fdf459d --- /dev/null +++ b/src/main/resources/db/migration/V1__baseline.sql @@ -0,0 +1,12 @@ +-- ===================================================== +-- Flyway 基线脚本 +-- 版本: V1 +-- 描述: 记录当前数据库结构基线,不执行任何操作 +-- 日期: 2024-12-29 +-- ===================================================== + +-- 此脚本作为基线版本,用于标记已有数据库的初始状态 +-- 由于 baseline-on-migrate: true,Flyway 会自动将此版本标记为已执行 +-- 后续的迁移脚本从 V2 开始 + +SELECT 1; diff --git a/src/main/resources/db/migration/V2__add_project_fields.sql b/src/main/resources/db/migration/V2__add_project_fields.sql new file mode 100644 index 0000000..396ad96 --- /dev/null +++ b/src/main/resources/db/migration/V2__add_project_fields.sql @@ -0,0 +1,42 @@ +-- ===================================================== +-- 迁移脚本: 添加项目表字段 +-- 版本: V2 +-- 描述: 为 martial_project 表添加 event_type 和报名时间字段 +-- 日期: 2024-12-29 +-- ===================================================== + +-- 添加 event_type 字段(如果不存在) +SET @exist := (SELECT COUNT(*) FROM information_schema.columns + WHERE table_schema = DATABASE() + AND table_name = 'martial_project' + AND column_name = 'event_type'); +SET @sql := IF(@exist = 0, + 'ALTER TABLE martial_project ADD COLUMN event_type int DEFAULT NULL COMMENT "项目类型: 1-套路 2-散打 3-器械 4-对练" AFTER category', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 添加 registration_start_time 字段(如果不存在) +SET @exist := (SELECT COUNT(*) FROM information_schema.columns + WHERE table_schema = DATABASE() + AND table_name = 'martial_project' + AND column_name = 'registration_start_time'); +SET @sql := IF(@exist = 0, + 'ALTER TABLE martial_project ADD COLUMN registration_start_time datetime DEFAULT NULL COMMENT "报名开始时间" AFTER price', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 添加 registration_end_time 字段(如果不存在) +SET @exist := (SELECT COUNT(*) FROM information_schema.columns + WHERE table_schema = DATABASE() + AND table_name = 'martial_project' + AND column_name = 'registration_end_time'); +SET @sql := IF(@exist = 0, + 'ALTER TABLE martial_project ADD COLUMN registration_end_time datetime DEFAULT NULL COMMENT "报名结束时间" AFTER registration_start_time', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt;