# Generator 基础设施补丁 — Kingbase Import Identity 修复记录 本文档详细记录 RuoYi Generator 代码生成器在 KingbaseES 数据库环境下的核心运行时兼容性问题排查、补丁设计、安全加固与验证全过程。 --- ## 1. 问题背景 RailwaySystem 后台基于 RuoYi-Vue 架构改造,数据库由 MySQL 迁移至国产 KingbaseES (V8R6 / PostgreSQL 语法体系)。代码生成器(`ruoyi-generator` 模块)用于读取数据库系统元数据、导入数据表配置并自动生成前端 Vue 与后端 Java/MyBatis 代码。 在 Generator 元数据体系中,包含两张核心表: - `gen_table`:存储已导入代码生成的表元数据; - `gen_table_column`:存储表关联的列元数据。 在最初从 MySQL 初始化表结构至 Kingbase 时,这两张表的自增主键定义未能正确映射为 Kingbase 标准的 Identity 列。 --- ## 2. 第一次真实 Import 如何暴露问题 在推进首个业务模块试点开发(**Teacher Pilot**,教师主数据模块)期间,需要通过前端代码生成管理界面(`/tool/gen`)调用“导入表(Import Table)”功能将 `train_teacher` 表导入至 Generator 配置库中。 当点击导入并提交时,后端触发了 `GenTableServiceImpl.importGenTable` 调用。此时服务端直接抛出数据库异常,表导入操作失败,导致后续代码生成流程无法继续。 --- ## 3. 错误现象 后端日志与数据库抛出类似如下的主键冲突与唯一约束冲突异常: ```text ### Error updating database. Cause: com.kingbase8.util.KSQLException: ERROR: duplicate key value violates unique constraint "gen_table_pkey" Detail: Key (table_id)=(0) already exists. ### The error may exist in URL [jar:file:.../ruoyi-generator.jar!/mapper/generator/GenTableMapper.xml] ### The error may involve com.ruoyi.generator.mapper.GenTableMapper.insertGenTable-Inline ### The error occurred while setting parameters ### SQL: insert into gen_table ( table_name, table_comment, class_name, ... ) values ( ?, ?, ?, ... ) ``` 对于 `gen_table_column`,同样在批量插入字段元数据时出现所有字段的 `column_id` 均被赋值为默认值 `0`,从而连续抛出主键冲突。 --- ## 4. 根因分析 1. **MyBatis 配置与运行时预期**: `GenTableMapper.xml` 与 `GenTableColumnMapper.xml` 中的插入语句均配置了: ```xml ``` RuoYi 的应用层期望数据库能够在插入记录时自动生成唯一递增主键,并通过 JDBC 驱动的 `getGeneratedKeys` 返回该主键值并回填至 Java 实体对象的 `tableId` / `columnId`。 2. **原始 MySQL 建表定义**: 在 `RuoYi-Vue/sql/ry_20250522.sql` 中,原始表结构为: ```sql CREATE TABLE gen_table ( table_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', ... PRIMARY KEY (table_id) ); CREATE TABLE gen_table_column ( column_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', ... PRIMARY KEY (column_id) ); ``` 3. **Kingbase 结构缺失 Identity / Sequence**: 在早期导入 Kingbase 实例时,由于 KingbaseES 不支持 MySQL 方言的 `AUTO_INCREMENT` 关键字,该列在创建时仅退化为 `BIGINT NOT NULL`,未配置 `GENERATED BY DEFAULT AS IDENTITY`,也未创建对应的自增序列(Sequence)。 由于 INSERT 语句未指定主键值,在某些兼容模式下默认填入 `0`,第二次插入必然导致唯一主键冲突。 4. **定位结论**: 本问题属于典型的 **Generator 运行时基础设施兼容性缺陷(Generator Runtime Compatibility Bug)**,与业务层 Database V2 数据模型无关。 --- ## 5. 补丁文件拆分与核心 DDL 为了避免局部补丁状态(Partial Patch)下执行发生冲突,补丁拆分为两个独立文件: ### 5.1 `002a_gen_table_identity_patch.sql` ```sql ALTER TABLE gen_table ALTER COLUMN table_id ADD GENERATED BY DEFAULT AS IDENTITY; ``` ### 5.2 `002b_gen_table_column_identity_patch.sql` ```sql ALTER TABLE gen_table_column ALTER COLUMN column_id ADD GENERATED BY DEFAULT AS IDENTITY; ``` 执行后,KingbaseES 底层会自动创建并维护两个关联序列: - `gen_table_table_id_seq` - `gen_table_column_column_id_seq` --- ## 6. Precheck(预检)与 Partial Patch 安全机制 ### 6.1 Partial Patch Safety(局部补丁状态安全) 在实际运维过程中,数据库可能处于以下四种状态之一: 1. **Case 1 (`CASE_1_BOTH_NEED_PATCH`)**:两列均未配置 Identity; 2. **Case 2 (`CASE_2_BOTH_ALREADY_PATCHED`)**:两列均已配置 Identity; 3. **Case 3 (`CASE_3_ONLY_COLUMN_NEEDS_PATCH`)**:`table_id` 已修复,但 `column_id` 未修复; 4. **Case 4 (`CASE_4_ONLY_TABLE_NEEDS_PATCH`)**:`column_id` 已修复,但 `table_id` 未修复。 若在 Case 3 下全量执行整脚本,Kingbase 执行 `gen_table` ALTER 时将直接报错 `column "table_id" of relation "gen_table" is already an identity column` 并中断退出。 **解决方案**: - `001_generator_identity_precheck.sql` 会直接输出全局 Case 判定; - 补丁拆分为 `002a` 与 `002b`,根据 Precheck 的判定选择性执行,实现精准幂等与局部安全性。 --- ## 7. Existing Metadata Safety 与 Sequence Alignment ### 7.1 核心原则 > **Identity 属性存在 ≠ Sequence 一定适合下一次 Insert** 在执行 `ALTER TABLE ... ADD GENERATED BY DEFAULT AS IDENTITY` 时: - **空表环境(0 rows)**:默认 Sequence `start_value = 1`,下一次插入分配 ID 为 1,天然安全; - **已有数据环境(已存在历史元数据)**: - Kingbase `ADD IDENTITY` 默认创建从 1 开始的 Sequence,**不宣称自动对齐已有数据的 MAX(id)**; - 若直接执行 Insert,Sequence 将尝试从 1 开始分配,如果已有数据包含 `id = 1`,将再次引发主键冲突! ### 7.2 Sequence 对齐机制(Sequence Alignment) 对于非空表环境,执行 Patch 后必须通过 Verify 脚本校验序列游标: - 计算表达式:`next_id = (CASE WHEN is_called THEN last_value + 1 ELSE last_value END)` - 若 `next_id > MAX(existing_id)`,判定为 `SAFE`; - 若 `next_id <= MAX(existing_id)`,判定为 `UNSAFE`,必须由 DBA 执行专项对齐脚本: ```sql -- 同步 Sequence 游标至当前最大值之后 SELECT setval('gen_table_table_id_seq', (SELECT COALESCE(MAX(table_id), 0) + 1 FROM gen_table), false); SELECT setval('gen_table_column_column_id_seq', (SELECT COALESCE(MAX(column_id), 0) + 1 FROM gen_table_column), false); ``` --- ## 8. Verify(复核验证) 执行只读复核脚本 `003_generator_identity_verify.sql`,执行四大维度的综合核验: 1. **核验列属性**:确认两列 `attidentity = 'd'`; 2. **核验 Sequence 存在性**:确认关联序列存在且 `relkind = 'S'`; 3. **核验依赖关系**:确认 `sys_depend.deptype = 'i'`(内部生命周期绑定); 4. **核验 Sequence 对齐安全性**:确认两表的 Sequence 处于 `SAFE` 或 `EMPTY_TABLE_SAFE` 状态; 5. **最终判定**:四项维度全通过输出 `VERIFY: PASS`。 --- ## 9. Rollback 风险说明 若因特殊场景需要撤销该修改,语法为: ```sql ALTER TABLE gen_table ALTER COLUMN table_id DROP IDENTITY IF EXISTS; ALTER TABLE gen_table_column ALTER COLUMN column_id DROP IDENTITY IF EXISTS; ``` ### 回滚风险评估 1. **Generator 瘫痪**:一旦移除 Identity,Generator 将立即恢复至无法导入表的故障状态; 2. **元数据序列断裂**:若已有元数据记录,回滚后再配置可能造成序列重新错位; 3. **结论**:该补丁作为 RuoYi 适配 Kingbase 必需的基础设施配置,**严禁回滚**;任何环境初始化后均应持久化保留此配置。 --- ## 10. 新环境部署要求 在搭建全新 Kingbase 部署环境或自动化 CI/CD 环境时: 1. 执行若依基础建表脚本; 2. 执行预检: ```bash ksql -h -p -U -d -f RuoYi-Vue/sql/generator/kingbase/001_generator_identity_precheck.sql ``` 3. 按照决策矩阵执行对应补丁(空表全新环境直接依次执行 `002a` 和 `002b`); 4. 执行复核验证并确认输出 `VERIFY: PASS`: ```bash ksql -h -p -U -d -f RuoYi-Vue/sql/generator/kingbase/003_generator_identity_verify.sql ``` --- ## 11. 与 Database V2 无关性声明 - **范围隔离**:本修复纯属 Generator 工具层基础设施兼容补丁,仅修改 `public.gen_table` 与 `public.gen_table_column` 两张系统级代码生成支撑表的主键属性; - **业务无关**:零改动 21 张 `train_*` 业务模型表;零改动任何业务枚举、外键约束与数据字典; - **版本独立**:补丁存放于 `RuoYi-Vue/sql/generator/kingbase/`,独立于业务迁移目录 `RuoYi-Vue/sql/train/v2/`。 --- ## 12. Teacher Pilot 验证事实与当前共享库核验 - 在 Teacher Pilot(`feat/issue-01-01-teacher`)开发期间,共享 Kingbase 数据库已应用该 DDL 修复; - 真实导入测试:调用 Generator 导入 `train_teacher` 表,`gen_table` 成功生成非零递增主键(`table_id = 1`),同时其 16 个字段成功写入 `gen_table_column`(`column_id = 1..16`); - 代码生成测试:成功基于导入元数据生成教师主数据相关 Controller、Service、Mapper、Domain 及前端 Vue 页面,未产生任何主键冲突; - **当前共享库只读核验数据(实测)**: - `gen_table`: `row_count = 1`, `max_id = 1`, `sequence last_value = 2`, `is_called = true`,下一个分配值为 3 > 1,状态为 `SAFE`; - `gen_table_column`: `row_count = 16`, `max_id = 16`, `sequence last_value = 32`, `is_called = true`,下一个分配值为 33 > 16,状态为 `SAFE`; - 属性 `attidentity = 'd'`,序列依赖均为 `deptype = 'i'`; - 复核脚本输出:`VERIFY: PASS (所有 Identity、Sequence、依赖关系与序列对齐安全核验一致通过)`。