使用JOIN FETCH查询DTO投影时遭遇NonUniqueResultException求助
Spring Data JPA JOIN FETCH关联集合时DTO查询报NonUniqueResultException问题
实体定义
Order 实体
public class Order { @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToOne(mappedBy = "order", cascade = CascadeType.ALL) private Cart cart; }
Cart 实体
public class Cart { @Id private Long id; @OneToOne(fetch = FetchType.LAZY) @MapsId private Order order; @OneToMany(mappedBy = "cart", cascade = CascadeType.ALL, orphanRemoval = true) private List<OrderItem> orderItems; }
OrderItem 实体
public class OrderItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "cart_id") private Cart cart; }
问题场景
执行以下DTO查询时抛出NonUniqueResultException:
return em.createQuery(""" select new OrderDTO( order.id, order.cart ) from Order order join fetch order.cart.orderItems where order.id = :orderId """, OrderDTO.class).setParameter("orderId", orderId).getSingleResult();
异常信息
jakarta.persistence.NonUniqueResultException: query did not return a unique result: 2
数据库数据
- Order表:
+----+ | id | +----+ | 1 | +----+
- Cart表(因使用
@MapsId,id与order_id字段值一致):
+----------+ | order_id | +----------+ | 1 | +----------+
- OrderItem表:
+----+---------+ | id | cart_id | +----+---------+ | 1 | 1 | | 2 | 1 | +----+---------+
需求说明
需通过JOIN FETCH加载所有订单项以避免N+1查询问题,但当前查询因关联集合产生多条结果,调用getSingleResult()报错。直接返回Order实体时查询正常,但改用DTO会触发语义异常;移除fetch指令则仍存在重复结果问题。
解决方案
方案1:添加DISTINCT关键字去重
在JPQL中加入DISTINCT,Hibernate会自动识别并合并重复的DTO实例,同时保留已加载的集合数据:
return em.createQuery(""" select distinct new OrderDTO( order.id, order.cart ) from Order order join fetch order.cart.orderItems where order.id = :orderId """, OrderDTO.class).setParameter("orderId", orderId).getSingleResult();
方案2:先查询实体再转换DTO
先通过JOIN FETCH获取Order实体(Hibernate会自动合并SQL返回的多条结果为单个实体),再手动转换为DTO:
Order order = em.createQuery(""" select distinct o from Order o join fetch o.cart c join fetch c.orderItems where o.id = :orderId """, Order.class).setParameter("orderId", orderId).getSingleResult(); return new OrderDTO(order.getId(), order.getCart());
方案3:修改DTO构造函数直接传入集合
调整OrderDTO的构造函数,直接接收orderItems集合,避免通过Cart间接获取:
// 假设OrderDTO新增构造函数:OrderDTO(Long orderId, Cart cart, List<OrderItem> orderItems) return em.createQuery(""" select distinct new OrderDTO( order.id, order.cart, order.cart.orderItems ) from Order order join fetch order.cart.orderItems where order.id = :orderId """, OrderDTO.class).setParameter("orderId", orderId).getSingleResult();
原理说明
JOIN FETCH关联OneToMany集合时,SQL层面会返回与集合元素数量一致的结果行,但Hibernate查询实体时会自动合并为单个实体对象。而查询DTO时,Hibernate不会自动合并重复的DTO实例,因此需要通过DISTINCT触发合并逻辑,或者先获取实体再做转换。
内容的提问来源于stack exchange,提问作者lotario
相关产品推荐
相关产品推荐

