2012-06-14 16 views
8

Tengo las siguientes entidades; El ticket contiene un conjunto de 0, N WorkOrder:Criterio API: recuperación de una lista devuelve entidad principal repetida

@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; 
} 

Estoy cargando Billetes y obteniendo los atributos. Todos los atributos 0,1 no presentan ningún problema. Para workOrders, utilicé this answer para obtener el siguiente código.

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

El resultado es que, en una consulta que me debería devolver 1 entradas con 5 órdenes de trabajo, estoy recuperando el mismo billete 5 veces.

Si acabo de hacer que los workOrders sean un Fetch ansioso y elimine el código de búsqueda, funciona como debería.

¿Alguien me puede ayudar? Gracias por adelantado.

ACTUALIZACIÓN:

Una explicación acerca de por qué no estoy feliz con la respuesta de JB Nizet (incluso si al final funciona).

Cuando acabo de hacer la relación ansiosa, JPA está examinando exactamente los mismos datos que cuando lo hago perezoso y agrego la cláusula fetch a Criteria/JPQL. Las relaciones entre los diversos elementos también son claras, ya que defino el ListAttribute para la consulta Criteria.

¿Hay alguna explicación razonable por la razón de que JPA no devuelve los mismos datos en ambos casos?

ACTUALIZACIÓN DE GENEROSIDAD: Si bien la respuesta de JB Nizet ha solucionado el problema, todavía me resulta sin sentido que, dados dos operaciones con el mismo significado ("Obtener Ticket a buscar todas WorkOrder dentro ticket.workOrders"), haciendo de ellos por una carga ansiosa no necesita más cambios mientras que especificar una búsqueda requiere un comando DISTINCT

+0

Si nos fijamos en [combinación izquierda] (http://www.w3schools.com/sql/sql_join_left.asp), que es la forma en que funciona. ¿Por qué necesita obtener resultados por leftjoin? – JMelnik

+0

De las tres opciones disponibles en la API de criterios, es la más sensata. Estamos hablando de JPA aquí, así que esperaba que la API organizara el SQL en las entidades de una manera más adecuada. – SJuan76

+0

Ver mi respuesta editada. –

Respuesta

20
  1. Hay una diferencia entre la carga ansiosa y la unión fetch. La carga ansiosa no significa que los datos se carguen dentro de la misma consulta. Simplemente significa que se carga inmediatamente, aunque por consultas adicionales.

  2. El criterio siempre se traduce a una consulta SQL. Si especifica uniones, se unirá en SQL. Por la naturaleza de SQL, esto multiplica los datos de la entidad raíz también, lo que lleva al efecto que obtuviste. (Tenga en cuenta que se obtiene la misma instancia en múltiples ocasiones, por lo que la entidad de raíz no se multiplica en la memoria.)

Hay varias soluciones a que:

  • uso distinct(true)
  • Use la distinta transformador de entidad raíz (.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)).
  • Cuando no necesita filtrar por propiedades secundarias, evite la unión
  • Cuando necesite filtrar por propiedades secundarias, filtre por una subconsulta (DetachedCriteria).
  • Optimizar el problema N + 1 utilizando batch-size
4

¿Ha intentado llamar al distinct(true) en el CriteriaQuery?

La especificación JPA 2, página 161, dice:

La palabra clave DISTINCT se utiliza para especificar que los valores duplicados deben ser eliminado del resultado de la consulta.

Si no se especifica DISTINCT, los valores duplicados no se eliminan.

el Javadoc también dice:

especificar si los resultados de consulta duplicado serán eliminated.A verdadero valor aparecerán duplicados a ser eliminados. Un valor falso causará que se retengan duplicados. Si no se ha especificado distinct, se deben conservar los resultados duplicados .

La razón por la que no es necesario el distintivo cuando la asociación se carga ansiosamente es probablemente solo porque la asociación no se carga con una unión fetch, sino mediante una consulta adicional.

+0

Ok, con 'distinct (true)' funciona. ¿Pero es la solución "correcta" o solo una solución alternativa? Me parece contradictorio que le digo a JPA que solo estoy buscando 'Ticket' y que los WorkOrders deben cargarse en una lista, y que JPA me devuelve el producto cartesiano. – SJuan76

+0

Supongo que esta es la solución correcta, porque eso es lo que la palabra clave distinta también hace en JPQL (o al menos, en HQL). –

+1

El único problema con el distrinct (verdadero) es que se equivoca con su OrderBy si están basados ​​en el atributo de un atributo. – TheBakker

Cuestiones relacionadas