Il recupero impaziente JPA non si unisce


112

Che cosa fa esattamente il controllo della strategia di recupero di JPA? Non riesco a rilevare alcuna differenza tra desideroso e pigro. In entrambi i casi JPA / Hibernate non unisce automaticamente le relazioni molti-a-uno.

Esempio: la persona ha un unico indirizzo. Un indirizzo può appartenere a molte persone. Le classi di entità annotate JPA hanno il seguente aspetto:

@Entity
public class Person {
    @Id
    public Integer id;

    public String name;

    @ManyToOne(fetch=FetchType.LAZY or EAGER)
    public Address address;
}

@Entity
public class Address {
    @Id
    public Integer id;

    public String name;
}

Se utilizzo la query JPA:

select p from Person p where ...

JPA / Hibernate genera una query SQL da selezionare dalla tabella Person, quindi una query di indirizzo distinta per ogni persona:

select ... from Person where ...
select ... from Address where id=1
select ... from Address where id=2
select ... from Address where id=3

Questo è molto negativo per set di risultati di grandi dimensioni. Se ci sono 1000 persone, genera 1001 query (1 da Persona e 1000 distinte da Indirizzo). Lo so perché sto guardando il registro delle query di MySQL. Sapevo che l'impostazione del tipo di recupero dell'indirizzo su eager farà sì che JPA / Hibernate interroghi automaticamente con un join. Tuttavia, indipendentemente dal tipo di recupero, genera comunque query distinte per le relazioni.

Solo quando gli dico esplicitamente di aderire, effettivamente si unisce:

select p, a from Person p left join p.address a where ...

Mi manca qualcosa qui? Ora devo codificare a mano ogni query in modo che si unisca alle relazioni molti-a-uno. Sto usando l'implementazione JPA di Hibernate con MySQL.

Modifica: sembra (vedere le domande frequenti su Hibernate qui e qui ) che FetchTypenon influisce sulle query JPA. Quindi nel mio caso ho detto esplicitamente di aderire.


5
I collegamenti alle voci delle domande frequenti sono interrotti, qui sta funzionando uno
n0weak

Risposte:


99

JPA non fornisce alcuna specifica sulla mappatura delle annotazioni per selezionare la strategia di recupero. In generale, le entità correlate possono essere recuperate in uno qualsiasi dei modi indicati di seguito

  • SELECT => una query per entità root + una query per entità / raccolta mappata correlata di ciascuna entità root = (n + 1) query
  • SUBSELECT => una query per entità root + seconda query per entità mappata correlata / raccolta di tutte le entità root recuperate nella prima query = 2 query
  • JOIN => una query per recuperare entrambe le entità root e tutta la loro entità / raccolta mappata = 1 query

Quindi SELECTe JOINsono due estremi e SUBSELECTcadono in mezzo. Si può scegliere la strategia adatta in base al proprio modello di dominio.

Per impostazione predefinita SELECTviene utilizzato sia da JPA / EclipseLink che da Hibernate. Questo può essere sovrascritto utilizzando:

@Fetch(FetchMode.JOIN) 
@Fetch(FetchMode.SUBSELECT)

in Hibernate. Consente inoltre di impostare la SELECTmodalità in modo esplicito utilizzando @Fetch(FetchMode.SELECT)che può essere regolata utilizzando la dimensione del batch, ad es @BatchSize(size=10).

Le annotazioni corrispondenti in EclipseLink sono:

@JoinFetch
@BatchFetch

5
Perché esistono queste impostazioni? Penso che JOIN debba essere usato quasi sempre. Ora devo contrassegnare tutte le mappature con annotazioni specifiche per l'ibernazione.
vbezhenar

4
Interessante ma purtroppo @Fetch (FetchMode.JOIN) non funziona affatto per me (Hibernate 4.2.15), in JPQL come in Criteria.
Aphax

3
Anche l'annotazione Hibernate sembra non funzionare affatto per me, utilizzando Spring JPA
TheBakker

2
@Aphax Ciò potrebbe essere dovuto al fatto che Hibernate utilizza diverse strategie predefinite per JPAQL / Criteria rispetto a em.find (). Vedere vladmihalcea.com/2013/10/17/… e la documentazione di riferimento.
Joshua Davis

1
@vbezhenar (e altri leggono il suo commento qualche tempo dopo): la query JOIN genera il prodotto cartesiano nel database quindi dovresti essere sicuro di volere che il prodotto cartesiano venga calcolato. Nota che se usi il fetch join, anche se metti LAZY verrà caricato con entusiasmo.
Walfrat

45

"mxc" è giusto. fetchTypespecifica solo quando la relazione deve essere risolta.

Per ottimizzare il caricamento desideroso utilizzando un outer join devi aggiungere

@Fetch(FetchMode.JOIN)

nel tuo campo. Questa è un'annotazione specifica per l'ibernazione.


6
Questo non funziona per me con Hibernate 4.2.15, in JPQL o Criteria.
Aphax

