# 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 3:Flyway 自动执行** 启动日志中会看到: ``` 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 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 {表名}; ```