17 Hibernate3 Gestione efficiente

Hibernate
Modificare gli oggetti
efficientemente
Dott. Doria Mauro
[email protected]
[email protected]
Transitive persistence
La persistenza transitiva è una tecnica che consente di
propagare automaticamente la persistenza ad un sottografico di
oggetti transienti e/o detached.
Ad esempio, nell’associazione tra Item e Bid, è possibile legare il
ciclo di vita di un oggetto bid a quello dell’ item in cui si trova:
–
–
2
Un bid viene automaticamente reso persistente al momento in cui
rendiamo persistente l’item
Un bid viene cancellato al momento che cancelliamo un item.
Esistono diversi modi per realiazzare la transitive persistence,
anche se si basano tutti sugli stessi principi
[email protected]
Persistence by reachability
3
Uno strato di persistenza si dice che implementa la persistence
by reachability se una istanza diventa persistente ogni volta che
l’applicazione crea un reference a questa istanza da un’altra
istanza che è già persistente.
[email protected]
Persistence by reachability
Nell’esempio precedente:
–
–
–
4
Quando l’oggetto Computer diventa persistente, lo diventano pure
Desktop PCs e Monitor
Quando Computer diventa detached lo diventano anche Desktop
PCs e Monitor
Se uno di questi due oggetti perde il reference da Computer, diventa
automaticamente transient (e quindi rimosso).
Si presume una navigazione nel grafo degli oggetti unidirezionale
da padre a figlio.
Navigando nel grafo degli oggetti, si “incontrano” soltanto oggetti
persistenti; non sono mai possibili violazioni di integrità
referenziale.
[email protected]
Persistence by reachability
Per quanto possa sembrare una strategia affascinante, questa
tecnica non è applicabile così com’è (ne da Hibernate, ne da altri
ORM framework)
Sarebbe necessario una sorta di garbage collector per la
persistenza con un comportamento simile a quello della JVM
Il problema più grosso sta nel fatto che:
–
–
5
Gli oggetti sono condivisi (quindi ad un oggetto possono essere
collegati references da diversi altri oggetti)
Gli oggetti presenti nel persistent context non rappresentano tutti i
legami presenti nel DB
Per fare questo G.C. sarebbe necessario fare una costante
scansione del DB alla ricerca dei legami tra le righe
[email protected]
Persistenza a cascata
6
Hibernate applica un tipo più semplice di persistence by
reachability: la persistenza a cascata.
Hibernate propaga lo stato di un oggetto verso un altro
referenziato in base a quanto descritto nella proprietà “cascade”.
La persistenza a cascata è più flessibile e assegna maggiore
controllo allo sviluppatore.
Solo così è possibile sapere facilmente quando un oggetto diventa detached.
Non si attiva di default ed è disponibile in tutti i tipi di
associazione: (uno-a-uno, uno-a-molti, molti-a-molti) e per tutte le
collections (<set>, <bag>, <list> e <map>).
[email protected]
Persistenza a cascata
Tutti i valori per l’attributo cascade:
(Come save-update)
7
[email protected]
Persistenza a cascata
8
[email protected]
Persistenza a cascata
9
Il valore delete-orphan si può applicare soltanto alle collections
[email protected]
Persistenza a cascata
E’ possibile combinare tra loro diverse opzioni. Ecco alcuni
esempi:
<many-to-one name="parent"
column="PARENT_CATEGORY_ID"
class="Category"
cascade="save-update, persist, merge"/>
NOTA: il valore delete-orphan
non è incluso quando si
sceglie il valore all
<set name="bids" cascade="all, delete-orphan"
inverse="true">
<key column ="ITEM_ID"/>
<one-to-many class="Bid"/>
</set>
NOTA: non vi è nessuna relazione tra l’attributo cascade e quello inverse
10
[email protected]
Esempi di cascade
11
Un amministratore deve poter aggiungere, eliminare e modificare
tutte le categorie dei prodotti che vende.
Il mapping della classe Category:
<class name="Category" table="CATEGORY">
<property name="name" column="CATEGORY_NAME"/>
<many-to-one name="parentCategory"
class="Category"
column="PARENT_CATEGORY_ID"
cascade="none"/>
<set name="childCategories"
table="CATEGORY"
cascade="save-update"
inverse="true">
<key column="PARENT_CATEGORY_ID"/>
<one-to-many class="Category"/>
</set>
...
NOTA: si tratta di una
associazione
autoreferenziata di tipo
molti-a-uno
[email protected]
Esempi di cascade
aggiungere una nuova categoria
E’ possibile aggiungere una categoria in diversi modi:
Session session = sessionFactory.openSession();
L’oggetto computer
Transaction tx = session.beginTransaction();
diventa persistent
Category computer =
(Category) session.load(Category.class, computerId);
Category laptops = new Category("Laptops");
computer.getChildCategories().add(laptops);
laptops.setParentCategory(computer);
tx.commit();
session.close();
Completa la
bidirezionalità
dell’associazione
12
L’oggetto laptop
diventa persistente
sotto l’effetto del
cascade
[email protected]
Esempi di cascade
aggiungere una nuova categoria: modalità detached
La stessa operazione si può fare quando la categoria padre è
detached:
Queste operazioni in nessuna sessione:
Category computer = (Category) session.get(…) // Caricato in una precedente sessione
Category laptops = new Category("Laptops");
computer.getChildCategories().add(laptops);
laptops.setParentCategory(computer);
Queste operazioni in una seconda sessione:
Cosa succederebbe se
il cascade fosse attivato
per l’associazione
<many-to-one>?
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
// Rendere persistente la nuova categoria già linkata a suo padre
session.save(laptops);
tx.commit();
Hibernate valuta il link verso il padre e aggiunge
session.close();
13
NO CASCADE QUI!!!
il riferimento alla riga padre anche nel DB
indipendentemente da cascade e inverse
[email protected]
Esempi di cascade
aggiungere una nuova categoria: modalità detached
Vediamo un altro esempio per valutare la convenienza di questo
stile detached:
Queste operazioni in nessuna sessione:
Category computer = (Category) session.get(…) // Caricato in una precedente sessione
Category laptops = new Category("Laptops");
Metodi che creano
Category laptopUltraPortable = new Category("Ultra-Portable");
l’associazione in tutte
Category laptopTabletPCs = new Category("Tablet PCs");
laptops.addChildCategory(laptopUltraPortable);
e due i sensi
laptops.addChildCategory(laptopTabletPCs);
computer.addChildCategory(laptops);
14
Queste operazioni in una seconda sessione:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
// Rendere persistente tutte le categorie
session.save(laptops);
tx.commit();
session.close();
Hibernate valuta il link verso il padre e
aggiunge il riferimento alla riga padre
anche nel DB e poi salva laptop e,a
cascata, tutte le sue sottocategorie
[email protected]
Esempi di cascade
aggiungere una nuova categoria: modalità detached
Vediamo ancora un altro esempio per valutare la capacità di
updatare (oltre che salvare) a cascata:
Queste, dopo aver chiuso la sessione dell’esempio precedente:
….. //continua dall’ esempio precedente
laptops.setName("Laptop Computers"); // Modifica
laptopUltraPortable.setName("Ultra-Portable Notebooks"); // Mdifica
laptopTabletPCs.setName("Tablet Computers"); // Modifica
Category laptopBags = new Category("Laptop Bags");
laptops.addChildCategory(laptopBags); // Aggiunge
15
Metodo che crea
l’associazione in tutte
e due i sensi
Queste operazioni in una seconda sessione:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
// Modifica 3 categorie e ne inserisce una nuova
session.saveOrUpdate(laptops);
tx.commit();
session.close();
Hibernate valuta in autonomia
cosa deve essere updatato e
cosa invece inserito
[email protected]
SaveOrUpdate
saveOrUpdate() ha il seguente effetto:
Tutto questo deve essere reso persistente non importa se nuovo o vecchio
16
E’ una buona idea utilizzare sempre il metodo saveOrUpdate() anche al
posto dei singoli save() o update().
Con saveOrUpdate(), Hibernate determina lo stato dell’oggetto (per
decidere quale operazione fare delle due) in maniera molto più semplice
rispetto ai metodi save() e update() e comunque senza accedere al DB.
Vi è solo un caso (raro) in cui Hibernate effettua una select per
determinare questo: quando l’oggetto utilizza un identificatore composito
con campi normali e non si conosce la sua versione (versioning).
[email protected]
Delete
17
Normalmente la responsabilità della cancellazione degli oggetti ricade
sullo sviluppatore che deve controllare se vi sono references all’oggetto
prima di cancellarlo.
Non è possibile cancellare oggetti a cui sono associati altri oggetti.
Abilitando il delete-orphan, quando si rimuove un oggetto dalla collection
in cui si trova e questi non ha altri references associati, Hibernate
provvedere alla sua rimozione automatica.
E’ una proprietà molto interessante che evita allo sviluppatore di
compiere la doppia azione di eliminare l’oggetto dalla collezione e dallo
stato di persistenza
E’ configurabile soltanto all’interno dei tag ti tipo collections
[email protected]
Delete-orphan
18
Con delete-orphan disattivato:
Item anItem = ... // Caricato in una sessione precedente
Necessarie in quanto la
anItem.getBids().remove(aBid);
semplice rimozione dalla
anItem.getBids().remove(anotherBid);
Session session = sessionFactory.openSession();
collection non è una
Transaction tx = session.beginTransaction();
operazione di persistenza.
session.delete(aBid);
session.delete(anotherBid);
session.saveOrUpdate(anItem);
tx.commit();
session.close();
Item anItem = ... // Caricata in una precedente sessione
anItem.getBids().remove(aBid);
Con delete-orphan attivato:
anItem.getBids().remove(anotherBid);
Session session = sessionFactory.openSession();
Update dell’oggetto prima di
Transaction tx = session.beginTransaction();
chiudere la sessione perché
session.saveOrUpdate(anItem);
anItem è detached; se fosse
tx.commit();
persistent, l’update sarebbe inutile
session.close();
[email protected]
bulk and batch operations
19
Quando si manipola una grande mole di dati, il mondo dei DB
mette a disposizione la possibilità di scrivere delle funzioni
direttamente dulla basedati dette stored procedure
Vedremo come Hibernate mette a disposizione degli strumenti
per gestire le stored procedure e in generale il problema della
massa dei dati.
Hibernate consente di modificare gli oggetti direttamente nel DB
attraverso il liguaggio HQL
[email protected]
Updatare oggetti nel DB
In Hibernate è possibile updatare gli oggetti direttamente nel DB
Queste operazioni sono speciali perché baipassano
completamente il persistence context, quindi non hanno nessun
effetto sugli oggetti in memoria!
Vi sono due modi possibili per procedere:
–
–
20
Operare in un nuovo contesto di persistenza
Utilizzare il metodo refresh()
[email protected]
Updatare oggetti nel DB
Grazie al linguaggio HQL di Hibernate è possibile lanciare le
query che operano direttamente nel DB
Ecco un esempio:
Query q = session.createQuery("update Item i set i.isActive = :isActive");
q.setBoolean("isActive", true);
Numero di oggetti modificati
int updatedItems = q.executeUpdate();
dall’istruzione update
21
La modifica avviene direttamente nel DB.
Le query HQL sono molto simili alle istruzioni SQL ma vanno
specificate le classi al posto delle tabelle e le proprietà al posto
delle colonne.
[email protected]
Updatare oggetti nel DB
Con HQL le update possono essere anche polimorfiche o
modificare la versione degli oggetti (versioning).
Query q = session.createQuery(
"update CreditCard set stolenOn <= :now where type = 'Visa‘" );
q.setTimestamp("now", new Date() );
int updatedCreditCards = q.executeUpdate();
Hibernate conosce la
ripartizione dei dati tra le
tabelle che mappano la
generalizzazione
Query q = session.createQuery("update versioned Item i set i.isActive = :isActive");
q.setBoolean("isActive", true);
int updatedItems = q.executeUpdate();
22
NOTA: le query HQL non si possono fare join, si può coinvolgere una sola
classe alla volta, gli alias sono opzionale e nella were e possibile scrivere
delle sottoquery
[email protected]
Inserire oggetti nel DB
23
Sempre nell’ottica di voler manipolare una grande mole di dati
direttamente nel DB, vediamo un esempio di inserimento.
Si vuole scrivere una procedura che marchi le carte di credito
come rubate e che sposti le informazioni (dati carta, proprietario)
in un’altra tabella)
L’idea è quella di usa una istruzione HQL di insert senza passare
dal persistent context.
Il primo passo è quello di creare una classe apposita da mappare
con il DB e il secondo di scrivere l’istruzione HQL.
[email protected]
Inserire oggetti nel DB
Query q = session.createQuery
("insert into StolenCreditCard (type, number, expMonth, expYear, ownerFirstname,
onwerLastname, ownerLogin, ownerEmailAddress, ownerHomeAddress)
select c.type, c.number, c.expMonth, c.expYear, u.firstname, u.lastname, u.username,
u.email, u.homeAddress
from CreditCard c join c.user u where c.stolenOn is not null“ );
int createdObjects = q.executeUpdate();
24
[email protected]
Batch query
E’ possibile eseguire una query riportando i dati in memoria in gruppi e
non tutti insieme. Ecco un esempio:
Session session = sessionFactory.openSession();
Creazione del cursore
Transaction tx = session.beginTransaction();
ScrollableResults itemCursor = session.createQuery("from Item").scroll();
int count=0;
while ( itemCursor.next() ) {
Spostamento del cursore
Item item = (Item) itemCursor.get(0);
Elemento a partire dalla
modifyItem(item);
posizione corrente
if ( ++count % 100 == 0 ) {
session.flush();
session.clear();
Ogni 100 elementi si
}
ripulisce il persistence
}
context
tx.commit();
session.close();
25
Per avere le migliori prestazioni:
hibernate.jdbc.batch_size = numero
[email protected]
Batch insert
E’ possibile anche inserire e modificare i modalità betch; lo scopo
è quello di fare il flushing dei dati e svuotare il persistent context
durante l’operazione
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
Item item = new Item(...);
session.save(item);
if ( i % 100 == 0 ) {
session.flush();
session.clear();
}
}
tx.commit();
session.close();
26
NOTA: sia per le query che
per l’insert, è opportuno
disabilitare la cache di
secondo livello per le classi
persistenti.
Se si utilizza il generatore di
identificativo “identity”, la
batch insert potrebbe non
essere supportata
[email protected]
Data filtering
In alcuni casi, per motivi di sicurezza, si vuole mettere a
disposizione soltanto una parte dei dati presenti sul DB.
Non sempre è possibile scrivere delle query appropriate che
dinamicamente selezionano i dati;
cosa succede se scrivo unaCategoria.getItems()? Ottengo tutti gli
item di quella categoria senza possibilità di evitarne alcuni
27
Una soluzione potrebbero essere le viste (view) sul DB, ma
queste non sono nello standard di SQL e molti DB non le
supportanto
Hibernate consente di filtrare i dati dinamicamente attraverso il
meccanismo dei filter
[email protected]
Dynamic data filter
Prima di tutto il filtro va dichiarato nel file XML di configurazione e si
assegna un nome univoco (la dichiarazione è globale):
<filter-def name="limitItemsByUserRank">
<filter-param name="currentUserRank" type="int"/>
</filter-def>
Nome del filtro
A questo punto è possibile aggiungere il filtro ad una classe:
<class name="Item" table="ITEM">
Una condizione SQL di
...
filtraggio degli oggetti Item
<filter name="limitItemsByUserRank“
condition=":currentUserRank >= (select u.RANK from USER u
where u.USER_ID = SELLER_ID)"/>
</class>
28
Infine, si abilita il filtro per una sessione e si passano gli eventuali
parametri
Filter filter = session.enableFilter("limitItemsByUserRank");
filter.setParameter("currentUserRank", loggedInUser.getRanking());
[email protected]
Dynamic data filter
E’ consentito un passaggio di parameri per la condizione del
filtro.
Il metodo enableFilter() abilita il filtro e torna un oggeto Filter su
cui è possibile invorare diversi metodi. I più utili sono:
–
–
–
–
29
setParameter(): setta un parametro nella condizione del filtro
getFilterDefinition(): lista dei parametri del filtro
validate(): Hibernate torna una exception se il parametro non è
settato.
setParameterList(): setta una lista di argomenti
[email protected]
Dynamic data filter
A questo punto, tutte le query effettuate sulla sessione filtrata
otterranno dei dati soggetti all’azione del filtro.
List<Item> filteredItems = session.createQuery("from Item").list(); List<Item>
filteredItems = session.createCriteria(Item.class).list();
Solo due metodi non sono soggetti al filtro:
–
–
30
Recupero attraverso l’identificatore
Navigazione tramite il metodo set (es: unaCategoria().getItem())
Per evitare questo ultimo aspetto, si può filtrare una collection
[email protected]
Dynamic data filter
31
I campi di applicazione dei filtri sono molti; per citarne alcuni:
–
Sicurezza: per motivi di sicurezza molto spesso è utile limitare
l’accesso ad una parte dei dati e non a tutto il DB
–
Dati spaziali: si potrebbe limitare l’accesso soltanto a quei dati che
sono relativi ad una certa regione spaziale
–
Dati temporali: si potrebbe mettere a disposizione dati in un certo
intervallo di tempo (ad esempio l’ultima settimana)
[email protected]
Lazy strategy
32
La lazy strategy è la capacità di Hibernate di gestire
efficientemente gli oggetti in memoria rimandando (o ritardando)
le operazioni a quando sono strettamente necessario, in modo
del tutto trasparente all’applicazione.
Hibernate applica di default, la lazy strategy per tutti i suoi oggetti
identity e le collections.
La lazy strategy si basa uno dei pattern fondamentali della GoF:
proxy.
Il comportamento di base attuato da Hibernate per il lazy è:
caricare in memoria soltanto gli oggetti che sono stati richiesti
[email protected]
Lazy loading
Vediamo la logica lazy alla base del recupero di un oggetto dal DB.
Consideriamo il caricamento in base all’ identificatore:
Item item = (Item) session.load(Item.class, new Long(123));
Per l’applicazione, il risultato sarà il caricamento in memoria
dell’item“123”, e l’oggetto è nello stato persistent.
In realtà, Hibernate proxa l’oggetto
l’oggetto proxato
reale e passa all’applicazione
Persistence Context
NO
33
SI
item
Proxy
123
123
xxx
yyy
zzz
[email protected]
Lazy loading
Ogni volta che viene richiesto ad Hibernate il caricamento di un
oggetto, esso controlla se può generare un oggetto proxy a runtime per evitare di accedere al DB.
Il proxy ritarda (lazy) il momento dell’accesso ai dati fino a
quando non viene richiesto l’accesso alle proprietà dell’oggetto.
34
Item item = (Item) session.load(Item.class, new Long(123));
item.getId();
item.getDescription(); // Inizializzazione the proxy
L’identificatore è già
presente nel proxy e quindi
non scatta l’accesso al DB
Ecco un altro esempio:
La descrizione no e quindi
scatta l’accesso al DB
Item item = (Item) session.load(Item.class, new Long(123));
User user = (User) session.load(User.class, new Long(1234));
Bid newBid = new Bid("99.99");
newBid.setItem(item);
newBid.setBidder(user);
session.save(newBid);
C’è solo uno spostamento
di puntatori e non è
necessarie select sul DB
[email protected]
Lazy loading
IL lazy loading è estremamento ottimizzato. Al momento del
caricamento dell’oggetto, si ottiene il proxy quando è possibile! Vediamo
un esempio:
L’oggetto notebook è
costruito e salvato in una
sessione
35
…
Category notebook = new Category(…..);
session2.save(notebook);
tx.commit();
L’oggetto notebook diventa
session2.close();
persistent nella nuova
//creazione di una nuova sessione con persistence context vuoto.
sessione
Session session3 = sessionFactory.openSession();
Transaction tx3 = session3.beginTransaction();
Il persistent
session3.update(notebook); //senza questa istruzione si ha il proxy
context già
//recupero l’oggetto notebook salvato in precedenza
possiede
Category altroNotebook= (Category)session3.load(Category.class, new Long(3));
l’oggetto ma di
System.out.println("CLASS: " + altroNotebook.getClass()); //Category
tipo Category;
System.out.println("CLASS: " + notebook.getClass()); //Category
viene restituito
System.out.println (" IDENTICI: " + altroNotebook == notebook); //true
questo e non un
proxy
[email protected]
Lazy loading
Il metodo get(), al contrario di load() non torna mai un proxy.
Se si vuole la classe reale dell’oggetto proxato si può utilizzare
un metodo statico di Hibernate:
HibernateProxyHelper.getClassWithoutInitializingProxy(obj);
36
Una volta che il proxy è “costretto” ad accedere al DB, questo si
dice inizializzato. In questo caso vengono caricati i dati dal DB
ma sempre in una ottica lazy.
Hibernate, quando carica un oggetto, cercherà di minimizzare
l’accesso al DB e di minimizzare l’occupazione di spazio di
memoria per quell’oggetto
[email protected]
Lazy loading
Quando un oggetto Item, ad esempio, è inizializzato, si presenta
così:
Hibernate costruirà una serie di
proxy, uno per ogni associazione
*-a-uno presenti nella classe Item
Hibernate costruirà una sorta di
segnaposto per tutte le collezioni
presenti nella classe Item (si parla
di segnaposto perché Hibernate
wrappa le collection originali con
sue collections interne).
37
Le proprietà value e i componenti
vengono caricati subito (no lazy)
[email protected]
Lazy loading
Le collections vengono inizializzate quando si comincia a
navigare all’interno o quando si chiamano metodi di supporto
come size() o contains().
Per le collections, Hibernate adotta un extra lazy:
<class name="Item" table="ITEM">
...
<set name="bids"
lazy="extra"
….
38
Con lazy=extra, quando vengono effettuate
chiamate ai metodi di supporto, Hibernate
non inizializza la collection ma, per ottenere
le informazioni, effettua query diretta al DB
[email protected]
Eager strategy
39
Cosa succede se si tenta di navigare un oggetto proxato non
inizializzato o con elementi non inizializzati fuori da una sessione
(l’oggetto detached)?
Si ottiene una LazyInitializationException! Hibernate non è più in
grado di recuperare gli oggetti associati perché il persistence
context è ormai chiuso.
Naturalmente, non sempre il proxy è desiderabile soprattutto per
le collections. Hibernate ha una seconda strategia di caricamento
dei dati: eager strategy.
La eager strategy carica subito l’oggetto in ogni sua parte!
[email protected]
Eager strategy
40
Non si vuole disabilitare la gestione proxy per tutte le classi;
questo sarebbe disastroso per le performance dell’applicazioni;
Con lazy=false, si disabilita la lazy strategy per una certa classe.
Ecco un esempio tra Item e User:
<class name="Item" table="ITEM">
...
<many-to-one name="seller“ lazy="false" .../>
<set name="bids"
lazy="false"
inverse="true">
<key column="ITEM_ID"/>
<one-to-many class="Bid"/>
</set>
...
</class>
[email protected]
Domande?
Lazy load
Eager strategy
Batch insert
reachability
cascade
Data filter
41
Batch query
proxy