Appearance
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缺省 = 字段名转下划线(companyName→company_name)- 子表字段(ONE2ONE/ONE2MANY)需
targetTable;foreignKey缺省 = 主表主键列名 - 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_leave | company_name / province / city / detail_addr(EXPAND 展开)/ extra(JSON 串)/ 流程上下文 + 系统字段 |
biz_leave_item | 2 条明细(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. 注意事项(踩坑清单)
- 主键:子表外键 = 主表主键——非自增表(雪花)必须配
setPrimaryKeyGenerator,生成器返回的主键自动注入子表外键;主表主键缺失时子表插入报错 - EXPAND 列:展开列必须在主表存在(元数据与 DDL 对齐),多传的子字段自动忽略;回显时展开列不重复平铺带出(1.8.0,对象形式已消费)
- 子表元数据:ONE2ONE/ONE2MANY 的子表也要有自己的 JSON 配置(没有则按 NORMAL 直写+原始行回显)
- JSON 列:业务表 JSON 列建议 VARCHAR/TEXT 存串(H2 原生 JSON 类型 JDBC 读取为字节数组,见 spec 09 踩坑)
- 子表系统用户字段(1.8.0):子表递归插入继承主表
apply_user_id(= 流程 operator)——子表create_user/update_user为 BIGINT 时不会回落 "system" 导致严格模式报错 - 中途更新(1.8.0):SYNC 模式下任务推进走
update——NORMAL/JSON/EXPAND 参与更新,ONE2ONE/ONE2MANY 子表不参与中途更新(子表数据变动走重新提交) - 边界:通用分页/条件/权限不在本组件——集成方查询体系按同一份元数据规范扩展
- 回落:未配元数据的表行为与 1.6.x 完全一致(零破坏)
4. 验证
sql
-- 跑完一条含明细的审批流(同意)后:
SELECT * FROM biz_leave; -- EXPAND 列展开、extra 为 JSON 串、仅 1 条
SELECT * FROM biz_leave_item; -- 明细多条,leave_id = 主表主键
-- 流程详情接口返回:address 对象 / extra 对象 / items 数组四语言合规测试见 规范 10 · 元数据驱动的动态写入/读取。