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
© Copyright 2024 Paperzz