表关系
表关系
业务用途
表关系用于定义业务表之间的关联关系(一对一、一对多、多对多)。配置表关系后,表单设计器和列表设计器可以利用关系实现:
- 下拉关联选择:字段编辑时从关联主表加载选项(如选择供应商时下拉显示供应商名称)
- 显示字段替代:列表中显示关联表的名称而非 ID(如
supplier_id列显示supplier_name) - 数据联查:为后续的数据联表查询提供元数据支撑
表关系配置存入 lc_table_relation 表,通过 TableRelationController 管理。
涉及文件
| 层 | 文件路径 | 说明 |
|---|---|---|
| Controller | backend/src/main/java/com/lowcode/controller/TableRelationController.java | /api/table-relation 端点 |
| Controller | backend/src/main/java/com/lowcode/controller/DatabaseController.java | 字段选项查询中用到表关系(findRelationOption) |
| Entity | backend/src/main/java/com/lowcode/entity/LcTableRelation.java | 表关系实体(@TableName("lc_table_relation")) |
| Service | backend/src/main/java/com/lowcode/service/TableRelationService.java | 表关系 CRUD |
| Service | backend/src/main/java/com/lowcode/service/SchemaService.java | queryRelationOptions() 查询关联选项 |
| 前端 | frontend/src/views/database/relation.vue | 表关系管理界面 |
| 前端API | frontend/src/api/tableRelation.ts | 前端请求封装 |
数据库表
lc_table_relation(表关系表)
CREATE TABLE `lc_table_relation` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` BIGINT DEFAULT 1 COMMENT '租户编号',
`app_id` BIGINT COMMENT '所属应用ID',
`relation_name` VARCHAR(100) COMMENT '关系名称',
`source_table_id` BIGINT NOT NULL COMMENT '源表ID',
`source_column` VARCHAR(100) NOT NULL COMMENT '源表字段',
`target_table_id` BIGINT NOT NULL COMMENT '目标表ID',
`target_column` VARCHAR(100) NOT NULL COMMENT '目标表字段',
`relation_type` VARCHAR(20) NOT NULL COMMENT '关系类型(one_to_one/one_to_many/many_to_many)',
`join_table` VARCHAR(100) COMMENT '关联表(多对多时使用)',
`display_column` VARCHAR(100) COMMENT '主表显示字段',
`enabled` TINYINT DEFAULT 1 COMMENT '是否启用',
`create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`create_by` VARCHAR(50) COMMENT '创建者',
`update_by` VARCHAR(50) COMMENT '更新者',
`deleted` TINYINT DEFAULT 0 COMMENT '是否删除',
PRIMARY KEY (`id`),
KEY `idx_relation_app` (`tenant_id`, `app_id`),
KEY `idx_relation_source` (`source_table_id`),
KEY `idx_relation_target` (`target_table_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='表关系表';LcTableRelation 实体字段
@TableName("lc_table_relation")
public class LcTableRelation extends BaseEntity {
private Long appId; // 所属应用ID
private String relationName; // 关系名称
private Long sourceTableId; // 源表ID(从表)
private String sourceColumn; // 源表字段(外键列,如 supplier_id)
private Long targetTableId; // 目标表ID(主表)
private String targetColumn; // 目标表字段(被引用列,如 id)
private String relationType; // 关系类型: one_to_one / one_to_many / many_to_many
private String joinTable; // 关联表(多对多时的中间表)
private String displayColumn; // 主表显示字段(如 supplier_name)
private Integer enabled; // 是否启用(0 启用,1 禁用)
}源表与目标表
- 源表(source) = 从表 = 有外键的表(如
erp_purchase_order.supplier_id) - 目标表(target) = 主表 = 被引用的表(如
erp_supplier.id) - displayColumn = 主表中用于显示的字段(如
supplier_name),列表中用此字段替代外键 ID 显示
后端实现
端点列表(TableRelationController -- /api/table-relation)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/table-relation/list?tableId=&appId= | 获取表关系列表(可按表ID或应用ID筛选) |
| GET | /api/table-relation/{id} | 获取表关系详情 |
| POST | /api/table-relation | 创建表关系 |
| PUT | /api/table-relation | 更新表关系 |
| DELETE | /api/table-relation/{id} | 删除表关系 |
字段选项查询中的关系应用
DatabaseController.getColumnOptions() 方法在获取字段下拉选项时,会检查表关系配置:
// DatabaseController.java -- getColumnOptions() 中的关系查找
private LcTableRelation findRelationOption(Long tableId, String columnName) {
List<LcTableRelation> relations = tableRelationService.listByTableId(tableId);
for (LcTableRelation relation : relations) {
if (relation.getEnabled() != null && relation.getEnabled() == 0) continue; // 跳过禁用的
// 当前表是目标表(主表),且字段是目标字段时,查找源表(从表)的选项
if (tableId.equals(relation.getTargetTableId())
&& columnName.equals(relation.getTargetColumn())) {
return relation;
}
}
return null;
}找到关系后,调用 SchemaService.queryRelationOptions() 从源表(主表)查询选项数据:
// SchemaService.java
public List<Map<String, String>> queryRelationOptions(Long tableId, String valueColumn, String labelColumn) {
LcTableMeta tableMeta = getTableDetail(tableId);
if (!hasColumn(tableMeta, valueColumn)) throw new RuntimeException("关联主表字段不存在");
// 显示字段优先用 labelColumn,如果不存在则用 valueColumn
String displayColumn = StrUtil.isNotBlank(labelColumn) && hasColumn(tableMeta, labelColumn)
? labelColumn : valueColumn;
// 构建查询:SELECT displayColumn AS label, valueColumn AS value FROM table
StringBuilder sql = new StringBuilder("SELECT ")
.append(safeColumnName(displayColumn)).append(" AS label, ")
.append(safeColumnName(valueColumn)).append(" AS value FROM `")
.append(tableMeta.getTableName()).append("`");
// 自动追加 deleted=0 和 tenant_id 过滤
if (hasColumn(tableMeta, "deleted")) sql.append(" WHERE deleted = 0");
if (hasColumn(tableMeta, "tenant_id")) sql.append(" AND tenant_id = ?");
sql.append(" ORDER BY id DESC LIMIT 500");
// ... 执行查询并返回 label/value 键值对列表
}前端实现
表关系管理界面
frontend/src/views/database/relation.vue 提供:
- 关系列表(可按表或应用筛选)
- 新建关系:选择源表、源字段、目标表、目标字段、关系类型、显示字段
- 编辑/删除关系

