Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Ottimizzazione Hibernate

Piccole accortezze per migliorare il funzionamento CRUD (Create, Read, Update, Delete) di Hibernate
Piccole accortezze per migliorare il funzionamento CRUD (Create, Read, Update, Delete) di Hibernate
Link copiato negli appunti

Di Hibernate ne abbiamo già parlato in un precedente articolo, nel quale avevamo visto una panoramica introduttiva. Quello che non abbiamo detto è che definisce tre tipi fondamentali di collezioni:

  • collezioni di valori
  • associazione uno a molti
  • associazione molti a molti

Questa classificazione distingue le varie relazioni tra tabelle e chiavi esterne, ma non ci dice abbastanza di quello che ci interessa sul modello relazionale. Per capire completamente la struttura relazionale e le caratteristiche di performance, dobbiamo anche prendere in considerazione la struttura della chiave primaria, che viene usata da Hibernate per modificare o cancellare le righe corrispondenti alla collezione. Questo suggerisce la classificazione seguente:

  • collezioni con indice (indexed collection)
  • insiemi (set)
  • "sacchi" (bags)

Tutte le collezioni indicizzate (mappe, liste, array) hanno una chiave primaria che consiste nelle colonne <key> (chiave) e <index> (indice). Solitamente in questi casi gli aggiornamenti delle collezioni sono molto performanti, poiché la chiave primaria può essere indicizzata in modo efficiente e una riga particolare può, quindi, essere localizzata rapidamente quando Hibernate cerca di modificarla o cancelarla.

Gli insiemi hanno una chiave primaria che consiste delle colonne <key> ed <element>. Questo può essere meno efficiente per alcuni tipi di elemento della collezione, in particola per elementi composti o campi molto lunghi di testo o dati binari; il database può non essere in grado di indicizzare un chiave primaria complessa in maniera altrettanto efficiente che nel caso precedente. Da un altro punto di vista, per associazioni uno a molti o molti a molti, in particolare nel caso di identificatori sintetici, è probabile che sia efficiente nello stesso modo.

I "sacchi" (bags) sono il caso peggiore. Poiché un bag consente elementi duplicati e non ha una colonna indice, non può essere definita una chiave primaria. Hibernate non ha modo di distinguere tra righe duplicate, e quindi risolve il problema rimuovendo completamente (con una singola DELETE) e ricreando la collezione ogni volta che cambia. Questo tuttavia può essere molto inefficiente.

Notate che per una collezione uno a molti, la chiave primaria può non essere la chiave primaria fisica della tabella del database, ma anche in questo caso la classificazione qui sopra è comunque utile, poiché riflette come Hibernate recupera righe specifiche della collezione.

Liste, mappe e insiemi sono le collezioni più efficienti da modificare. Gli insiemi consentono le operazioni più efficienti in termini di aggiunta, rimozione e modifica di elementi. C'è un vantaggio ulteriore che le collezioni indicizzate hanno rispetto agli insiemi per le associazioni molti a molti o le collezioni di valori. Per come è fatta la struttura di un Set, Hibernate non aggiorna (UPDATE) neppure una riga, quando un elemento è "cambiato". I cambiamenti ad un Set funzionano sempre via INSERT e DELETE (di righe individuali). Poiché ricordiamo che gli array non possono essere caricati a richiesta (lazy), concludiamo quindi che le liste, le mappe e gli insiemi sono tipi di collezione più performanti. Gli insiemi sono probabilmente il genere di collezione più comune nelle applicazioni basate su Hibernate.

Prima che buttiate via i "bag" per sempre, c'è un caso particolare in cui essi (e le liste) sono molto più performanti degli insiemi. Per una collezione con inverse="true" possiamo aggiungere elementi ad un bag o una lista senza bisogno di inizializzare (fetch) gli elementi del bag stesso. Questo perché Collection.add() o Collection.addAll() devono sempre ritornare "true" per un bag o un List (a differenza di un Set). Questo può rendere il codice seguente molto più veloce:

Parent p = (Parent) sess.load(Parent.class, id);
Child c = new Child();
c.setParent(p);
p.getChildren().add(c);
sess.flush();

