La sessione di Hibernate e la transazionalità

Una soluzione semplice per contesti non complessidi

Hibernate è lo strumento di mapping da oggetti a relazionale ormai più diffuso. Un problema classico è la gestione degli aspetti transazionali di Hibernate. Frameworks come Spring offrono delle soluzioni avanzate e di una flessibilità estrema. Qui si propone invece una soluzione semplice per la gestione del ciclo di vita della sessione di Hibernate, che può essere adatta in contesti di non elevata complessità e senza il supporto di Spring.

Introduzione

Gestire i confini transazionali di uno specifico contesto applicativo tramite gli strumenti di Hibernate significa comprendere come utilizzare la sessione di Hibernate (classe Session) e in che modo la transazionalità  è legata alla sessione. Hibernate offre un insieme di soluzioni, senza forzare l‘utilizzo di schemi rigidi. Lo scopo di questo articolo è offrire una soluzione che può essere utilizzata con profitto in determinati scenari, senza dover far ricorso a particolari infrastrutture.

La SessionFactory di Hibernate

La SessionFactory è il punto centrale per lo sviluppatore. Si può pensare come un contenitore di tutta la "paccottiglia" di Hibernate specifica della vostra applicazione. E‘ consolante poter partire da un unico punto, elimina il senso di disorientamento che angoscia gli sviluppatori alle prese col nuovo prodotto di turno. La SessionFactory è capace di caricare le informazioni specifiche del contesto applicativo corrente sia attraverso dei file xml che dei file di properties. Un approccio può essere questo: scrivete la configurazione di Hibernate in un file hibernate.cfg.xml che conterrà  all‘interno i riferimenti a tutti i file di "mapping" utilizzati nell‘applicazione, memorizzate il file in modo che sia disponibile nel classpath, per esempio sotto la directory dove memorizzate i vostri sorgenti (in questo modo il file sarà  memorizzato nella cartella dei compilati). Ecco un esempio di file hibernate.cfg.xml:

trueoracle.jdbc.driver.OracleDriverpasswordjdbc:oracle:thin:@:1521:ORCLusernameorg.hibernate.dialect.Oracle9Dialect3...

La SessionFactory può quindi essere inizializzata e resa disponibile all‘applicazione mediante un codice del tipo:

SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();

Il metodo configure() dell‘oggetto Configuration legge le informazioni di configurazione dal classpath e costruisce di conseguenza un‘istanza di SessionFactory. Normalmente la SessionFactory viene resa disponibile in modo statico all‘applicazione, come normalmente avviene per le "factory".

Infatti lo scopo della SessionFactory è quello di fornire delle nuove istanze di sessioni Hibernate al chiamante tramite il metodo openSession(), per cui non c‘è nessuna problematica di utilizzo concorrente della SessionFactory.

La sessione di Hibernate e la transazionalità 

Attraverso la SessionFactory è possibile ottenere le sessioni di Hibernate. La sessione rappresenta il contesto di lavoro di Hibernate. Lo sviluppatore può definire il "confine" transazionale di un particolare flusso applicativo aprendo una transazione attraverso la sessione ed effettuando il commit, o il rollback in caso di errori. Si è portati quindi a pensare che il ciclo di vita della transazione sia in qualche modo contenuto in quello della sessione. Non è cosà¬. Hibernate permette di far vivere una transazione tra più sessioni attraverso il concetto di oggetti "detached". In qualche modo gli oggetti che rappresentano le entità  mappate su db possono essere "staccati" e "riattaccati" a un contesto transazionale che viaggia su più sessioni. In questo modo è possibile gestire per esempio transazioni che in un contesto applicativo web "attraversano" più richieste. Poichà© in questo articolo miriamo alla semplicità , e la pigrizia estiva aiuta tale intento, ci limiteremo al caso più familiare di una o più transazioni all‘interno di un‘unica sessione. Lo scenario tipico su web è quello che prevede una transazione per una richiesta http. Qui introdurremo un tocco di flessibilità  in più prevedendo più transazioni all‘interno di un‘unica sessione, il che ci permette di gestire scenari non propriamente associati a una interazione web. Tra le varie possibilità  quella più immediata è far vivere la sessione a livello di un thread di esecuzione. In pratica nel contesto di una applicazione web un thread rappresenta una richiesta http. E‘ possibile far vivere degli oggetti nello spazio di memorizzazione di un thread utilizzando delle variabili di tipo ThreadLocal. Le variabili di tipo ThreadLocal hanno un metodo set che permette di memorizzare il riferimento a un generico oggetto e un metodo get che restituisce un generico oggetto. In qualche misteriosa maniera il metodo set della variabile ThreadLocal memorizza il riferimento all‘oggetto nel contesto del Thread corrente, mentre il metodo get permette di recuperare lo stesso oggetto dal Thread corrente. Dichiarando statiche tali variabili è possibile "vedere" gli oggetti memorizzati in esse attraverso più chiamate di metodi anche su oggetti diversi. Nel prossimo paragrafo è descritto come si possano utilizzare delle variabili ThreadLocal per memorizzare le sessioni Hibernate attraverso una semplice classe di utilità .

