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
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.
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:
distinct(true)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
).DetachedCriteria
).