Cancellazione

Supponiamo di aggiungere un elemento singolo ad una collezione di dimensione 20, e poi rimuovere due elementi. Hibernate lancerà una INSERT e due DELETE (a meno che la collezione sia un bag). Però, supponiamo di rimuovere diciotto elementi, lasciandone due, e poi di aggiungere tre elementi nuovi. Ci sono due modi possibili di procedere.

  • cancellare le diciotto righe una ad una e poi inserire le tre;
  • rimuovere tutta la collezione in un solo comando DELETE e inserire tutti i cinque elementi rimanenti uno ad uno.

Hibernate non è abbastanza "furbo" da sapere che la seconda opzione è probabilmente più veloce in questo caso, meglio così in quanto un comportamento del genere potrebbe far confondere dei trigger. Fortunatamente, potete rimediare questo comportamento in ogni momento scartando la collezione originale ed impostando una nuova collezione con tutti gli elementi che devono rimanere. Questo può essere molto utile e potente, in certi casi. Hibernate fa il caching degli oggetti persistenti al livello della Session, è comunque possibile impostare strategie di caching più aggressive per classi specifiche.

Hibernate implementa un sistema per l'inizializzazione ritardata (lazy) degli oggetti persistenti tramite dei mediatori (proxy) creati in fase di esecuzione tramite una tecnica di arricchimento del codice binario (byte code) che sfrutta le funzionalità fornite dall'eccellente libreria CGLIB. Il file di mappaggio dichiara una classe o un'interfaccia che va usata come interfaccia del proxy per quella classe. L'approccio raccomandato è specificare la classe stessa:

<class name="eg.Cat" proxy="eg.Cat">
...
<subclass name="eq.DomesticCat" proxy="eq.DomesticCat">
...
<subclass>
<class>

Prima di tutto, le istanze di Cat non potranno essere oggetto di "cast" a DomesticCat, anche se l'istanza sottostante è effettivamente un DomesticCat.

Car cat = (Cat) session.load(Cat.class,id);
if(cat.isDomesticCat()){
  DomesticCt dc = (DomesticCt) cat;
  ...
}

In secondo luogo, è possibile che la semantica "==" non valga per il proxy.

Cat cat = (Cat) session.load(Cat.class,id);
DomesticCt dc = (DomesticCat) sessionLoad(DomesticCat.ckass,id);
System.out.println(cat==dc); //false

Comunque, queste situazioni non sono poi cosi male come sembra. Anche se ora abbiamo due riferimenti diversi ad oggetti proxy, l'istanza sottostante è comunque la stessa:

cat.setWeight(11.0);
System.out.println(dc.getWeigth());

Terzo, non è possibile usare un mediatore CGLIB per una classe final o per una classe con metodi final. Infine, se il vostro oggetto persistente acquisisce delle risorse quando si istanzia (ad esempio negli inizializzatori o nel costruttori di default), quelle risorse saranno acquisite anche dal proxy, poiché la classe del proxy è effettivamente una sottoclasse della classe persistente. Questi problemi sono tutti derivanti da limitazioni di base nel modello a ereditarietà singola in Java. Se volete evitarli, le vostre classi persistenti devono inplementare un'interfaccia che dichiari i loro metodi di business. Dovete poi specificare queste interfacce nel file di mapping:

<class name="eg.Cat" proxy="eg.ICat">
...
<subclass name="eg.DomesticCat" proxy="eg.IDomesticCat">
...
</subclass>
</class>

Laddove Cat implementa l'interfaccia ICat e DomesticCat implementa l'interfaccia IDomesticCat. A questo punto, load() o iterate() possono restituire direttamente istanze di Cat e DomesticCat.

Alcune operazioni non richiedono inizializzazione del proxy:

  • equals(), se la classe persistente non sovrascrive equals();
  • hashCode(), se la classe persistente non sovrascrive hashCode();
  • Il metodo "getter" per l'identificatore.

Hibernate individuerà le classi persistenti che sovrascrivono equals() o hashCode(). Le eccezioni che capitano quando si inizializza un proxy vengono racchiuse in una LazyInitializationException. In alcuni casi, dobbiamo assicurarci che un mediatore o una collezione vengano inizializzati prima di chiudere la Session.

