【问题标题】:JPA / EclipseLink: Joined WHERE query does not give expected resultJPA / EclipseLink:加入 WHERE 查询未给出预期结果
【发布时间】:2014-06-13 08:19:41
【问题描述】:

现在这很令人困惑...我有一个 JPA 实体 Order 引用了一个实体 User。用户可以是订单的买家或卖家。

因为买家和卖家都可以为订单输入额外信息,所以我将其移至额外的实体 OrderUserData。可能有也可能没有对应的 OrderUserData 对象,但如果存在,用户应该只能看到他们创建的条目(基于 USER_ID),而不是另一方的条目。

实体如下所示:

@Entity
@Table(name = "T_ORDER")
public class Order {

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

    @ManyToOne
    @JoinColumn(name = "SELLER_ID")
    private User seller;

    @ManyToOne
    @JoinColumn(name = "BUYER_ID")
    private User buyer;

    @OneToMany(mappedBy = "order", fetch = FetchType.EAGER)
    private List<OrderUserData> userData = new ArrayList<>();

   //..
}

--

@Entity
@Table(name = "T_ORDER_USERDATA")
public class OrderUserData {

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

    @ManyToOne
    @JoinColumn(name = "ORDER_ID")
    private Order order;

    @ManyToOne
    @JoinColumn(name = "USER_ID")
    private User user;

    private String comment;
  //...
}

User 不是很精彩,只是 ID 和基本名称字段)

现在当我尝试选择合适的数据显示在网站上时,我遇到了一个问题:

String qry = "SELECT o FROM Order o LEFT JOIN o.userData ud "
            + " WHERE (o.seller.id = :userId OR o.buyer.id = :userId)"
            + " AND ( ud.user IS NULL OR ud.user.id = :userId )";
    TypedQuery<Order> query = em.createQuery(qry, Order.class);

    query.setParameter("userId", userId);

假设我执行此操作,将 userId 设置为 2:

我的数据库如下所示:

ORDER
=====
ID    SELLER_ID    BUYER_ID
1     1            2
2     2            3
3     3            1

ORDER_USERDATA
===============
ID     ORDER_ID    USER_ID    COMMENT
1      1           1          Comment that only user 1 should see
2      1           2          Comment that only user 2 should see

但与您预期的不同,在执行上述查询时,两条记录都包含在userData 列表中!似乎 JPA 正在执行两个查询(尽管 EAGER 获取)并忽略了第二个查询的 WHERE。这是为什么?除了循环遍历 Java 级别的 userData 列表并踢出相应用户不应该看到的条目之外,还有什么其他解决方案?

【问题讨论】:

    标签: java jakarta-ee jpa eclipselink


    【解决方案1】:

    无法使用查询在 Order 对象中加载 OrderUserData 对象。也许您将 ORM 功能(将数据库中的行映射到 Java 对象)与查询功能混淆了。
    映射意味着行和对象之间的 1-1 对应关系,因此 Order 对象始终包含与 Order 相关的每个 OrderUserData 行的所有 OrderUserData 对象 行。 fetch 类型只是一种加载策略,它决定了在什么时候获取相关对象,一旦包含对象被加载(EAGER)或被包含对象被访问(LAZY)。
    您可以使用适当的过滤器获取对 OrderUserData 对象的查询并从每个对象中获取 Order 对象的列表,即

    SELECT ud FROM OrderUserData ud WHERE (ud.order.seller.id = :userId 或 ud.order.buyer.id = :userId) AND ( ud.user 为 NULL 或 ud.user.id = :userId)

    【讨论】:

    • 谢谢,真的很管用!我唯一不明白的是为什么? :) WHERE (ud.order.seller.id = :userId ... 反过来不是一样的吗?这是从 OrderUserData 中进行选择,同时对不同的实体(即附加到 OrderUserData 的订单)应用 WHERE 条件。那么为什么从 ud => order 遍历时它可以工作,但反过来却不行呢?
    【解决方案2】:

    您的查询似乎运行良好,因为它正确选择了 Order 实体。然后 JPA 获取所选 Order 的所有 OrderUserData 子项:这是因为未过滤 oneToMany 连接。 我认为不可能使用 eclipseLink(如 Hibernate @FILTER)对预过滤的 oneToMany 进行建模,因此您应该删除它并仅映射 orderUserDataId 字段。然后您可以在 1 个查询中获取您的实体,但它们不会被链接

    SELECT o, ud FROM Order o, o.userData ud WHERE (o.seller.id = :userId OR o.buyer.id = :userId) AND ( ud.orderUserDataId = o.id and (ud.user IS NULL OR ud.user.id = :userId) )";
    

    另一方面,如果其他用例需要 oneToMany,那么您可以创建 2 个不同的 Order 实体:

    • 1 没有 oneToMany 的“OrderLight”
    • 1 带有 oneToMany 的“OrderFull”,源自 OrderLight。

    【讨论】:

      【解决方案3】:

      虽然 user3580357 和 remigio 已经给出了为什么这不起作用的正确答案,但我建议您在数据库级别创建一个视图

      类似的东西(可能需要根据您的需要或 RDBMS 进行调整):

      CREATE OR REPLACE VIEW 
        ORDER_WITH_USERDATA 
      AS 
        SELECT o.*, oud.* 
      FROM ORDER o
      LEFT JOIN ORDER_USERDATA oud
        ON o.id = oud.order_id
      

      这实际上将为每个订单提供两个不同的“逻辑”记录。然后,您可以创建一个在此视图上工作的附加 JPA 实体并执行您的 SELECT/WHERE...,而根本不需要 (LEFT)JOIN。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-10-11
        • 2017-06-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多