修复代码生成:补全Kingbase导入元数据Identity
This commit is contained in:
1 parent
7fcaea29b8
commit
863d3c7c39
6 files changed
+562
No files matched your search
@@ -0,0 +1,202 @@
|
||||
# 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
|
||||
<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` 中,原始表结构为:
|
||||
```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 <HOST> -p <PORT> -U <USER> -d <DBNAME> -f RuoYi-Vue/sql/generator/kingbase/001_generator_identity_precheck.sql
|
||||
```
|
||||
3. 按照决策矩阵执行对应补丁(空表全新环境直接依次执行 `002a` 和 `002b`);
|
||||
4. 执行复核验证并确认输出 `VERIFY: PASS`:
|
||||
```bash
|
||||
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、依赖关系与序列对齐安全核验一致通过)`。
|
||||
Reference in new issue
Block a user