Cominciamo ad analizzare lo strato di persistenza, ossia quello che permette di salvare in maniera permanente i dati della nostra applicazione. Hibernate è senza dubbio il framework open source di maggior successo per l‘implementazione dello strato di persistenza secondo la metodologia ORM. Vediamo come utilizzarlo nel nostro sistema.
Introduzione
Si è già parlato, nel corso della serie, dell’implementazione dello strato di persistenza di una applicazione. Non ci dilungheremo quindi su concetti già noti pur facendo un piccolo richiamo ad alcuni aspetti fondamentali per comodità del lettore.
La persistenza dei dati è uno degli aspetti fondamentali da tenere in conto nella realizzazione di un’applicazione in quanto è ovvio che praticamente qualsiasi sistema necessita in qualche modo di memorizzare in maniera permanente i dati sui quali deve operare. Lo strato di persistenza di un’applicazione non fa altro che occuparsi di questo basilare compito. Le modalità per rendere persistenti i dati sono in teoria molteplici ma, nella pratica, quando si parla di persistenza in un’applicazione ci si riferisce quasi sempre a dati memorizzati in un database relazionale.
Che il modello relazionale sia quello universalmente riconosciuto come il migliore per la memorizzazione dei dati applicativi è un dato di fatto; diverse però sono le modalità con le quali un’applicazione può gestire l’interazione con il database e più genericamente l’implementazione della persistenza.
Una delle tecniche ormai consolidate è il cosiddetto ORM, Object Relational Mapping. Si è già accennato nel corso dei precedenti articoli al fatto che il modello relazionale e quello ad oggetti sono piuttosto differenti e utilizzano modalità diverse per la rappresentazione delle entità e delle loro associazioni. Nei testi che affrontano il problema si parla di paradigm mismatch ovvero di una “mancata corrispondenza” tra i due paradigmi che necessita da parte dell’architetto e/o dello sviluppatore di un intervento di adattamento.
I tool ORM consentono di rendere persistenti oggetti in tabelle di database relazionali in maniera automatica e trasparente per lo sviluppatore che continuerà ad avere a che fare nel suo codice solo con le entità del suo modello a oggetti. È il tool ORM che si occupa di gestire le problematiche di adattamento tra i due paradigmi purche’ gli vengano forniti i metadati necessari a realizzare il mapping tra il modello ad oggetti e quello relazionale.
Hibernate è il framework open source in assoluto più utilizzato e di successo che fornisce tutte le funzionalità di un ORM completo ed affidabile.
Breve introduzione a Hibernate
Hibernate come ogni framework si compone di alcuni moduli elementari.
Hibernate Core è l’insieme delle librerie di base che comprendono tutte le funzionalità core comprese quelle di mapping mediante descrizione di metadati in formato XML. L’Hibernate Core può essere utilizzato così com’è in qualsiasi applicazione e non richiede nessun particolare runtime.
Hibernate Annotations è il componente di Hibernate che aggiunge la possibilità di utilizzare le annotazioni, caratteristica introdotta nel linguaggio Java a partire della versione 5.0 del JDK, per la rappresentazione dei metadati, a differenza di quanto già in uso nella libreria core. Hibernate Annotations comprende un insieme di annotazioni di base che implementano lo standard JPA più un insieme di estensioni per funzioni particolari.
È immediato osservare come utilizzando le Hibernate Annotations la definizione dei metadati per il mapping object/relational diventa veramente simile a quanto già visto nell’implementazione dello strato si persistenza con gli EJB 3.0.
Hibernate Entity Manager è invece la libreria che fornisce un’implementazione in base allo standard JPA delle interfacce riguardanti il ciclo di vita degli oggetti e l’esecuzione delle query.
Senza entrare nei dettagli della tecnologia, per i quali rimandiamo ai riferimenti bibliografici, è bene spendere appena due parole per giustificare la scelta dell’utilizzo dell’approccio ORM, EJB 3.0 o Hibernate che sia la tecnologia usata, per implementare lo strato di persistenza di un’applicazione.
Come già detto la metodologia ORM non è l’unica possibile, anzi l’interfacciamento con i database relazionali utilizzando le funzionalità native della JDBC API è stato per anni l’approccio maggiormente utilizzato. Molto spesso, in progetti complessi, il team di progetto arrivava a realizzare, in maniera più o meno conscia, un vero e proprio framework proprietario per l’accesso ai dati. Ciò significava un notevole sforzo di sviluppo e un impiego di tempo e risorse per la scrittura di codice per la maggior parte ripetitivo e senza alcun valore aggiunto per la propria applicazione. I risultati di questo approccio non sono quasi mai un granche’, anche perche’ scrivere codice di accesso ai dati performante e portabile tra diversi database non è cosa da poco e non è compito che si possa realizzare in maniera “artigianale”.
Sicuramente scrivere codice di accesso ai dati utilizzando le librerie di un ORM come Hibernate è un compito assai più semplice che codificare “a mano” tutte le istruzioni SQL necessarie. Ciò non significa però che lo sviluppatore possa permettersi di non conoscere il linguaggio SQL. Anche se l’ORM lo svincola dall’occuparsi di questo compito gravoso, senza una buona conoscenza del linguaggio SQL sarà difficile comprendere e ottimizzare le operazioni effettuate sul database dal nostro ORM.
Utilizzare un ORM consente però di estrarre dal codice della nostra applicazione tutte le miriadi di operazioni ripetitive e senza valore aggiunto tipiche dell’accesso ai dati e quindi rende il codice più mantenibile ed incrementa la produttività dello sviluppo.
Oltre a ciò conferisce allo strato di persistenza della nostra applicazione una reale portabilità tra database differenti che è molto difficile realizzare con le tecniche tradizionali.
Il modello delle entità e Hibernate
Nel corso della serie è già stato presentato il percorso che porta a definire il modello delle entità del sistema, il cosiddetto domain object model, punto di partenza per la definizione dello strato di persistenza. Dal percorso logico effettuato si è giunti al modello raffigurato in Figura 1.
Figura 1 – Il modello delle entità
Nel modello sono rappresentate le principali entità del nostro sistema e le relazioni che intercorrono tra di esse. L’approccio utilizzato è quindi quello di tipo top-down sicuramente più vicino al modo di ragionare di un analista object-oriented in base al quale si parte dal modello delle entità per definire la struttura del database relazionale. È bene però considerare che in molte situazioni reali è necessario procedere con un approccio inverso, di tipo bottom-up quando si ha un database già esistente.
L’approccio ORM rimane valido in entrambi i casi, anche se noi stiamo seguendo il primo approccio. Hibernate fornisce tool automatici per effettuare sia un forward-engineering dagli oggetti allo schema del database che un reverse-engineering dallo schema del database agli oggetti.
Un esempio pratico
Come primo esempio di utilizzo di Hibernate prendiamo una relazione già analizzata in precedenza, la relazione Apiario-Famiglia. Si tratta come già visto di una classica relazione bidirezionale uno a molti.
Gli oggetti dello strato di persistenza per Hibernate non sono altro che comuni POJO (Plain Old Java Objects) ossia oggetti che seguono le regole dei JavaBeans, che quindi non devono implementare interfacce o classi del framework ma sono comuni classi riutilizzabili anche in altri contesti. Inoltre Hibernate non modifica la semantica del linguaggio Java per cui le associazioni tra entità in Java si modellano come si farebbe con una qualsiasi classi indipendentemente dal fatto che essa venga poi resa persistente con Hibernate. Questa sono alcune delle caratteristiche che hanno reso Hibernate preferibile per lungo tempo agli EJB di tipo Entity, la cui specifica fino alla versione 2.1 era estremamente complessa. Il modello di Hibernate è stato fatto proprio nella specifica EJB 3.0 che ha colmato questo evidente gap.
Le due classi che renderemo persistenti con Hibernate avranno quindi la seguente forma a meno dei metodi get e set delle proprietà (accessors):
public class Famiglia { private Apiario apiario; private Integer id; private Integer position; private String notes; public Famiglia() { } public Famiglia(Apiario a) { this.apiario = a; } ... ... } public class Apiario { /** * @associates <{it.mokabyte.hibernate.Famiglia}> * @aggregation composite */ private Collection famiglie; private Integer id; private String location; private String name; private String description; public Apiario() { } ... ... }
Il lettore attento noterà come non vi siano differenze rispetto a quanto visto nell’implementazione con gli EJB 3.0. A questo punto, definite le classi di persistenza è necessario effettuare il mapping con le tabelle del nostro database relazionale. Useremo in questo esempio la metodologia classica di Hibernate che prevede la descrizione dei metadati in formato XML. Forniremo in seguito anche un esempio di utilizzo delle annotazioni per la rappresentazione dei metadati.
I file XML di mapping hanno una notazione piuttosto ricca e complessa, per approfondire la quale si consiglia di consultare la documentazione di riferimento del framework [1]. Di seguito sono riportati i due file di mapping per la relazione presa ad esempio. Come naming convention standard i file XML di mapping hanno il nome dell’entità a cui si riferiscono più il suffisso hbm seguito dall’estensione del file. Avremo quindi:
Apiario.hbm.xml
"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
Famiglia.hbm.xml
"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
È interessante notare come si sia usato per le chiavi delle due tabelle un campo auto-incrementante il cui valore è acquisito in base alle caratteristiche del singolo database, come specificato dall’attributo native.
Nella classe Apiario inoltre si è configurata la relazione di tipo uno a molti con la classe Famiglia mentre la relazione inversa è stata impostata per la classe Famiglia.
Tutta la configurazione necessaria ad Hibernate va posta in un ulteriore file XML denominato hibernate.cfg.xml posto nel classpath dell’applicazione. Nel file sono configurati tutti i parametri necessari, quali quelli per la connessione al DB, e il riferimento ai file di mapping delle entità. Di seguito un esempio del file con i vari campi da personalizzare :
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> database utrilizzato true oracledriver del database url di connessione username password
Definite le classi che rappresentano le entità e le loro relazioni, configurato il mapping con il database relazionale e configurata la sessione Hibernate, abbiamo tutto ciò che ci serve per rendere persistenti nel database le nostre entità utilizzando le librerie del framework. Da ora in poi non scriveremo più tedioso codice SQL, ma eseguiremo operazioni di salvataggio, acquisizione e aggiornamento delle nostre entità che il tool ORM si occuperà di tradurre in opportune istruzioni SQL per il database da noi utilizzato.
Un esempio di salvataggio delle nostre entità Apiario e Famiglia nel database assume quindi la seguente forma nel codice:
//acquisizione factory delle sessioni Hibernate SessionFactory factory = HibernateUtil.getSessionFactory(); //aperture di una sessione Hibernate Session session = factory.openSession(); //inzio della tramnsazione session.beginTransaction(); //istanziamento classi e setting proprietà Apiario apiario = new Apiario(); apiario.setDescription("Primo"); apiario.setLocation("Perugia"); apiario.setName("Apiario1"); Famiglia famiglia = new Famiglia(); famiglia.setNotes("Famiglia1"); famiglia.setPosition(new Integer(1)); famiglia.setApiario(apiario); Set famiglie = new HashSet(); famiglie.add(famiglia); apiario.setFamiglie(famiglie); //salvataggio nel database delle entità session.save(apiario); //chiusura sessione session.getTransaction().commit();
Tralasciando per il momento la descrizione dettagliata di ogni singola riga di codice, che esula dallo scopo di questo articolo, la cosa importante che si intende sottolineare è come nel codice non vi sia alcuna traccia di istruzioni SQL o della struttura relazionale. Le entità vengono istanziate come normali classi Java delle quali vengono settate le varie proprietà. L’operazione di save dice al framework di occuparsi di rendere persistenti le due entità nel database.
A seguito di questa operazione, il tool in maniera trasparente genererà le seguenti istruzioni:
- select hibernate_sequence.nextval from dual
- select hibernate_sequence.nextval from dual
- insert into APIARY (name, location, description, id) values (?, ?, ?, ?)
- insert into FAMILY (notes, position, apiary_id, id) values (?, ?, ?, ?)
ovvero acquisirà i valori delle chiavi per le due tabelle in base ai meccanismi del database sottostante (Oracle nell’esempio) ed eseguirà le istruzioni di inserimento allo scopo di rendere persistenti le entità. Tutto ciò in base ai metadati che forniscono tutte le informazioni necessarie per colmare il paradigm mismatch ormai ben noto.
Conclusioni
Con questo articolo si è introdotto Hibernate e si è iniziato a descrivere come sia possibile utilizzarlo per implementare lo strato di persistenza. Per iniziare si è considerata una relazione di tipo bidirezionale uno a molti. Lo scopo dell’articolo era mostrare le nozioni di base per l’utilizzo di Hibernate prendendo come esempio alcune delle entità del nostro sistema. Nell’articolo del mese prossimo approfondiremo gli altri tipi di relazione e vedremo come essi possano essere modellati con gli strumenti di Hibernate. Forniremo inoltre anche il codice completo di esempio in modo che il lettore possa cimentarsi con l’utilizzo di questo potentissimo framework.
Riferimenti
[1] Hibernate Reference Documentation 3.3.1,Copyright © 2004 Red Hat Middleware, LLC.
[2] Christian Bauer – Gavin King, “Java Persistence with Hibernate”, Novembre 2006