Hibernate: Caricare migliaia di righe

Hibernate: getResultList() vs ScrollableResults G.Morreale
Introduzione:
Se sul db ci sono migliaia di righe in una tabella e intendiamo caricare i corrispettivi entity(magari per effettuare un batch update), potremmo trovarci di fronte a delle scelte al fine di ottimizzare l'uso della memoria.
Le soluzioni adottate sono due.
  1. Usare un JTA EntityManager, richiamare una namedQuery e ottenere la collection dei risultati con getResultList() (richiamato sull'oggetto Query)
  2. Usare l'oggetto session di hibernate e appoggiarsi all'oggetto ScrollableResults per ottenere i risultati
Esempio
Supponiamo di avere il solito Entity Esempiotable(composto da due campi: int:id e string:testo)
Supponiamo che ci siano sul db 500.000 righe (per le quali hibernate genera 500.000 Entity)
La namedQuery utilizzata è la seguente:
@NamedQuery(name = "Esempiotable.ALL", query = "SELECT e FROM Esempiotable e")
Codice Soluzione 1:
public void listAll()
{
List<Esempiotable> list = em.createNamedQuery("Esempiotable.ALL").getResultList();
list.get(0);
}
Codice Soluzione 2:
public void scrollableResultTest()
{
Configuration configuration = new Configuration();
SessionFactory sf = configuration.buildSessionFactory();
Session ses = sf.openSession();
ScrollableResults res = (ScrollableResults) ses.getNamedQuery("Esempiotable.ALL").scroll();
res.next();
}
Quindi i metodi al confronto sono "getResultList" e "scroll".
Leggendo le javadoc non ci sono molti dettagli circa la funzionalità di tali metodi, il primo carica i risultati della query nella struttura dati List, il secondo invece permette di ottenere un iterator in grado di estrarre i risultati spostandosi attraverso dei cursori all'interno del result iterator stesso.
Quindi forse il metodo migliore per coglierne effettivamente le differenze è quello di lanciare il profiler e analizzare il consumo di memoria:
Nel caso di getResultList vengono allocati 2.839.152 B (immagine sopra)
Nel caso invece di scroll vengono allocati 12.840 B (immagine sotto)
Quindi è facile notare come il consumo di memoria nel caso di inizializzazione della List e dello ScrollableResult sia nettamente minore nel secondo caso.
nota:
Il supporto della scrollabilità dei risultati dipende dal driver jdbc sottostante.
E le performance?
Diamo un occhio all'immagine sopra, il metodo listAll() rappresenta la soluzione 1, mentre scrollableResultTest() rappresenta la soluzione 2.
Come è facile notare l'inizializzazione della seconda struttura dati è molto più veloce rispetto alla prima.
nota:
(il divario resta, anzi aumenta anche nel caso di invocazioni multiple dei metodi)
Però..
Se una volta ottenute le corrispettive strutture dati si vuole fare un ciclo al fine di operare sugli entity via via estratti dalla struttura i risultati si ribaltano:
(metodo eseguito su 500.000 rows)
Infatti estrarre i risultati dalla scrollableList richiede una quantità di memoria minore, nonchè un inizializzazione più veloce, ma in fase di estrazione risulta essere più lento.
Conclusione
Come in ogni caso la scelta della soluzione dipende dal contesto.
Se si intende risparmiare sulla memoria e penalizzare leggermente le performance si può optare per la soluzione "scrollable", altrimenti se non si hanno problemi di memoria (macchine con molta molta ram, su cui è configurato una max heap size) notevole allora si può caricare tutto in memoria e operare sui vari dati già estratti.
Tutto dipende..

1 comment:

Fil said...

Ciao, ho letto questo tuo post e l'ho trovato molto utile. Volevo farti una domanda. E' possibile usare lo ScrollableResults abbinando alla query un transformer?
In pratica vorrei che fosse hibernate a processare ogni riga, caricando le colonne ottenute in un bean che non è un entity.
Col list() e settando l'apposito transformer su di un query riuscivo a farlo ma con lo scroll() mi sembra che non ci sia modo. Conosci per caso qualche alternativa? GRazie mille
Fil