Criteria API:获取列表返回重复的主实体

SJu*_*n76 9 jpa criteria-api jpa-2.0

我有以下实体; 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;
}
Run Code Online (Sandbox Code Playgroud)

我正在加载Tickets并获取属性.所有0,1属性都没有问题.对于workOrders,我使用此答案来获取以下代码.

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();
Run Code Online (Sandbox Code Playgroud)

结果是,在一个查询应该返回1个带有5个workOrders的Ticket,我正在检索相同的Ticket 5次.

如果我只是让workOrders成为一个Eager Fetch并删除了获取代码,它就可以正常工作.

谁能帮我?提前致谢.

更新:

一个解释为什么我不满意JB Nizet的答案(即使它最终有效).

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

有两个合理的解释是因为JPA在这两种情况下都没有返回相同的数据吗?

更新BOUNTY:虽然JB Nizet的答案确实解决了这个问题,但我仍然发现,如果两个具有相同含义的操作("Get Ticketand fetch all WorkOrderinside ticket.workOrders")没有意义,那么通过急切加载来执行它们不需要进一步更改fetch需要一个DISTINCT命令

Ste*_*ger 24

  1. 预先加载和获取加入之间存在差异.急切加载并不意味着数据在同一查询中加载.它只是意味着它是立即加载的,尽管是通过其他查询.

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

有几种解决方案:

  • 使用 distinct(true)
  • 使用不同的根实体转换器(.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)).
  • 如果不需要按子属性进行筛选,请避免连接
  • 当您需要按子属性进行筛选时,请按子查询(DetachedCriteria)进行筛选.
  • 使用批量大小优化N + 1问题