Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial
  • Lezione 30 di 68
  • livello ninja
Indice lezioni

Relazioni: caricamenti EAGER e LAZY

Analizziamo i tipi di caricamento del campo di un Entity per comprendere il meccanismo di caricamento di oggetti Entity relazionati: EAGER e LAZY.
Analizziamo i tipi di caricamento del campo di un Entity per comprendere il meccanismo di caricamento di oggetti Entity relazionati: EAGER e LAZY.
Link copiato negli appunti

Prima di procedere con la realizzazione di query più complesse di quella che implementa il metodo findBy, è necessario
comprendere il meccanismo di caricamento di oggetti Entity relazionati.

Tipi di caricamento

Per un campo di un Entity possiamo specificare
due tipi di caricamento: EAGER e LAZY. Il tipo di caricamento può essere specificato su un campo di un Entity che rappresenta
una colonna attraverso l'annotation @Basic, mentre su un campo che rappresenta una relazione attraverso l'annotation di relazione
stessa avremo @OneToOne, @OneToMany, @ManyToMany o @ManyToOne.

Il caricamento EAGER indica che l'oggetto di campo/relazione sarà caricato
al momento della creazione dell'oggetto padre che lo contiene. Il caricamento LAZY indica invece che l'oggetto di relazione sarà
caricato al momento dell'invocazione del metodo sul quale è applicata l'annotation che specifica la modalità LAZY.

Ad esempio, se avessimo un campo con annotation @Lob collegato ad una colonna di database che contiene un'immagine, vorremmo
che l'immagine venisse caricata selezionando un particolare record dalla lista recuperata e non contestualmente alla
creazione della lista stessa, questo per ovvi motivi di performance.

Per realizzare il LAZY su un campo di questo tipo, utilizziamo
l'attributo fetch presente nell'annotation @Basic e in quelle di relazione:

..
@Column(name="IMAGE")
@Lob
@Basic(fetch=FetchType.LAZY)
public byte[] getImage(){
 return image;
}
..

Cosi quando all'interno di un applicativo si invoca il metodo getImage() su un oggetto dell'Entity che lo racchiude,
viene eseguita la query per il recupero dell'immagine.

Relazioni e comportamenti

Le relazioni hanno un loro comportamento di default,
il comportamento in assenza della specifica dell'attributo fetch:

  • OneToOne: EAGER
  • ManyToOne: EAGER
  • OneToMany: LAZY
  • ManyToMany: LAZY

Esiste chiaramente un motivo per la scelta di questi valori. La OneToOne e la ManyToOne si traducono
a livello di database in una query con un JOIN tra due tabelle che restituisce un solo record, da un punto di vista relazionale
questo JOIN è un'operazione più efficiente rispetto a quella di effettuare una query per l'entity padre ed una query per l'entity
figlia se il comportamento fosse impostato su LAZY.

Con le OneToMany e ManyToMany l'aspetto critico è rappresentato
dalla quantità di record relazionati. Supponiamo infatti di specificare per la relazione OneToMany tra Customer e Booking
il comportamento EAGER e di effettuare una query (JPQL che vedremo tra un pò) in modo tale da recuperare tutti i clienti.
Se per il cliente i-esimo della lista ritornata abbiamo Ni oggetti Booking recuperati e se la lista ha dimensione m,
avremo un carico di record associato alla query pari a N1+N3+N4+...+Nm, un quantitativo di record tale da poter creare seri problemi di performance
a livello di database.

Un discorso analogo può essere fatto per la ManyToMany. Questo spiega la scelta di EAGER come default.
Dobbiamo però comprendere che i comportamenti di default non rappresentano la soluzione migliore a tutti i casi possibili.
Supponiamo infatti di avere un'Entity con un gran numero di relazioni ManyToOne, se lasciamo il comportamento di default a EAGER
al recupero di un oggetto o di una lista di oggetti di questa Entity, avremo la costruzione di una query con un gran numero di JOIN
che può degradare pesantemente le performance del sistema.

La soluzione in questo caso è analizzare la relazione dell'Entity con le altre
ed impostare a LAZY quelle che effettivamente non sono necessarie nel contesto corrente. Le relazioni OneToMany e ManyToMany nel loro
comportamento di default possono essere, invece, soggette al cosiddetto N+1 problem che si spiega facilmente attraverso un esempio.

Immaginiamo di dover presentare una lista di oggetti Customer e di avere necessità di caricare contestualmente anche i dati di Booking.
Abbiamo impostato sulla relazione "OneToMany Customer-Booking" il comportamento LAZY, quindi siamo portati ad effettuare una prima
query per recuperare la lista di Customer e successivamente iterare la lista ottenuta invocando su ciascun oggetto Customer il metodo getBooking()
all'interno di un contesto transazionale per Hibernate.

Procedendo in questo modo diamo vita ad un'ulteriore query per ciascun oggetto Customer. Se gli oggetti
Customer restituiti sono N, abbiamo la prima query più N successive query: N+1 in totale. Questo problema si risolve utilizzando una particolare
query JPQL come vedremo tra breve.

Un altro aspetto importante da comprendere è che le operazioni persist(), merge() e remove() dell'EnityManager
non causano un immediato cambiamento sul database, queste operazioni sono posticipate alla fase di flush dell'EntityManager per motivi
di performance. Inserire gli statament il più possibile in una operazione batch è molto più performante che eseguire molti statement singoli
verso il database.

Per default la modalità flush è impostata su AUTO, questo significa che l'EntityManager realizza il flush automaticamente
quando necessario. In generale ciò accade alla fine di una transazione per un persistence-context transaction-scoped e quando il persistence-context
è chiuso nel caso di persistence-context extended.

Si può scegliere di impostare il flush su COMMIT attraverso il metodo setFlushMode()
dell'EntityManager, questo ci porta quindi a dover gestire manualmente la sincronizzazione avendo però un controllo completo. Si tratta
di un metodo da usare con cautela e solo se davvero necessario.

Ti consigliamo anche