Spring Data JPA/Hibernate:关联查询直接映射嵌套DTO的方法
可以直接将关联查询结果映射到嵌套DTO吗?
完全可以,不需要手动编写Stream转换逻辑,Spring Data JPA和Hibernate提供了多种直接映射的方案,以下是最实用的两种:
1. 嵌套接口投影(推荐,贴合你当前的接口投影用法)
直接定义嵌套结构的投影接口,JPA会自动处理关联数据的嵌套映射,无需额外转换。
定义投影接口
// 顶层投影接口 public interface JoinedDto { ParentProjection getParent(); List<ChildWithGrandChildProjection> getChildren(); } // Parent的字段投影 public interface ParentProjection { Long getId(); String getName(); // 按需添加其他需要的Parent字段 } // Child的嵌套投影 public interface ChildWithGrandChildProjection { ChildProjection getChild(); List<GrandChildProjection> getGrandChildren(); } // Child的字段投影 public interface ChildProjection { Long getId(); String getChildName(); // 按需添加其他需要的Child字段 } // GrandChild的字段投影 public interface GrandChildProjection { Long getId(); String getGrandChildName(); // 按需添加其他需要的GrandChild字段 }
在Repository中编写查询
直接返回顶层投影接口,用JOIN FETCH避免N+1查询:
public interface ParentRepository extends JpaRepository<Parent, Long> { @Query("SELECT p AS parent, c AS child, gc AS grandChild " + "FROM Parent p " + "LEFT JOIN FETCH p.children c " + "LEFT JOIN FETCH c.grandChildren gc " + "WHERE p.id = :parentId") JoinedDto findJoinedById(@Param("parentId") Long parentId); }
JPA会自动将查询结果映射到嵌套的投影接口结构中。
2. 构造函数映射(适合用类类型DTO的场景)
如果需要不可变的类DTO而非接口,可以通过NEW关键字指定构造函数,直接映射嵌套结构。
定义DTO类
public class JoinedDto { private final ParentDto parent; private final List<ChildWithGrandChildDto> children; public JoinedDto(ParentDto parent, List<ChildWithGrandChildDto> children) { this.parent = parent; this.children = children; } // getter方法 } public class ParentDto { private final Long id; private final String name; public ParentDto(Long id, String name) { this.id = id; this.name = name; } } public class ChildWithGrandChildDto { private final ChildDto child; private final List<GrandChildDto> grandChildren; public ChildWithGrandChildDto(ChildDto child, List<GrandChildDto> grandChildren) { this.child = child; this.grandChildren = grandChildren; } } public class ChildDto { private final Long id; private final String childName; public ChildDto(Long id, String childName) { this.id = id; this.childName = childName; } } public class GrandChildDto { private final Long id; private final String grandChildName; public GrandChildDto(Long id, String grandChildName) { this.id = id; this.grandChildName = grandChildName; } }
在Repository中编写查询
使用NEW关键字配合Hibernate的COLLECT函数收集子列表,注意要写全DTO的包路径:
public interface ParentRepository extends JpaRepository<Parent, Long> { @Query("SELECT NEW com.yourpackage.JoinedDto(" + "NEW com.yourpackage.ParentDto(p.id, p.name), " + "COLLECT(NEW com.yourpackage.ChildWithGrandChildDto(" + "NEW com.yourpackage.ChildDto(c.id, c.childName), " + "COLLECT(NEW com.yourpackage.GrandChildDto(gc.id, gc.grandChildName))" + "))" + ") FROM Parent p " + "LEFT JOIN p.children c " + "LEFT JOIN c.grandChildren gc " + "WHERE p.id = :parentId " + "GROUP BY p.id, p.name, c.id, c.childName") JoinedDto findJoinedById(@Param("parentId") Long parentId); }
注意事项
- 两种方案都要注意关联查询的性能,优先用
JOIN FETCH或分组避免N+1查询。 - 接口投影更简洁,无需编写构造函数,适合字段较多的场景;构造函数映射适合需要不可变DTO的场景。
内容的提问来源于stack exchange,提问作者Sidharth Bajpai
相关产品推荐
相关产品推荐

