2012-06-14 8 views
8

Ho le seguenti Entità; Ticket contiene un insieme di 0, N WorkOrder:: Recupero di una lista restituisce entità principale ripetuta

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

sto caricando I biglietti e andare a prendere gli attributi. Tutti gli attributi 0,1 non presentano alcun problema. Per gli ordini di lavoro, ho usato this answer per ottenere il seguente codice.

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

Il risultato è che, in una query che mi dovrebbe restituire 1 biglietto con 5 workorders, sto Recupero lo stesso biglietto per 5 volte.

Se faccio appena il lavoroOrders a Eager Fetch ed elimini il codice di recupero, funziona come dovrebbe.

Qualcuno può aiutarmi? Grazie in anticipo.

UPDATE:

Una spiegazione sul motivo per cui io non sono solo felice con la risposta di JB Nizet (anche se alla fine funziona).

Quando faccio la relazione desiderosa, JPA sta esaminando esattamente gli stessi dati che quando lo faccio pigro e aggiungo la clausola fetch ai criteri/JPQL. Anche le relazioni tra i vari elementi sono chiare, poiché definisco lo ListAttribute per la query Criteri.

C'è qualche ragionevole spiegazione per il fatto che l'APP non restituisce gli stessi dati in entrambi i casi?

UPDATE per Bounty: Mentre la risposta di JB Nizet ha risolto il problema, trovo ancora privo di significato che, date due operazioni con lo stesso significato ("Get Ticket a prendere tutte WorkOrder all'interno ticket.workOrders"), facendo loro da un eager loading non richiede ulteriori modifiche mentre specificare un recupero richiede un comando DISTINCT

+0

Se si guarda a [Registrazione Sinistra] (http://www.w3schools.com/sql/sql_join_left.asp), è il modo in cui funziona. Perché hai bisogno di recuperare i risultati di leftjoin? – JMelnik

+0

Delle tre opzioni disponibili nell'API dei criteri, è il più ragionevole. Stiamo parlando di JPA qui, quindi mi aspettavo che l'API organizzasse l'SQL nelle entità in modo più corretto. – SJuan76

+0

Vedere la mia risposta modificata. –

risposta

20
  1. C'è una differenza tra il caricamento ansioso e il collegamento fetch. Il caricamento di Eager non significa che i dati vengano caricati all'interno della stessa query. Significa semplicemente che viene caricato immediatamente, anche se con ulteriori query.

  2. I criteri vengono sempre convertiti in una query SQL. Se si specifica join, sarà join in SQL. Per la natura di SQL, moltiplica anche i dati dell'entità root, il che porta all'effetto ottenuto. (Si noti che si ottiene la stessa istanza più volte, in modo che il soggetto principale non è moltiplicato in memoria.)

Ci sono diverse soluzioni a questo:

  • uso distinct(true)
  • Utilizzare i distinti trasformatore di entità root (.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)).
  • Quando non è necessario filtrare per proprietà figlio, evitare l'unione
  • Quando è necessario filtrare per proprietà figlio, filtrare in una sottoquery (DetachedCriteria).
  • Ottimizzare il problema N + 1 utilizzando batch-size
4

Hai provato a chiamare distinct(true) su CriteriaQuery?

La specifica JPA 2, pagina 161, dice:

La parola chiave DISTINCT viene utilizzata per specificare che i valori duplicati devono essere eliminati dal risultato della query.

Se DISTINTO non viene specificato, i valori duplicati non vengono eliminati.

JavaDoc dice anche:

Specificare se i risultati delle query duplicato saranno eliminated.A vero valore causerà duplicati da eliminare. Un valore falso causerà il mantenimento dei duplicati . Se distinto non è stato specificato, i risultati duplicati devono essere conservati.

Il motivo per cui non è necessario il distinto quando l'associazione è caricata con impazienza è probabilmente solo che l'associazione non viene caricata utilizzando un join di recupero, ma utilizzando una query aggiuntiva.

+0

Ok, con 'distinct (true)' funziona. Ma è la soluzione "corretta" o solo una soluzione? Trovo controintuitivo che sto dicendo a JPA che sto cercando "Ticket" e che gli ordini di lavoro devono essere caricati in una lista e che l'APP mi restituisce solo il prodotto cartesiano. – SJuan76

+0

Immagino che questa sia la soluzione corretta, perché è quello che fa anche la parola chiave distinta in JPQL (o almeno, in HQL). –

+1

Unico problema con distrazione (true) è che si confonde con il tuo OrderBy se si basano sull'attribuzione di un attributo. – TheBakker

Problemi correlati