You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot中如何将Postgres关联视图结果映射为嵌套Document实体

扁平化视图结果转嵌套实体的实现方案

针对多表左连接产生的扁平化结果,以下提供两种可直接落地的实现方案,无需创建多个分表视图:

通用手动映射方案

不依赖ORM框架,适配所有Java场景,核心逻辑是按各层级主键ID做去重缓存,逐层组装嵌套结构:

步骤1:定义扁平化结果接收DTO

先创建和视图返回列一一对应的DTO类,用于承接JDBC/ORM框架返回的每行数据:

@Data
public class DocumentFlatDTO {
    private String documentId;
    private String filename;
    private String fieldId;
    private String type;
    private String fieldValueId;
    private String value;
    private String coordinateId;
    private Integer startX;
    private Integer startY;
    private Integer endX;
    private Integer endY;
}

步骤2:逐层组装嵌套实体

通过多层Map做临时缓存,遍历扁平化列表完成组装:

public List<Document> convertFlatListToDocument(List<DocumentFlatDTO> flatList) {
    // 第一层缓存:key=documentId,value=已组装的Document对象
    Map<String, Document> docCache = new HashMap<>();
    // 第二层缓存:key=documentId:fieldId,value=已组装的Field对象
    Map<String, Field> fieldCache = new HashMap<>();
    // 第三层缓存:key=documentId:fieldId:fieldValueId,value=已组装的FieldValue对象
    Map<String, FieldValue> valueCache = new HashMap<>();

    for (DocumentFlatDTO item : flatList) {
        String docId = item.getDocumentId();
        // 1. 组装Document层
        Document doc = docCache.get(docId);
        if (doc == null) {
            doc = new Document();
            doc.setId(docId);
            doc.setFilename(item.getFilename());
            doc.setFields(new ArrayList<>());
            docCache.put(docId, doc);
        }
        // 左连接无关联字段时跳过后续层级
        String fieldId = item.getFieldId();
        if (fieldId == null) continue;

        // 2. 组装Field层
        String fieldKey = docId + ":" + fieldId;
        Field field = fieldCache.get(fieldKey);
        if (field == null) {
            field = new Field();
            field.setId(fieldId);
            field.setType(item.getType());
            field.setValues(new ArrayList<>());
            doc.getFields().add(field);
            fieldCache.put(fieldKey, field);
        }
        // 左连接无关联字段值时跳过后续层级
        String valueId = item.getFieldValueId();
        if (valueId == null) continue;

        // 3. 组装FieldValue层
        String valueKey = fieldKey + ":" + valueId;
        FieldValue fieldValue = valueCache.get(valueKey);
        if (fieldValue == null) {
            fieldValue = new FieldValue();
            fieldValue.setId(valueId);
            fieldValue.setValue(item.getValue());
            fieldValue.setCoordinates(new ArrayList<>());
            field.getValues().add(fieldValue);
            valueCache.put(valueKey, fieldValue);
        }
        // 左连接无关联坐标时跳过
        String coordinateId = item.getCoordinateId();
        if (coordinateId == null) continue;

        // 4. 组装Coordinate层
        Coordinate coordinate = new Coordinate();
        coordinate.setId(coordinateId);
        coordinate.setStart_x(item.getStartX());
        coordinate.setStart_y(item.getStartY());
        coordinate.setEnd_x(item.getEndX());
        coordinate.setEnd_y(item.getEndY());
        fieldValue.getCoordinates().add(coordinate);
    }
    return new ArrayList<>(docCache.values());
}

MyBatis 自动映射方案

如果项目使用MyBatis,可直接通过resultMap的嵌套collection+主键ID标记实现自动组装,无需手动编写转换逻辑:

<resultMap id="DocumentNestedResultMap" type="com.yourpackage.Document">
    <!-- Document层主键,用于去重 -->
    <id property="id" column="document_id"/>
    <result property="filename" column="filename"/>
    <!-- 嵌套Field集合 -->
    <collection property="fields" ofType="com.yourpackage.Field">
        <!-- Field层主键,用于去重 -->
        <id property="id" column="field_id"/>
        <result property="type" column="type"/>
        <!-- 嵌套FieldValue集合 -->
        <collection property="values" ofType="com.yourpackage.FieldValue">
            <!-- FieldValue层主键,用于去重 -->
            <id property="id" column="field_value_id"/>
            <result property="value" column="value"/>
            <!-- 嵌套Coordinate集合 -->
            <collection property="coordinates" ofType="com.yourpackage.Coordinate">
                <!-- Coordinate层主键,用于去重 -->
                <id property="id" column="coordinate_id"/>
                <result property="start_x" column="start_x"/>
                <result property="start_y" column="start_y"/>
                <result property="end_x" column="end_x"/>
                <result property="end_y" column="end_y"/>
            </collection>
        </collection>
    </collection>
</resultMap>

<!-- 查询语句直接关联视图,使用上述resultMap即可返回嵌套结构 -->
<select id="listDocument" resultMap="DocumentNestedResultMap">
    SELECT * FROM your_view ORDER BY document_id, field_id, field_value_id, coordinate_id
</select>

注意事项

  1. 查询视图时建议按document_id > field_id > field_value_id > coordinate_id的顺序排序,可大幅提升组装效率,避免频繁查找缓存。
  2. 左连接会产生大量空关联字段,代码中需要加空值判断,避免空指针异常。
  3. 如果关联层级更多,只需按上述逻辑扩展缓存层级或者MyBatis的collection嵌套层级即可,无需修改整体方案。

内容的提问来源于stack exchange,提问作者sai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.12 10:27:04