Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 417
SJuan76 Avatar asked Jun 14 '12 11:06

SJuan76


1 Answers

  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
like image 121
Stefan Steinegger Avatar answered Oct 08 '22 20:10

Stefan Steinegger