Gestire i confini transazionali con una classe di utilità 

Per gestire la transazionalità  secondo lo schema tracciato nel precedente paragrafo si è scelto di creare una classe di utilità  denominata HibernateUtil, che permette di gestire la sessione con "visibilità " di Thread. Ecco il codice della classe HibernateUtil:

import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.hibernate.Session;import org.hibernate.SessionFactory;import org.hibernate.Transaction;import org.hibernate.cfg.Configuration;public class HibernateUtil {private static final Log log = LogFactory.getLog(HibernateUtil.class);private static final SessionFactory sessionFactory;private static ThreadLocal sharedSession;private static ThreadLocal transaction;static {try {// Crea la SessionFactory da hibernate.cfg.xmlsessionFactory = new Configuration().configure().buildSessionFactory();sharedSession = new ThreadLocal();transaction = new ThreadLocal();} catch (Throwable ex) {log.error("Creazione della SessionFactory fallita.", ex);throw new ExceptionInInitializerError(ex);}}public static void init() throws Throwable{}public static Session openSession() {Session session = (Session)sharedSession.get();if (session == null) {session = sessionFactory.openSession();sharedSession.set(session);}return session;}public static void closeSession() {Session shSession = (Session)sharedSession.get();if (shSession != null ) {shSession.close();sharedSession.set(null);}}public static void startTransaction() throws HibernateUtilException{Session shSession = (Session)sharedSession.get();Transaction tr = (Transaction)transaction.get();if (shSession == null) {log.error("Sessione nulla.");throw new HibernateUtilException("Session is null.");} else if (!shSession.isOpen()) {log.error("Sessione chiusa.");throw new HibernateUtilException("Session is closed.");}if(tr == null){tr = shSession.beginTransaction(); transaction.set(tr);}}public static void endTransaction(boolean commit) {Transaction tr = (Transaction)transaction.get();if (tr != null) {if (commit) {tr.commit();} else{tr.rollback();}transaction.set(null);}}public static Session getSession() throws HibernateUtilException{Session shSession = (Session)sharedSession.get();if (shSession == null) {log.error("Sessione nulla.");throw new HibernateUtilException("Session is null.");}return shSession;}}

Il metodo init() della classe HibernateUtil permette di inizializzare la SessionFactory da un file hibernate.cfg.xml reso disponibile nel classpath della propria applicazione. La classe HibernateUtil gestisce la transazionalità  a livello di Thread attraverso due variabili statiche di tipo ThreadLocal, sharedSession e transaction. Il metodo openSession() crea, se non esiste già , una sessione dalla SessionFactory e la memorizza nella variabile ThreadLocal sharedSession in modo che sia disponibile nel Thread corrente. Il metodo closeSession chiude la sessione e setta a null il contenuto della sharedSession. Il metodo getSession() recupera la sessione corrente dalla sharedSession. Se si vuole gestire un contesto transazionale all‘interno della sessione corrente è sufficiente eseguire il metodo startTransaction() che memorizza la transazione nella variabile ThreadLocal transaction. Il metodo endTransaction(true) termina la transazione con un commit mentre endTransaction(false) termina la transazione ed effettua il rollback.

Esempio:

HibernateUtil.openSession();...try {HibernateUtil.startTransaction();  (codice eseguito in un contesto transazionale)HibernateUtil.endTransaction(true); } catch (Throwable ex) {HibernateUtil.endTransaction(false);...}...HibernateUtil.closeSession();

Le classi DAOs e la transazionalità 

Le classi DAOs rappresentano nella modellazione a oggetti i componenti che gestiscono e nascondono agli altri "layer" applicativi le tematiche della persistenza degli oggetti. I DAOs non dovrebbero avere al loro interno confini transazionali. Le transazioni vengono gestite normalmente a livello dei servizi a "grana grossa" che forniscono l‘accesso alla logica di business. Nel nostro caso quindi i DAOs non fanno altro che recuperare la sessione tramite la chiamata HibernateUtil.getSession() ed effettuare attraverso di essa le operazioni di persistenza di Hibernate.

Generare i mappings, le corrispondenti classi java e i DAOs

Dopo aver definito la strategia di gestione degli aspetti transazionali con la classe HibernateUtil, possiamo chiederci se sia possibile risparmiarci la fatica dello scrivere a mano le classi DAO per la nostra applicazione. La risposta è sà¬, ode alla pigrizia. Per l‘ambiente di sviluppo eclipse è disponibile il plugin JbossIde. JbossIde contiene il pacchetto HibernateTools che permette di gestire il "reverse engineering" del database con diverse possibilità  di configurazione. Il tool è capace di generare i mappings di Hibernate, le classi java e i DAOs attraverso dei templates scritti sotto forma di files Velocity. Una domanda che sorge spontanea è perchà© utilizzare il JbossIde se quello che serve è solo il plugin HibernateTools. La risposta è semplice quanto disarmante: HibernateTools da solo, per qualche misteriosa ragione, non funziona correttamente. Inutile perdere tempo a cercare il baco o aspettare una release più stabile, meglio fare il download dell‘intero fardello. Possiamo utilizzare gli strumenti di reverse engineering di HibernateTools per generare i mappings, gli oggetti java e i DAOs per la nostra applicazione. Nel nostro caso i DAOs devono utilizzare la classe HibernateUtil per avere accesso alla sessione. E‘ possibile sovrascrivere il template di generazione dei DAOs in modo da soddisfare le nostre richieste. Vediamo come fare. La figura 1 mostra la schermata del wizard che permette di configurare e avviare il reverse engineering. La selezione di "use custom templates" permette di sovrascrivere il comportamento di default. Creiamo una cartella temporanea (nella figura "hibernate-templates") nel nostro progetto, creiamo lଠdentro la cartella dao con all‘interno il template di generazione dei DAOs modificato a dovere (occorre rispettare per i templates la struttura di cartelle di HibernateTools). A questo punto possiamo lanciare il tool. Vediamo un frammento di un DAO generato:

public class EsempioHome {private static final Log log = LogFactory.getLog(EsempioHome.class);public void persist(Esempio transientInstance) {log.debug("persisting Esempio instance");try {HibernateUtil.getSession().persist(transientInstance);log.debug("persist successful");}catch (RuntimeException re) {log.error("persist failed", re);throw re;}}

Conclusioni

Spesso è necessario avere delle soluzioni immediate per problematiche importanti, senza dover ricorrere alla complicazioni di infrastrutture che richiedono tra l‘altro tempi di "startup" e di studio non irrilevanti. La soluzione proposta per la gestione della transazionalità  in Hibernate può essere utilizzata con profitto all‘interno di progetti di non elevata complessità . In caso contrario il consiglio è quello di introdurre un framework come Spring.

Riferimenti bibliografici

[1] "Hibernate Reference Documentation" 3.1.1
http://www.hibernate.org

Condividi

Pubblicato nel numero
110 settembre 2006
Mario Casari si è laureato in Fisica a Cagliari. Ha lavorato come progettista e sviluppatore soprattutto in ambito J2EE per diverse e importanti realtà italiane. Attualmente sta approfondendo le tematiche di utilizzo di Spring in progetti per la pubblica amministrazione.
Ti potrebbe interessare anche