【问题标题】:Criteria API: Fetch of a list returns repeated main entityCriteria API:获取列表返回重复的主要实体
【发布时间】:2012-06-17 09:40:27
【问题描述】:

我有以下实体; Ticket 包含一组 0,N 个 WorkOrder:

@Entity
public class Ticket {

  ...

  @OneToMany(mappedBy="ticket", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
  private List<WorkOrder> workOrders = null;

  ...
}

@Entity
public class WorkOrder {
  ...
  @ManyToOne
  @JoinColumn(nullable = false)
  private Ticket ticket;
}

我正在加载票证并获取属性。所有 0,1 属性都没有问题。对于workOrders,我使用this answer获取以下代码。

CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Ticket> criteriaQuery = criteriaBuilder
  .createQuery(Ticket.class);
Root<Ticket> rootTicket = criteriaQuery.from(Ticket.class);

ListAttribute<? super Ticket, WorkOrder> workOrders =
  rootTicket.getModel().getList("workOrders", WorkOrder.class);
rootTicket.fetch(workOrders, JoinType.LEFT);

    // WHERE logic
    ...

criteriaQuery.select(rootTicket);
TypedQuery<Ticket> query = this.entityManager.createQuery(criteriaQuery);
return query.getResultList();

结果是,在一个应该返回我 1 个工单和 5 个工作订单的查询中,我检索了同一个工单 5 次。

如果我只是将 workOrders 设为 Eager Fetch 并删除 fetch 代码,它会正常工作。

谁能帮帮我?提前致谢。

更新:

关于为什么我对 JB Nizet 的回答不满意的一个解释(即使最终它有效)。

当我只是使关系急切时,JPA 正在检查与我使它变得懒惰并将 fetch 子句添加到 Criteria / JPQL 时完全相同的数据。各种元素之间的关系也很清楚,因为我为 Criteria 查询定义了ListAttribute

对于JPA在两种情况下没有返回相同数据的原因有一些合理的解释吗?

更新赏金:虽然 JB Nizet 的回答确实解决了这个问题,但我仍然认为这毫无意义,因为两个操作具有相同的含义(“获取 Ticket 并在 ticket.workOrders 中获取所有 WorkOrder”),做它们通过预先加载不需要进一步更改,而指定 fetch 需要 DISTINCT 命令

【问题讨论】:

  • 如果您查看Left Join,它就是这样工作的。为什么需要leftjoin取结果?
  • 在条件 API 中可用的三个选项中,它是更明智的。我们在这里讨论的是 JPA,所以我希望 API 能够以更恰当的方式组织实体中的 SQL。

标签: jpa jpa-2.0 criteria-api


【解决方案1】:
  1. 急切加载和获取连接之间存在差异。急切加载并不意味着数据是在同一个查询中加载的。它只是意味着它会立即加载,尽管是通过额外的查询。

  2. 条件始终转换为 SQL 查询。如果您指定连接,它将是 SQL 中的连接。根据 SQL 的性质,这也会乘以根实体的数据,从而产生您得到的效果。 (请注意,您多次获得相同的实例,因此根实体不会在内存中相乘。)

有几种解决方案:

  • 使用distinct(true)
  • 使用不同的根实体转换器 (.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY))。
  • 当您不需要按子属性过滤时,请避免加入
  • 当您需要按子属性进行过滤时,请按子查询 (DetachedCriteria) 进行过滤。
  • 使用batch-size优化N+1问题

【讨论】:

    【解决方案2】:

    您是否尝试在 CriteriaQuery 上调用 distinct(true)

    JPA 2 规范,第 161 页说:

    DISTINCT 关键字用于指定重复值必须是 从查询结果中剔除。

    如果未指定 DISTINCT,则不会消除重复值。

    javadoc 还说:

    指定是否消除重复的查询结果。A true value 将导致重复项被消除。错误的值会导致 要保留的重复项。如果没有指定 distinct, 必须保留重复的结果。

    在急切加载关联时不需要 distinct 的原因可能只是关联没有使用 fetch join 加载,而是使用附加查询。

    【讨论】:

    • 好的,distinct(true) 可以工作。但它是“正确”的解决方案还是只是一种解决方法?我发现我告诉 JPA 我只是在寻找 Ticket 并且 WorkOrders 必须加载到一个列表中,并且 JPA 只返回我的笛卡尔积,这违反直觉。
    • 我想这是正确的解决方案,因为 distinct 关键字在 JPQL(或至少在 HQL)中也是如此。
    • distrinct(true) 的唯一问题是,如果它们基于属性的属性,它会与您的 OrderBy 混淆。
    猜你喜欢
    • 2020-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-16
    • 1970-01-01
    • 1970-01-01
    • 2018-11-05
    相关资源
    最近更新 更多