Skip to content

09 · 元数据驱动入库(复杂表单落库与回显)

版本:1.8.0(起,1.8.0 同步演进:子表用户继承 + EXPAND 去冗余 + 中途更新) 场景:表单含对象 / JSON / 子表字段(如地址对象、明细列表、附加 JSON), 需要流程结束落库 + 流程详情回显——不再依赖任何框架的低代码设施,JSON 配置即用。

1. 适用场景

场景说明
复杂表单落库对象字段展开为多列(EXPAND)、JSON 字段序列化(JSON)、子表明细(ONE2ONE/ONE2MANY)
流程详情回显按流程实例 ID 读回完整业务数据(对象/JSON/子表自动组装)
元数据驱动集成方只配 JSON(或实现 SPI),不再写插入/反序列化逻辑

对照:简单表(全 NORMAL 字段)用 spec 09 的默认 writer 即可;本规范覆盖复杂字段场景,两者可共存(无元数据回落)。

2. 三步接入

① 编写元数据 JSON

persist-meta/biz_leave.json(文件系统或 classpath,文件名=表名):

json
{
  "tableName": "biz_leave",
  "primaryKey": "id",
  "fields": [
    { "name": "companyName", "columnName": "company_name" },
    { "name": "address", "storageType": "EXPAND",
      "expandFields": { "province": "province", "city": "city", "detail": "detail_addr" } },
    { "name": "extra", "storageType": "JSON" },
    { "name": "items", "storageType": "ONE2MANY",
      "targetTable": "biz_leave_item", "foreignKey": "leave_id" }
  ]
}

规则:

  • columnName 缺省 = 字段名转下划线(companyNamecompany_name
  • 子表字段(ONE2ONE/ONE2MANY)需 targetTableforeignKey 缺省 = 主表主键列名
  • storageType 支持名称或数字(mldong 1-5 语义)

② 装配(替换默认 writer / 增加 reader)

java
// Java——注册写侧(替换默认 writer,拦截器仍模型级挂载)
JdbcDynamicTableWriter base = new JdbcDynamicTableWriter(dataSource);
base.setPrimaryKeyGenerator(IdWorker::getId);            // 雪花主键(非自增表)
IDynamicMetaProvider provider = new JsonMetaProvider();  // classpath persist-meta/
ServiceContext.put("dynamicTableWriter", new MetaTableWriter(base, provider));

// 读侧(流程详情接口用)
MetaTableReader reader = new MetaTableReader(new JdbcTableReader(dataSource), provider);
go
// Go
writer := persist.NewMetaTableWriter(persist.NewJdbcDynamicTableWriter(db), persist.NewJsonMetaProvider("persist-meta"))
reader := persist.NewMetaTableReader(persist.NewJdbcTableReader(db), persist.NewJsonMetaProvider("persist-meta"))
python
# Python
from jeeflow.meta import MetaTableWriter, MetaTableReader, JdbcTableReader, JsonMetaProvider
writer = MetaTableWriter(JdbcDynamicTableWriter(conn), JsonMetaProvider("persist-meta"))
reader = MetaTableReader(JdbcTableReader(conn), JsonMetaProvider("persist-meta"))
ts
// Node
import { MetaTableWriter, MetaTableReader, JdbcTableReader, JsonMetaProvider } from '@mldong/jeeflow'
const writer = new MetaTableWriter(new SqliteDynamicTableWriter(db), new JsonMetaProvider('persist-meta'))
const reader = new MetaTableReader(new JdbcTableReader(db), new JsonMetaProvider('persist-meta'))

③ 发起/回显(流程定义配置不变)

流程定义仍配 relTableName + postInterceptors(Java),发起时提交表单变量:

f_companyName = "测试公司"
f_address     = { "province": "广东省", "city": "深圳市", "detail": "科技园路1号" }
f_extra       = { "tag": "vip", "level": 3 }
f_items       = [ { "name": "电脑", "qty": 2 }, { "name": "键盘", "qty": 3 } ]

流程结束同意后落库:

内容
biz_leavecompany_name / province / city / detail_addr(EXPAND 展开)/ extra(JSON 串)/ 流程上下文 + 系统字段
biz_leave_item2 条明细(leave_id = 主表主键)

回显(流程详情接口):

java
Map<String, Object> form = reader.readByProcessInstance("biz_leave", processInstanceId);
// form.address = { province, city, detail }(EXPAND 反展开)
// form.extra   = { tag, level }(JSON 反序列化)
// form.items   = [ { name, qty }, ... ](ONE2MANY 子表组装)
// form.process_instance_id / apply_user_id ... 原样带出

3. 注意事项(踩坑清单)

  1. 主键:子表外键 = 主表主键——非自增表(雪花)必须配 setPrimaryKeyGenerator,生成器返回的主键自动注入子表外键;主表主键缺失时子表插入报错
  2. EXPAND 列:展开列必须在主表存在(元数据与 DDL 对齐),多传的子字段自动忽略;回显时展开列不重复平铺带出(1.8.0,对象形式已消费)
  3. 子表元数据:ONE2ONE/ONE2MANY 的子表也要有自己的 JSON 配置(没有则按 NORMAL 直写+原始行回显)
  4. JSON 列:业务表 JSON 列建议 VARCHAR/TEXT 存串(H2 原生 JSON 类型 JDBC 读取为字节数组,见 spec 09 踩坑)
  5. 子表系统用户字段(1.8.0):子表递归插入继承主表 apply_user_id(= 流程 operator)——子表 create_user/update_user 为 BIGINT 时不会回落 "system" 导致严格模式报错
  6. 中途更新(1.8.0):SYNC 模式下任务推进走 update——NORMAL/JSON/EXPAND 参与更新,ONE2ONE/ONE2MANY 子表不参与中途更新(子表数据变动走重新提交)
  7. 边界:通用分页/条件/权限不在本组件——集成方查询体系按同一份元数据规范扩展
  8. 回落:未配元数据的表行为与 1.6.x 完全一致(零破坏)

4. 验证

sql
-- 跑完一条含明细的审批流(同意)后:
SELECT * FROM biz_leave;          -- EXPAND 列展开、extra 为 JSON 串、仅 1 条
SELECT * FROM biz_leave_item;     -- 明细多条,leave_id = 主表主键
-- 流程详情接口返回:address 对象 / extra 对象 / items 数组

四语言合规测试见 规范 10 · 元数据驱动的动态写入/读取

jeeflow · 轻量级多语言工作流引擎