Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
485 views
in Technique[技术] by (71.8m points)

jpa - Criteria API: Fetch of a list returns repeated main entity

I have the following Entities; Ticket contains a set of 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;
}

I am loading Tickets and fetching the attributes. All of the 0,1 attributes present no problem. For workOrders, I used this answer to get the following code.

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();

The result is that, in a query that should return me 1 Ticket with 5 workOrders, I am retrieving the same Ticket 5 times.

If I just make the workOrders an Eager Fetch and delete the fetch code, it works as it should.

Can anyone help me? Thanks in advance.

UPDATE:

One explanation about why I am not just happy with JB Nizet's answer (even if in the end it works).

When I just make the relationship eager, JPA is examining exactly the same data that when I make it lazy and add the fetch clause to the Criteria / JPQL. The relationships between the various elements is also clear, as I define the ListAttribute for the Criteria query.

There is some reasonable explanaition for the reason that JPA does not return the same data in both cases?

UPDATE FOR BOUNTY: While JB Nizet's answer did solve the issue, I still find it meaningless that, given two operations with the same meaning ("Get Ticket and fetch all WorkOrder inside ticket.workOrders"), doing them by an eager loading needs no further changes while specifying a fetch requires a DISTINCT command

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)
  1. There is a difference between eager loading and fetch join. Eager loading doesn't mean that the data is loaded within the same query. It just means that it is loaded immediately, although by additional queries.

  2. The criteria is always translated to an SQL query. If you specify joins, it will be join in SQL. By the nature of SQL, this multiplies the data of the root entity as well, which leads to the effect you got. (Note that you get the same instance multiple times, so the root entity is not multiplied in memory.)

There are several solutions to that:

  • use distinct(true)
  • Use the distinct root entity transformer (.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)).
  • When you don't need to filter by child properties, avoid the join
  • When you need to filter by child properties, filter by a subquery (DetachedCriteria).
  • Optimize the N+1 problem by using batch-size

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...