如何在JPA原生查询中获取含列表属性的对象集合
JPA原生查询返回带列表属性的对象解决方案
由于JPA原生查询返回的是扁平化结果集,无法直接通过接口投影自动映射带集合属性的对象,以下提供两种可行的实现方案:
方案一:Repository查询原始数据,服务层分组转换
步骤1:定义中间投影接口
用于接收关联查询的扁平化结果:
public interface ExampleChildProjection { Integer getExampleId(); String getExampleLabel(); Integer getChildId(); String getChildCode(); String getChildLabel(); }
步骤2:编写Repository原生查询方法
关联两张表查询所有需要的字段:
@Query(nativeQuery = true, value = """ SELECT e.id AS example_id, e.label AS example_label, c.id AS child_id, c.code AS child_code, c.label AS child_label FROM example e LEFT JOIN child c ON e.id = c.example_id ORDER BY e.id """) List<ExampleChildProjection> searchRaw();
步骤3:服务层分组转换为目标接口
将扁平化结果按Example ID分组,组装成带Child列表的ExampleInterface实例:
public List<ExampleInterface> search() { List<ExampleChildProjection> rawResults = exampleRepository.searchRaw(); Map<Integer, ExampleInterface> exampleMap = new HashMap<>(); for (ExampleChildProjection projection : rawResults) { Integer exampleId = projection.getExampleId(); // 不存在则创建新的ExampleInterface实现 ExampleInterface example = exampleMap.computeIfAbsent(exampleId, id -> new ExampleInterface() { private final Integer idVal = id; private final String labelVal = projection.getExampleLabel(); private final List<ChildInterface> children = new ArrayList<>(); @Override public Integer getId() { return idVal; } @Override public String getLabel() { return labelVal; } @Override public List<ChildInterface> getChildren() { return children; } }); // 若存在Child数据,添加到对应Example的子列表中 if (projection.getChildId() != null) { ChildInterface child = new ChildInterface() { @Override public Integer getId() { return projection.getChildId(); } @Override public String getCode() { return projection.getChildCode(); } @Override public String getLabel() { return projection.getChildLabel(); } }; example.getChildren().add(child); } } return new ArrayList<>(exampleMap.values()); }
方案二:使用Hibernate ResultTransformer直接转换
该方案依赖Hibernate API,可在Repository层完成结果转换:
import org.hibernate.transform.ResultTransformer; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; // 在Repository类中注入EntityManager @PersistenceContext private EntityManager entityManager; public List<ExampleInterface> search() { String sql = """ SELECT e.id AS example_id, e.label AS example_label, c.id AS child_id, c.code AS child_code, c.label AS child_label FROM example e LEFT JOIN child c ON e.id = c.example_id ORDER BY e.id """; return entityManager.createNativeQuery(sql) .unwrap(org.hibernate.query.Query.class) .setResultTransformer(new ResultTransformer() { private Map<Integer, ExampleInterface> exampleMap = new HashMap<>(); @Override public Object transformTuple(Object[] tuple, String[] aliases) { Integer exampleId = (Integer) tuple[0]; String exampleLabel = (String) tuple[1]; Integer childId = (Integer) tuple[2]; String childCode = (String) tuple[3]; String childLabel = (String) tuple[4]; ExampleInterface example = exampleMap.computeIfAbsent(exampleId, id -> new ExampleInterface() { private final Integer idVal = id; private final String labelVal = exampleLabel; private final List<ChildInterface> children = new ArrayList<>(); @Override public Integer getId() { return idVal; } @Override public String getLabel() { return labelVal; } @Override public List<ChildInterface> getChildren() { return children; } }); if (childId != null) { ChildInterface child = new ChildInterface() { @Override public Integer getId() { return childId; } @Override public String getCode() { return childCode; } @Override public String getLabel() { return childLabel; } }; example.getChildren().add(child); } return example; } @Override public List<?> transformList(List collection) { return new ArrayList<>(exampleMap.values()); } }) .getResultList(); }
关键注意事项
- 必须添加
ORDER BY e.id,确保同一Example的所有Child数据连续返回,避免分组时遗漏数据。 - 方案一基于JPA规范实现,兼容性更好;方案二依赖Hibernate特定API,代码更紧凑。
内容的提问来源于stack exchange,提问作者Pro Grammer
相关产品推荐
相关产品推荐

