Files
railwaysystem/docs/generator-v2/01_kingbase_import_identity_patch.md
T

10 KiB
Raw Blame History

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. 错误现象

后端日志与数据库抛出类似如下的主键冲突与唯一约束冲突异常:

### 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 中的插入语句均配置了:

    <insert id="insertGenTable" parameterType="GenTable" useGeneratedKeys="true" keyProperty="tableId">
    <insert id="insertGenTableColumn" parameterType="GenTableColumn" useGeneratedKeys="true" keyProperty="columnId">
    

    RuoYi 的应用层期望数据库能够在插入记录时自动生成唯一递增主键,并通过 JDBC 驱动的 getGeneratedKeys 返回该主键值并回填至 Java 实体对象的 tableId / columnId。

  2. 原始 MySQL 建表定义: 在 RuoYi-Vue/sql/ry_20250522.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

ALTER TABLE gen_table
ALTER COLUMN table_id
ADD GENERATED BY DEFAULT AS IDENTITY;

5.2 002b_gen_table_column_identity_patch.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 执行专项对齐脚本:
    -- 同步 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 风险说明

若因特殊场景需要撤销该修改,语法为:

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. 执行预检:
    ksql -h <HOST> -p <PORT> -U <USER> -d <DBNAME> -f RuoYi-Vue/sql/generator/kingbase/001_generator_identity_precheck.sql
    
  3. 按照决策矩阵执行对应补丁(空表全新环境直接依次执行 002a 和 002b);
  4. 执行复核验证并确认输出 VERIFY: PASS:
    ksql -h <HOST> -p <PORT> -U <USER> -d <DBNAME> -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、依赖关系与序列对齐安全核验一致通过)。