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>
注意事项
- 查询视图时建议按
document_id > field_id > field_value_id > coordinate_id的顺序排序,可大幅提升组装效率,避免频繁查找缓存。 - 左连接会产生大量空关联字段,代码中需要加空值判断,避免空指针异常。
- 如果关联层级更多,只需按上述逻辑扩展缓存层级或者MyBatis的collection嵌套层级即可,无需修改整体方案。
内容的提问来源于stack exchange,提问作者sai
相关产品推荐
相关产品推荐

