Con il termine Object relational Mapping (ORM) si intende una tecnica di programmazione utilizzata per l’integrazione di sistemi software che si basano sul paradigma della programmazione ad oggetti con sistemi per la gestione di basi dati relazionali (RDBMS). In altri termini offrono un modello per il mapping (standard secondo la specifica JPA) tra oggetti di un dominio Object Oriented (OO) e entità di un dominio relazionale (tipicamente tabelle e relazioni). Esistono diversi framework open source che supportano il paradigma ORM, tra i quali iBatis (confluito poi in myBatis) ed il più famoso Hibernate.
In questo articolo muoviamo i primi passi con Hibernate e vediamo come creare una semplice applicazione java che lo utilizza.
Setup dell’Applicazione
Per la configurazione dell’applicazione di esempio utilizziamo maven e creiamo un nuovo progetto java senza specificare l’archetype. Quindi apriamo il file pom.xml
ed inseriamo le dipendenze necessaire, che sono Hibernate (ovviamente) e il connettore per il database utilizzato, che nel nostro caso sarà MySql.
1 2 3 4 5 6 7 8 9 10 11 |
<dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.2.4.Final</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> |
Proseguiamo creiamo la tabella UTENTI
che sarà utilizzata nell’esempio. Si noti che per personale convenzione utilizzo il plurale per i nomi delle tabelle ed il singolare per i nomi degli oggetti associati. Lo script di generazione della tabella sulla schema javaboss è il seguente:
1 2 3 4 5 6 7 8 9 |
USE javaboss; DROP TABLE IF EXISTS UTENTI; CREATE TABLE UTENTI ( USERNAME VARCHAR(10) PRIMARY KEY, NOME VARCHAR(15), COGNOME VARCHAR(15), PASSWORD VARCHAR(8), EMAIL VARCHAR(20) ); |
Configurazione di Hibernate
Per il corretto utilizzo di Hibernate è necessario fornire alcune informazioni in anticipo, come ad esempio: il database cui connettersi (e le relative credenziale), ma anche il mapping da operare tra le tabelle e gli oggetti del dominio.
Tali informazioni possono essere definite o attraverso un file di properties, denominato hibernate.properties
, o mediante un file XML, denominato hibernate.cfg.xml
. In entrambe i casi il file deve essere disponibile le classpath dell’applicazione, quindi, essendo il nostro un progetto maven, possiamo inserirli nella cartella src/main/resources
.
Nell’esempio utilizzeremo il file XML, che dovrà essere conforme al DTD http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd (l’elenco di tutti i DTD utilizzati dal framework è disponibile al link http://hibernate.org/dtd/)
. Il file è il seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- Database connection settings --> <property name="hibernate.connection.driver_class"> com.mysql.jdbc.Driver </property> <property name="hibernate.connection.url"> jdbc:mysql://localhost/javaboss </property> <property name="connection.username">root</property> <property name="connection.password">root</property> <!-- SQL dialect --> <property name="dialect"> org.hibernate.dialect.MySQL5InnoDBDialect </property> <!-- JDBC connection pool (use C3P0) --> <property name="c3p0.min_size">5</property> <property name="c3p0.max_size">20</property> <property name="c3p0.timeout">300</property> <property name="c3p0.max_statement">50</property> <!-- Show and print nice SQL on stdout --> <property name="show_sql">true</property> <property name="format_sql">true</property> <!-- List of XML mapping files --> <mapping resource="Utente.hbm.xml" /> </session-factory> </hibernate-configuration> |
Ispezionando il file troviamo una serie di proprietà di configurazione ed (in fondo) il tag <mapping>
, di cui parleremo in seguito.
Connessione
Alcune proprietà sono direttamente riconducibili alle informazioni di configurazione presenti nella specifica JDBC, che è utilizzata da Hibernate, descritta nel post Accesso ai Database in Java con JDBC. Tra queste troviamo la url di connessione al db hibernate.connection.url
, e le credenziali di accesso connection.username
e connection.password
. Il driver di connessione allo specifico RDBMS è invece definita dalla proprietà hibernate.connection.driver_class
.
Ciascun RDBMS supporta il linguaggio SQL standard ma aggiunge a questo alcune caratteristiche distintive. La specifica del dialetto, attraverso la proprietà dialect
, consente ad Hibernate di personalizzare gli statement SQL generati per lo specifico RDBMS. Nel nostro esempio il dialetto utilizzato è quello per MySQL con InnoDB, che è identificato dalla stringa org.hibernate.dialect.MySQL5InnoDBDialect
.
Connection Pool
Per la gestione del connection pool Hibernate supporta diversi meccanismi. In una Web Application, ad esempio, è possibile utilizzare il connection pool fornito dal container ed ottenuto utilizzando JNDI. Il nostro esempio è però una applicazione java standard, quindi dobbiamo utilizzare uno dei connection pool supportati: c3p0, Apache DBCP, Proxool, etc.
Le stringhe di configurazione del connection pool sono ovviamente dipendenti dalla specifica implementazione. Nel nostro esempio utilizziamo c3p0 e le proprietà sono:
- hibernate.c3p0.min_size: Numero minimo di connessioni JDBC nel pool (default 1);
- hibernate.c3p0.max_size: Numero massimo di connessioni JDBC nel pool (default 100);
- hibernate.c3p0.timeout: Indica in secondi quando tempo una connessione può rimanere non attiva prima di essere rimossa dal pool (default 0, ovvero mai);
- hibernate.c3p0.max_statements: Numero di statement SQL conservati in cache al fine di migliorare le performace dell’applicazione (default: 0, ovvero chaching disabilitato).
Mapping
Tra le proprietà di configurazione di Hibernate il mapping delle classi nelle entità del database è sicuramente quella più onerosa. Nelle prime versioni l’operazione era eseguita attraverso uno specifico file XML, mentre nelle versioni più recenti il mapping può essere realizzato anche attraverso l’utilizzo delle annotation. Nell’esempio è utilizzato il file Utente.hbm.xml
, il quale è dichiarato nel tag <mapping>
del file di configurazione hibernate.cfg.xml
. Naturalmente è possibile avere più file XML di mapping (ad esempio un per classe) e conseguentemente più tag <mapping>
nel file di configurazione.
Analizziamo la struttura base del file di mapping partendo da quello dell’esempio:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="it.javaboss.model.Utente" table="UTENTI"> <id name="username" column="username"/> <property name="nome" column="nome"/> <property name="cognome" column="cognome"/> <property name="email"/> <property name="password"/> </class> </hibernate-mapping> |
Il tag <class>
definisce il mapping tra una classe (o persisted class), indicata nella proprietà name
, e la tabella, indicata nella proprietà table
. Nel caso di coincidenza tra nome classe e nome tabella la proprietà table
non è necessaria. Il tag <id>
è utilizzato per indicare la colonna destinata a contenere la primary key della tabella e che quindi ne identifica in modo univoco la specifica riga. L’attributo name
identifica la proprietà della persisted class destinata a contenere la primary key associata alla colonna indicata dall’attributo column
. Infine il tab <property>
è utilizzato per mappare le restanti proprietà della classe. Si noti che l’attributo column
è sempre non necessario nel caso che il nome della proprietà sia identico a quello della colonna associata.
Gestione della Sessione
L’accesso alla base dati avviene attraverso un oggetto di tipo Session
. Gli oggetti persistiti sono letti e scritti attraverso tale oggetto che offre i metodi di basi necessari alla lettura, cancellazione e inserimento di righe in una tabella mappata in una persisted class. Gli oggetti conservati in sessione possono assumere tre possibili stati:
- TRANSIENT: è un qualsiasi oggetto di una persisted class non associato ad una sessione che non ha una chiave associata e nessuna corrispondenza nel database;
- PERSISTENT: un oggetto transient diventa persistito quando è associato ad una sessione, ha un identificativo univoco ed una rappresentazione nella base dati;
- DETACHED: una volta che la sessione è chiusa l’oggetto diviene detached ovvero non connesso.
La sessione dovrebbe essere tenuta aperta solo per il tempo necessario all’esecuzione delle operazioni richieste, infatti non essendo di tipo thread safe, la sessione dovrebbe essere aperta e chiusa all’occorrenza.
Nell’esempio abbiamo implementato una classe di utilità per il set-up di un oggetto di tipo SessionFactory, il quale legge i file di configurazione e restituisce, all’occorrenza, una nuova sessione.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class HibernateUtil { private static SessionFactory sessionFactory = initHibernateUtil(); private static SessionFactory initHibernateUtil() { try { return new Configuration().configure().buildSessionFactory(); } catch (HibernateException ex) { throw new ExceptionInInitializerError(ex); } } public static SessionFactory getSessionFactory() { return sessionFactory; } public static void shutdown() { // Close caches and connection pools getSessionFactory().close(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 |
Session session = HibernateUtil.getSessionFactory().openSession(); Transaction tx = null; try { tx = session.beginTransaction(); // do some work ... tx.commit(); } catch (Exception e) { if ( tx!=null ) tx.rollback(); } finally { session.close(); } |
- creazione di un nuovo utente;
- aggiornamento dell’utente appena creato;
- recupero di tutti gli utenti nella tabella.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
public class HibernateMain { public static void main(String[] args) { Session session = HibernateUtil.getSessionFactory().openSession(); Transaction tx = null; try { //--- First unit of work tx = session.beginTransaction(); Utente user = new Utente("pippo"); String userId = (String) session.save(user); tx.commit(); session.close(); //--- Second unit of work session = HibernateUtil.getSessionFactory().openSession(); tx = session.beginTransaction(); user = session.get(Utente.class, userId); user.setNome("Filippo"); tx.commit(); session.close(); //--- Third unit of work session = HibernateUtil.getSessionFactory().openSession(); tx = session.beginTransaction(); List<Utente> users = session .createNativeQuery("select * from UTENTI order by USERNAME") .addEntity(Utente.class).list(); System.out.println(users.size() + " user(s) found: "); for (Iterator<Utente> iter = users.iterator(); iter.hasNext();) { Utente us = iter.next(); System.out.println( us.getNome() ); } tx.commit(); session.close(); } catch (HibernateException e) { if (tx!=null) { tx.rollback(); } } finally { if ( session != null && session.isOpen() ) { session.close(); } // Shutting down the application HibernateUtil.shutdown(); } } } |
Uso delle Annotazioni
In alternativa all’utilizzo dei file di mapping hbm.xml
è possibile utilizzare le annotazioni JPA introdotte nel JDK 5.0 ed inserite nel package javax.persistence
. Per farlo innanzitutto eliminiamo l’attributo resource
nel tag <mapping>
del file hibernate.cfg.xml
e sostituiamolo con l’attributo class
nel modo segunete:
1 2 3 4 5 6 7 |
<hibernate-configuration> <session-factory> ... <mapping class="it.javaboss.model.Utente"/> .... <session-factory> <hibernate-configuration> |
Successivamente annotiamo la casse Utente.java
, al fine di ottenere lo stesso mapping che avevamo con il file Utente.hbm.xml
, nel modo seguente:
1 2 3 4 5 6 7 8 9 10 |
@Entity @Table( name = "UTENTI" ) public class Utente { .... @Id public String getUsername() { return username; } .... } |
A livello di classe sono presenti due annotazioni:
@Entity
: dichiara la classe pojo come una entità che può essere persistita e quindi gestita da Hibernate. E’ possibile specificare un nome per l’entità valorizzando l’attributoname
dell’annotazione o, come nel nostro caso, il nome corrisponderà al nome della classe. Il nome dell’entità è importante per la definizione delle query HQL (non trattate nel post).@Table
: dichiara il mapping vero e proprio verso la specifica tabella del database. L’annotazione prevede diversi attributi, tutti opzionali, per specificare il catalogo, lo schema, i constraint, etc. Nel nostro esempio abbiamo valorizzato l’attributoname
per indicare il nome della tabella, poichè altrimenti sarebbe stato implicitamente mappato su una tabella UTENTE non esistente.
A livello di proprietà abbiamo utilizzato la sola annotazione @Id
che identifica la proprietà destinata a contenere la primary key dell’entità. Nel nostro esempio i nomi delle proprietà della classe pojo coincidono con i nomi delle colonne della tabella, quindi non sono necessarie ulteriori annotazioni. Alternativamente avremmo potuto utilizzare l’annotazione @Column
sul metodo get
della proprietà e specificare l’attributo name
per indicare il nome della colonna.
Codice Sorgente
Il codice sorgente dell’esempio è scaricabile qui hb-user.