6
@Aphax Penso che sia perché JPAQL e Criteria non obbediscono alla specifica Fetch. L'annotazione Fetch funziona solo per em.find (), AFAIK. Vedere vladmihalcea.com/2013/10/17/… Inoltre, vedere i documenti di ibernazione. Sono abbastanza sicuro che questo sia coperto da qualche parte.
Joshua Davis

@JoshuaDavis Quello che voglio dire è che l'annotazione \ @Fetch non applica alcun tipo di ottimizzazione JOIN nelle query, sia JPQL che em.find (), ho appena fatto un altro tentativo su Hibernate 5.2. + Ed è sempre lo stesso
Aphax

38

L'attributo fetchType controlla se il campo annotato viene recuperato immediatamente quando viene recuperata l'entità primaria. Non determina necessariamente come viene costruita l'istruzione fetch, l'implementazione effettiva di sql dipende dal provider che stai utilizzando toplink / hibernate ecc.

Se imposti fetchType=EAGERCiò significa che il campo annotato viene popolato con i suoi valori contemporaneamente agli altri campi nell'entità. Quindi, se apri un entitymanager, recuperi i tuoi oggetti persona e poi chiudi entitymanager, eseguendo successivamente un person.address non verrà generata un'eccezione di caricamento lazy.

Se imposti fetchType=LAZYil campo viene popolato solo quando vi si accede. Se hai chiuso Entitymanager per allora, verrà lanciata un'eccezione di caricamento lento se esegui person.address. Per caricare il campo è necessario rimettere l'entità in un contesto entitymangers con em.merge (), quindi accedere al campo e quindi chiudere entitymanager.

Potresti volere un caricamento lento quando costruisci una classe cliente con una raccolta per gli ordini dei clienti. Se hai recuperato ogni ordine per un cliente quando volevi ottenere un elenco di clienti, questa potrebbe essere un'operazione costosa del database quando cerchi solo il nome del cliente e i dettagli di contatto. È meglio lasciare l'accesso al database più tardi.

Per la seconda parte della domanda: come ottenere l'ibernazione per generare SQL ottimizzato?

Hibernate dovrebbe consentirti di fornire suggerimenti su come costruire la query più efficiente, ma sospetto che ci sia qualcosa di sbagliato nella costruzione della tua tabella. La relazione è stabilita nelle tabelle? Hibernate potrebbe aver deciso che una semplice query sarà più veloce di un join, specialmente se mancano gli indici ecc.


20

Prova con:

select p from Person p left join FETCH p.address a where...

Per me funziona in modo simile con JPA2 / EclipseLink, ma sembra che questa funzione sia presente anche in JPA1 :



2

per unirti puoi fare più cose (usando eclipselink)

  • in jpql puoi eseguire il recupero del join sinistro

  • nella query con nome è possibile specificare un suggerimento per la query

  • in TypedQuery puoi dire qualcosa come

    query.setHint("eclipselink.join-fetch", "e.projects.milestones");

  • c'è anche un suggerimento per il recupero in batch

    query.setHint("eclipselink.batch", "e.address");

vedere

http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html


1

Ho avuto esattamente questo problema con l'eccezione che la classe Person aveva una classe chiave incorporata. La mia soluzione è stata unirmi a loro nella query E rimuovere

@Fetch(FetchMode.JOIN)

La mia classe ID incorporata:

@Embeddable
public class MessageRecipientId implements Serializable {

    @ManyToOne(targetEntity = Message.class, fetch = FetchType.LAZY)
    @JoinColumn(name="messageId")
    private Message message;
    private String governmentId;

    public MessageRecipientId() {
    }

    public Message getMessage() {
        return message;
    }

    public void setMessage(Message message) {
        this.message = message;
    }

    public String getGovernmentId() {
        return governmentId;
    }

    public void setGovernmentId(String governmentId) {
        this.governmentId = governmentId;
    }

    public MessageRecipientId(Message message, GovernmentId governmentId) {
        this.message = message;
        this.governmentId = governmentId.getValue();
    }

}

-1

Mi vengono in mente due cose.

Innanzitutto, sei sicuro di voler dire ManyToOne per indirizzo? Ciò significa che più persone avranno lo stesso indirizzo. Se viene modificato per uno di essi, verrà modificato per tutti. È questo il tuo intento? Il 99% delle volte gli indirizzi sono "privati" (nel senso che appartengono a una sola persona).

In secondo luogo, hai altre relazioni appassionate con l'entità Persona? Se ricordo bene, Hibernate può gestire solo una relazione desiderosa su un'entità, ma probabilmente è un'informazione obsoleta.

Lo dico perché la tua comprensione di come dovrebbe funzionare è essenzialmente corretta da dove sono seduto.


Questo è un esempio inventato che utilizza molti-a-uno. L'indirizzo personale potrebbe non essere stato il miglior esempio. Non vedo altri tipi di recupero desiderosi nel mio codice.
Steve Kuo,

Il mio suggerimento è quindi di ridurre questo a un semplice esempio che viene eseguito e fa ciò che stai vedendo e quindi pubblicalo. Potrebbero esserci altre complicazioni nel modello che causano un comportamento imprevisto.
cletus

1
Ho eseguito il codice esattamente come appare sopra e mostra tale comportamento.
Steve Kuo,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.