Naturalmente, possiamo sempre forzare l'inizializzazione chiamando cat.getSex() o cat.getKittens().size(), ad esempio. Ma questo confonde chi legge il codice e non è pratico per del codice generico. I metodi statici Hibernate.initialize() e Hibernate.isInitialized() forniscono all'applicazione un modo comodo per lavorare con collezioni inizializzate a richiesta o con i mediatori.Hibernate.initialize(cat) imporrà l'inizializzazione di un mediatore cat, a condizione che la sua Session sia ancora aperta.Hibeernate.initialize(cat.getKittens()) ha un effetto simile per la collezione.

La cache di secondo livello

Una Session di Hibernate è una cache di dati persistenti durante la transazione. È possibile configurare una cache a livello di cluster o a livello di macchina virtuale (JVM level o SessionFactory level) per classi o collezioni specifiche. È anche possibile agganciare (plug-in) una cache in un cluster. Fate attenzione, tuttavia: le cache non sono mai coscienti di cambiamenti fatti ai dati sul contenitore fisico da un'altra applicazione (benché possano essere configurate in modo tale da fare scadere i dati conservati in memoria). L'impostazione predefinita di Hibernate è di usare la libreria EHCache per il caching a livello di JVM (Il supporto di JCS è deprecato e verrà rimosso in una versione futura di Hibernate). È possibile scegliere una implementazione diversa specificando il nome di una classe che implementi net.sf.hibernate.cache.CacheProvider usando la proprietà hibernate.cache.provider_class

Se l'applicazione deve modificare i dati, una cache read-write (lettura/scrittura) potrebbe essere appropriata. Questa strategia di caching non dovrebbe essere mai usata se è richiesto un livello di isolamento serializzabile delle transazioni. Se la cache è usata in un ambiente JTA, dovete specificare la proprietà hibernate.transaction.manager_lookup_class, indicando una strategia per ottenere il TransactinManager JTA. In altri ambienti, dovete assicurarvi che la transazione venga completata quando vengono chiamati Session.close() o Session.disconnect(). Se volete usare questa strategia in un cluster, dovete assicurarvi che l'implementazione della cache sottostante supporti il locking. La cache fornita con Hibernate non lo fa.

Se l'applicazione ha bisogno di modificare dati solo occasionalmente (cioè se è molto improbabile che due transazioni tentino di modificare lo stesso oggetto simultaneamente) e l'isolamento stretto delle transazioni non è richiesto, potrebbe essere appropriata una cache nonstrict-read.write (lettura/scrittura non stretta). Se la cache è usata in un abiente JTA, dovete specificare hibernate.transaction.manager_lookup_class. In altri ambienti, dovete assicurare che la transazione sia completa quando vengono chiamati Session.close() o Session.disconnect().

La strategia di caching transazionale fornisce supporto per cache completamente transazionali come la JBoss TreeCache. Una cache di questo tipo può essere usata solo in un contesto JTA e dovete specificare la proprietà hibernate.transaction.manager_lookup_class.

Gestione della cache di Session

Ogni volta che passate un oggetto ai metodi di save(), update() o saveOrUpdate() e ogni volta che ne recuperate uno usando load(), find(), iterate() o filter(), quell'oggetto viene aggiunto alla cache interna della Session.

Quando poi viene chiamato flush(), lo stato di quell'oggetto sarà sincronizzato con il database. Se non volete che questa sincronizzazione avvenga, o se state elaborando un grande numero di oggetti e volete gestire la memoria efficentemente, potete usare il metodo evict() per rimuovere l'oggetto e le sue collezioni della cache.

La Session fornisce anche un metodo contains() per determinare se un'istanza appartiene alla cache di sessione. Per rimuovere completamente tutti gli oggetti dalla cache di sessione, esiste il metodo Session.clear(). Per la cache di secondo livello, ci sono dei metodi definiti su SessionFactory e che hanno lo scopo di rimuovere lo stato di un'istanza dala cache, un'intera classe, una istanza di collezione o un intero ruolo di collezione.

Ti consigliamo anche