前端 API 封装
// frontend/src/api/tableRelation.ts
export function getTableRelationList(params?: { tableId?: number, appId?: number } | number) {
const query = typeof params === 'number' ? { tableId: params } : (params || {})
return request.get<any>('/table-relation/list', { params: query })
}
export function getTableRelationDetail(id: number) {
return request.get<any>(`/table-relation/${id}`)
}
export function createTableRelation(data: Partial<TableRelationVO>) {
return request.post<any>('/table-relation', data)
}
export function updateTableRelation(data: Partial<TableRelationVO>) {
return request.put<any>('/table-relation', data)
}
export function deleteTableRelation(id: number) {
return request.delete<any>(`/table-relation/${id}`)
}运行时列表中的关系选项加载
RuntimeList.vue 在渲染关联字段时,会调用字段选项接口:
// RuntimeList.vue 第 881 行
const res = await request.get(
`/database/table/${props.config.tableId}/column/${col.columnName}/options`
)操作步骤
- 进入「数据库管理 -> 表关系」页面
- 点击「新建关系」
- 填写关系名称(如「采购订单-供应商」)
- 选择源表(从表,如「采购订单」)和源字段(外键列,如
supplier_id) - 选择目标表(主表,如「供应商」)和目标字段(被引用列,如
id) - 选择关系类型:一对一 / 一对多 / 多对多
- 设置显示字段(如
supplier_name),用于列表中替代 ID 显示 - 点击「保存」
- 在表单设计器或列表设计器中,该字段会自动加载关联选项

常见问题
关系配置后下拉选项为空
queryRelationOptions() 从源表(sourceTableId,即主表)查询数据。如果主表没有数据,或 displayColumn 配置错误,选项会为空。请检查主表是否有数据,以及显示字段名是否正确。
enabled 字段的值含义
LcWebhook 和 LcTableRelation 的 enabled / status 字段含义不同:LcTableRelation.enabled 中 0 表示启用、1 表示禁用(见 findRelationOption 中的 relation.getEnabled() == 0 判断)。配置时请注意这个反转逻辑。
