Appearance
10 · 元数据驱动的动态写入/读取(persist-meta 规范)
版本:1.8.0(起,1.8.0 同步演进) 定位:字段元数据(storageType)驱动的执行规范——复杂字段插入(对象/JSON/子表) 与流程回显读取成为通用能力,不再绑定任何框架的低代码设施。 与
DynamicTableWriter(spec 09)的关系:演进而非推翻——现有 SPI 保留,无元数据回落现状。
1. 目标与边界
做(最小闭环)
- 字段元数据(storageType)驱动的动态写入(NORMAL/EXPAND/JSON/ONE2ONE/ONE2MANY)
- 按流程实例的动态读取回显(
readByProcessInstance,与写入共用同一份元数据) - 元数据来源可插拔(SPI),内置 JSON 配置加载器(零依赖)
不做(明确边界)
通用条件分页 / 动态条件语法(m_EQ_xxx)/ 数据权限 / 排序——集成方查询体系(如 mldong tablePage) 的领域,双方共享字段元数据规范保持语义一致。
2. 元数据模型(四语言契约)
2.1 JSON 配置字段语义(persist-meta/biz_leave.json,文件名=表名)
顶层:
| 字段 | 类型 | 缺省 | 语义 |
|---|---|---|---|
tableName | string | 必填 | 业务表名(应与此 JSON 文件名一致) |
primaryKey | string | id | 主键列名(ONE2ONE/ONE2MANY 子表外键缺省回落它) |
fields | array | 必填 | 字段定义列表(见下) |
fields[] 元素:
| 字段 | 类型 | 缺省 | 语义 |
|---|---|---|---|
name | string | 必填 | 表单字段名(实例 Variables 中 f_ 去前缀后的名字,如 f_title → title) |
columnName | string | name 转下划线 | 主表列名(如 companyName → company_name) |
storageType | string | number | NORMAL | 存储类型——名称或数字双解析(见下表) |
expandFields | object | 无 | EXPAND 专用:子字段名 → 表列名映射({ "province": "province", "detail": "detail_addr" }) |
targetTable | string | 无 | ONE2ONE/ONE2MANY 专用:子表表名 |
foreignKey | string | 主表 primaryKey 列名 | ONE2ONE/ONE2MANY 专用:子表外键列 |
storageType 取值(对齐 mldong dev_schema_field 1-5,名称/数字双解析):
| 值 | 名称 | 语义 |
|---|---|---|
| 1 | NORMAL | 直写列(默认) |
| 2 | EXPAND | 对象展开为多列(expandFields 定义子字段列映射) |
| 3 | JSON | 对象/数组序列化为 JSON 串写列 |
| 4 | ONE2ONE | 子表单条递归插入(外键 = 主表主键,同事务) |
| 5 | ONE2MANY | 子表多条递归插入(data 值为数组,同事务) |
JSON 配置示例(persist-meta/biz_leave.json):
json
{
"tableName": "biz_leave",
"primaryKey": "id",
"fields": [
{ "name": "companyName", "columnName": "company_name", "storageType": "NORMAL" },
{ "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" }
]
}3. SPI(集成方只实现一件事:提供元数据)
java
public interface IDynamicMetaProvider {
/** 加载表元数据;未定义返回 null(回落表结构探测,全 NORMAL 语义) */
TableMeta loadTableMeta(String tableName);
}- 内置
JsonMetaProvider:从文件系统/classpath 加载persist-meta/*.json - 写、读共用:写入引擎与回显读取都按同一份元数据执行,storageType 语义两侧一致
- 默认回落:未配元数据时行为与 1.6.x 完全一致(零破坏)
4. 组件分工(读写职责分离)
写侧(DynamicTableWriter 接口不变)
├── JdbcDynamicTableWriter 现状:表结构探测,全 NORMAL
└── MetaTableWriter 新:元数据驱动(NORMAL/JSON/EXPAND/子表递归)
读侧(流程回显最小闭环)
JdbcTableReader 底层行查询(按列等值,limit)
MetaTableReader readByProcessInstance:storageType 反序列化 + 子表组装5. 写入语义(MetaTableWriter)
| storageType | 写入 |
|---|---|
| NORMAL | 直写(列名宽松匹配沿用 spec 09) |
| EXPAND | 对象字段展开为多列(address.province → province 列) |
| JSON | 对象/数组序列化为 JSON 串写列 |
| ONE2ONE | 子表单条递归插入(外键 = 主表主键),同事务 |
| ONE2MANY | 子表多条递归插入(data 值为数组),同事务 |
复用现有能力:主键生成(含非自增雪花,生成器返回主键供子表外键)/ 系统字段 / 同链防重 / schema 限定探测。未消费字段(流程上下文 process_instance_id 等)直通。
中途更新(update,1.8.0):按元数据 storageType 组装 SET 列——NORMAL/JSON/EXPAND 参与更新;ONE2ONE/ONE2MANY 子表不参与中途更新(SYNC 任务推进只更新主表行状态, 子表数据变动走重新提交);未消费字段直通。无元数据回落基础 writer。
子表系统用户字段(1.8.0):子表递归插入时继承主表 apply_user_id (拦截器场景 = 流程 operator,putIfAbsent 子表单显式同名字段优先)——fillSystemFields 的用户列默认值可解析到 operator,避免 BIGINT create_user/update_user 列回落 "system" 严格模式报错(与主表行为一致)。
6. 读取语义(MetaTableReader)
java
// 按 relTableName + process_instance_id 回显一条(无分页/无条件)
Map<String, Object> readByProcessInstance(String tableName, Object processInstanceId);| storageType | 读取 |
|---|---|
| NORMAL | 直读列 |
| EXPAND | 反展开为对象(province/city/detail_addr → address.province/...) |
| JSON | 反序列化为对象/数组 |
| ONE2ONE | 按外键=主表主键查子表单条,组装为对象 |
| ONE2MANY | 按外键=主表主键查子表多条,组装为数组 |
- 定位键
process_instance_id(写入幂等键同款) - 未消费列(流程上下文/系统字段)统一小写带出(跨方言一致)
- EXPAND 展开列不重复平铺带出(1.8.0):
province/city/detail_addr已消费为address对象,不再作为顶层平铺键重复出现 - 无元数据回落原始行(列名→值)
7. 版本与发布
| 语言 | 发布形态 | 版本 |
|---|---|---|
| Java | jeeflow-persist 子模块(Maven Central) | 1.8.0 |
| Go | persist/ 子包(go get 同 module) | 1.8.0 |
| Python | jeeflow.meta 模块(PyPI 随主包) | 1.8.0 |
| Node | src/meta.ts(npm 随主包) | 1.8.0 |