使用 JOIN FETCH 查询具有一对多 BI 关联的 DTO 投影时出现 NonUniqueResultException

NonUniqueResultException when querying with JOIN FETCH a DTO projection that has onetomany bi association

提问人:lotario 提问时间:10/25/2023 最后编辑:lotario 更新时间:10/26/2023 访问量:64

问:

我正在使用Spring Data JPA,我得到了以下实体:

订单实体:

public class Order {

@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@OneToOne(mappedBy = "order",
        cascade = CascadeType.ALL)
private 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;

我正在使用以下查询:

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

这 2 个是指具有相同cart_id的订单项目,而不是订单(没有重复的订单 ID)。但是,我不确定 Hibernate 为什么要这样做,因为:

Order Table:

+----+
| id |
+----+
|  1 |
+----+

Cart Table: 

+----------+
| order_id |
+----------+
|        1 |
+----------+

// Since Cart has @MapsId.

OrderItem table:

+----+---------+
| id | cart_id |
+----+---------+
|  1 |       1 |
|  2 |       1 |
+----+---------+

我希望 hibernate 加入 FETCH 所有订单商品,而不仅仅是唯一的结果,因为它们是属于同一购物车的不同订单商品。

我不认为关联配置不正确,也许不是最佳配置,而是我使用的 SQL 查询缺少一些东西,但我不知道是什么。

我正在尝试使用 JOIN FETCH,因为 Cart 中的 OneToMany OrderItem 集合是默认的 LAZY,以便在同一提取中同时接收订单和购物车及其 orderItems。(以前我使用的是 EAGER 但那一团糟,它有 N+1 查询问题)。

如果有人能帮忙,我将不胜感激!

谢谢!

编辑有用的相关信息:

相反,如果我直接使用此查询返回订单实体

 return em.createQuery("select order from Order order " +
                    "join fetch order.cart as cart join fetch cart.orderItems " +
                    "where order.id = :orderId", Order.class)
            .setParameter("orderId", orderId)
            .getSingleResult();

它有效,没有问题。但是,如果我像这样对 OrderDTO 应用相同的 SQL 查询:

return em.createQuery("""
            select new OrderDTO(
               order.id,
               order.cart
            )
            from Order order join fetch order.cart as cart join fetch cart.orderItems
            where order.id = :orderId
            """, OrderDTO.class).setParameter("orderId", orderId).getSingleResult();

我得到

org.hibernate.query.SemanticException: query specified join fetching, but the owner of the fetched association was not present in the select list [SqmSingularJoin(PizzaApp.api.entity.order.Order(order).cart(cart) : cart)

订单DTO:

public record OrderDTO(
        Long id,
        Cart cart
) {
}

编辑2: 我还尝试过使用 POJO 作为 DTO 而不是记录,但仍然由于购物车丢失而得到。SemanticException

编辑 3:删除 fetch 指令(因为在查询 DTO 时不需要它)只留下 join 指令后,我又回到了

jakarta.persistence.NonUniqueResultException: query did not return a unique result

无论我使用什么

return em.createQuery("""
            select new OrderDTO(
               order.id,
               order.cart
            )
            from Order order join order.cart as cart join cart.orderItems
            where order.id = :orderId
            """, OrderDTO.class).setParameter("orderId", orderId).getSingleResult();

 return em.createQuery("""
                select new OrderDTO(
                   order.id,
                   order.cart
                )
                from Order order join order.cart.orderItems
                where order.id = :orderId
                """, OrderDTO.class).setParameter("orderId", orderId).getSingleResult();
java mysql spring hibernate jpa

评论


答:

0赞 phuongnq1995 10/25/2023 #1

如果在数据库上运行休眠生成的查询,则结果中将有 2 行。当您选择 (Order) 时,它起作用的原因是 Hibernate 将 2 行映射到 1 个 Order 对象中,其中包含 2 个基于实体映射类的 OrderItems。Hibernate 从不接触你的数据库,也不关心你的 DTO,他们没有 .Hibernate 根据你用 s 标记 s 的方式做出所有决定,比如 , 。 最简单的解决方案是使用 Entity(Ordering) 保留您的选择,并有一个映射器来映射到 OrderingDTO。Entity@EntityClasses@Annotation@Entity@ManyToOne

评论

0赞 lotario 10/26/2023
您提到的解决方案是一种解决方法,我已经有一个变通方法。我有兴趣学习和理解为什么休眠会抛出,为什么不直接获取所有cart_id与订单 ID/购物车 ID 匹配的 OrderItems,并将它们提取到 Cart 内的 OrderItem 列表中。NonUniqueResultException
0赞 phuongnq1995 10/26/2023
@lotario我更新了我的答案,它可能不清楚和细节。您可以搜索更多信息或学习课程以深入了解它。这是一条艰难的道路。我希望它有所帮助。