Files
his/docs/FLYWAY_USAGE_GUIDE.md
华佗 56ec755cf3 docs: 新增 Flyway 使用指南 + 铁律
- 新增 docs/FLYWAY_USAGE_GUIDE.md (326行完整使用指南)
- AGENTS.md 新增铁律: 数据库变更必须通过 Flyway 迁移
  - 禁止直接执行 DDL 而不创建迁移文件
  - 禁止修改已执行的迁移文件
  - 新表必须包含租户/审计/软删除字段
2026-06-04 14:46:37 +08:00

327 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Flyway 数据库迁移使用指南
> **项目**: OpenHIS 医院管理系统
> **数据库**: PostgreSQL 192.168.110.252:15432 (schema: hisdev)
> **Flyway 版本**: 8.5.x (Spring Boot 2.7 管理)
> **编制日期**: 2026-06-04
---
## 一、当前配置
| 配置项 | 值 | 说明 |
|---|---|---|
| `spring.flyway.enabled` | `true` | 启用 Flyway |
| `spring.flyway.baseline-on-migrate` | `true` | 首次启用时对现有表建基线 |
| `spring.flyway.baseline-version` | `0` | 基线版本号 |
| `spring.flyway.locations` | `classpath:db/migration` | 迁移文件目录 |
| `spring.flyway.validate-on-migrate` | `true` | 执行前校验 |
**迁移文件目录:**
```
openhis-server-new/openhis-application/src/main/resources/db/migration/
```
**当前状态:**
```
V0 << Flyway Baseline >> (自动基线,覆盖现有所有表)
V1 baseline_marker (空标记文件)
```
---
## 二、文件命名规范
```
V{版本号}__{描述}.sql
```
| 规则 | 示例 | 说明 |
|---|---|---|
| 版本号必须递增 | `V2`, `V3`, `V4` | 整数,不可重复 |
| 双下划线分隔 | `V2__add_column.sql` | 单下划线会被当作版本号一部分 |
| 描述用下划线连接 | `V2__add_user_avatar.sql` | 不要用空格或中文 |
| 大小写敏感 | `V2__Add_Column.sql` | 建议全小写 |
**✅ 正确示例:**
```
V2__add_practitioner_avatar.sql
V3__create_nurse_station_table.sql
V4__modify_encounter_diagnosis_index.sql
V5__add_yb_catalog_fields.sql
```
**❌ 错误示例:**
```
v2__add_column.sql # 版本号必须大写 V
V2 add column.sql # 缺少双下划线
V2__Add Column.sql # 描述中有空格
V2.1__add_column.sql # 不支持小数版本号
```
---
## 三、新增表(完整示例)
### 场景:新建一个「手术排班统计」表
**Step 1创建迁移文件**
```sql
-- 文件db/migration/V2__create_surgery_schedule_stats.sql
CREATE TABLE IF NOT EXISTS surgery_schedule_stats (
id BIGINT PRIMARY KEY,
schedule_id BIGINT NOT NULL COMMENT '排程ID',
doctor_code VARCHAR(64) COMMENT '医生编码',
surgery_count INT DEFAULT 0 COMMENT '手术数量',
total_duration INT DEFAULT 0 COMMENT '总时长(分钟)',
tenant_id INT DEFAULT 1 COMMENT '租户ID',
create_by VARCHAR(64) DEFAULT 'system' COMMENT '创建人',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) COMMENT '更新人',
update_time TIMESTAMP COMMENT '更新时间',
valid_flag INT DEFAULT 1 COMMENT '有效标志 1=有效 0=无效'
);
COMMENT ON TABLE surgery_schedule_stats IS '手术排班统计表';
CREATE INDEX idx_surgery_stats_schedule ON surgery_schedule_stats(schedule_id);
CREATE INDEX idx_surgery_stats_tenant ON surgery_schedule_stats(tenant_id);
```
**Step 2启动应用**
```bash
cd openhis-server-new
mvn clean package -DskipTests
java -jar openhis-application/target/openhis-application.jar --spring.profiles.active=dev --server.port=18082
```
**Step 3Flyway 自动执行**
启动日志中会看到:
```
Flyway 迁移完成,执行了 1 个迁移
```
数据库中 `flyway_schema_history` 表新增一条记录:
```
installed_rank | version | description | type | success
---------------+---------+--------------------------+------+---------
3 | 2 | create surgery schedule.. | SQL | t
```
---
## 四、修改表结构ALTER
### 场景:给 practitioners 表加一个 phone 字段
```sql
-- 文件db/migration/V3__add_practitioner_phone.sql
-- PostgreSQL
ALTER TABLE practitioner ADD COLUMN IF NOT EXISTS phone VARCHAR(32) COMMENT '联系电话';
```
### 场景:给表加索引
```sql
-- 文件db/migration/V4__add_encounter_index.sql
CREATE INDEX IF NOT EXISTS idx_encounter_patient ON adm_encounter(patient_id);
CREATE INDEX IF NOT EXISTS idx_encounter_tenant ON adm_encounter(tenant_id);
```
### 场景:修改字段类型
```sql
-- 文件db/migration/V5__extend_charge_item_code.sql
-- PostgreSQL: 修改 varchar 长度
ALTER TABLE adm_charge_item ALTER COLUMN charge_item_code TYPE VARCHAR(128);
```
---
## 五、多租户表迁移
项目有 50+ 张多租户表(`tenant_id` 字段),新增的多租户表需要:
```sql
-- 文件db/migration/V6__create_clinic_referral_table.sql
CREATE TABLE IF NOT EXISTS clinic_referral (
id BIGINT PRIMARY KEY,
encounter_id BIGINT NOT NULL,
referral_reason TEXT,
tenant_id INT DEFAULT 1,
create_by VARCHAR(64) DEFAULT 'system',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
valid_flag INT DEFAULT 1
);
COMMENT ON TABLE clinic_referral IS '转诊记录表';
```
然后在 `MybatisPlusConfig.java``TENANT_TABLES` 集合中添加表名:
```java
private static final Set<String> TENANT_TABLES = new HashSet<>(Arrays.asList(
// ... 现有表 ...
"clinic_referral" // 新增
));
```
---
## 六、开发规范
### 必须遵守
| 规则 | 原因 |
|---|---|
| **不要修改已执行的迁移文件** | Flyway 会校验 checksum修改后启动报错 |
| **版本号只能递增** | 不能回退版本号 |
| **每次只改一个表** | 方便回滚和排查 |
| **使用 `IF NOT EXISTS`** | 防止重复执行报错 |
| **迁移文件加入 Git** | 全团队共享迁移历史 |
### 推荐做法
| 做法 | 说明 |
|---|---|
| 先在测试环境验证 | 生产部署前确认迁移无误 |
| 一个迁移文件改一张表 | 便于追踪和回滚 |
| 文件名描述清晰 | `V2__add_yb_catalog_drug_name.sql``V2__update.sql` 好 |
| DDL 和 DML 分开 | 建表用 `V2__create_xxx.sql`,数据初始化用 `V3__init_xxx_data.sql` |
---
## 七、回滚方案
Flyway **不支持自动回滚**,需要手动处理:
### 情况 1迁移刚执行还没提交代码
```bash
# 1. 删除 flyway_schema_history 中的记录
PGPASSWORD=Jchl1528 psql -h 192.168.110.252 -p 15432 -U postgresql -d postgresql \
-c "SET search_path TO hisdev; DELETE FROM flyway_schema_history WHERE version = '6';"
# 2. 手动撤销 DDL
PGPASSWORD=Jchl1528 psql -h 192.168.110.252 -p 15432 -U postgresql -d postgresql \
-c "SET search_path TO hisdev; DROP TABLE IF EXISTS clinic_referral;"
# 3. 删除迁移文件
rm openhis-server-new/openhis-application/src/main/resources/db/migration/V6__create_clinic_referral_table.sql
# 4. 重启应用
```
### 情况 2已提交代码需要紧急回滚
```sql
-- 手动执行逆向 SQL
DROP TABLE IF EXISTS clinic_referral;
DELETE FROM flyway_schema_history WHERE version = '6';
```
---
## 八、常用排查命令
```sql
-- 查看所有已执行的迁移
SELECT installed_rank, version, description, type, success, installed_on
FROM flyway_schema_history
ORDER BY installed_rank;
-- 查看是否有失败的迁移
SELECT * FROM flyway_schema_history WHERE success = false;
-- 查看当前最新版本
SELECT MAX(version) AS current_version FROM flyway_schema_history;
-- 手动标记某版本为成功(紧急修复用)
-- UPDATE flyway_schema_history SET success = true WHERE version = '6';
```
---
## 九、文件清单
```
openhis-server-new/openhis-application/src/main/resources/db/migration/
├── README.md # 使用说明
├── V1__baseline_marker.sql # 基线标记(空文件)
├── V2__xxx.sql # 你的第一个迁移
├── V3__xxx.sql # 第二个迁移
└── ...
```
---
## 十、注意事项HIS 系统特有)
| 场景 | 处理方式 |
|---|---|
| **新功能开发** | 建表/改表时创建 `V{n}__xxx.sql` |
| **代码生成器生成的表** | 生成后把 DDL 放入迁移文件 |
| **Flowable 工作流表** | 由 Flowable 自己管理,不要用 Flyway 管 |
| **多租户字段** | 新表必须加 `tenant_id INT DEFAULT 1` |
| **逻辑删除字段** | 新表必须加 `valid_flag INT DEFAULT 1` |
| **审计字段** | 新表必须加 `create_by`, `create_time`, `update_by`, `update_time` |
---
## 十一、PostgreSQL 常用 DDL 速查
### 建表模板
```sql
CREATE TABLE IF NOT EXISTS {表名} (
id BIGINT PRIMARY KEY,
{字段} {类型} {默认值} COMMENT '{说明}',
tenant_id INT DEFAULT 1 COMMENT '租户ID',
create_by VARCHAR(64) DEFAULT 'system' COMMENT '创建人',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) COMMENT '更新人',
update_time TIMESTAMP COMMENT '更新时间',
valid_flag INT DEFAULT 1 COMMENT '有效标志 1=有效 0=无效'
);
COMMENT ON TABLE {表名} IS '{表说明}';
CREATE INDEX idx_{表名}_{字段} ON {表名}({字段});
```
### 加字段
```sql
ALTER TABLE {表名} ADD COLUMN IF NOT EXISTS {字段} {类型} COMMENT '{说明}';
```
### 加索引
```sql
CREATE INDEX IF NOT EXISTS idx_{表名}_{字段} ON {表名}({字段});
```
### 改字段类型
```sql
ALTER TABLE {表名} ALTER COLUMN {字段} TYPE {新类型};
```
### 删字段
```sql
ALTER TABLE {表名} DROP COLUMN IF EXISTS {字段};
```
### 删表
```sql
DROP TABLE IF EXISTS {表名};
```