Spring offre un modello di supporto all‘accesso ai dati, all‘ORM (Object Relational Mapping) e agli aspetti transazionali flessibile e potente, mettendo in pratica con una certa eleganza i predicati dell‘Aspect Oriented Programming. Gli sforzi per la portabilità sono notevolmente alleviati, e con poca fatica è persino possibile far migrare il proprio sistema in un contesto transazionale distribuito come JTA.
Introduzione
Le problematiche nella gestione degli aspetti di persistenza hanno caratterizzato fortemente le ultime evoluzioni nell‘ambito delle tecnologie Java, tanto che le soluzioni a riguardo sono numerose e tutte abbastanza sofisticate. È possibile utilizzare a basso livello le funzioni JDBC oppure affidarsi a vari tools di Object Relational Mapping tra i quali spicca Hibernate. Spring fornisce un supporto uniforme sia verso JDBC che verso tutti gli altri tools. Inoltre fornisce una soluzione estremamente elegante per la gestione dei confini transazionali del codice applicativo. In questo articolo ci limiteremo a mostrare come integrare Hibernate in un sistema basato su Spring.
Il pattern Template Method
Spring utilizza i concetti del pattern Template Method unitamente a un meccanismo di “callback” per offrire un layer uniforme verso le API di persistenza, che si tratti di JDBC, di Hibernate o di altre tecnologie come per esempio JDO. Il pattern Template Method nella sua formulazione standard prevede che si utilizzi una classe astratta contenente dei metodi “concreti” che contengano l‘implementazione degli aspetti standard della problematica che si vuole affrontare, mentre la logica specifica viene delegata, all‘interno dei metodi concreti, a dei metodi astratti della stessa classe la cui implementazione sarà fornita dalle sottoclassi. Spring, invece di utilizzare implementazioni di metodi astratti, definisce delle specifiche interfacce le cui implementazioni saranno fornite ai metodi concreti in fase di esecuzione. In pratica le firme dei metodi concreti prevedono tra i parametri di ingresso le interfacce date, delle quali a runtime vengono passate le implementazioni specifiche. Le caratteristiche comuni che Spring mette a disposizione nella sua implementazione del Template Method sono tutte quelle operazioni che normalmente “sporcano” il nostro codice, come per esempio l‘apertura e la chiusura delle connessioni. Nel paragrafo “Hibernate Template” si vedrà un esempio di implementazione del pattern per quanto riguarda l‘integrazione di Hibernate come tool di ORM.
Gestione delle eccezioni
Spring “nasconde” le eccezioni lanciate dalle API delle tecnologie di persistenza utilizzate dietro una propria gerarchia di eccezioni che fa capo all‘eccezione di tipo runtime DataAccessException. Il fatto che la DataAccessException sia di tipo runtime, cioè sia un‘estensione della RuntimeException, evita al codice chiamante l‘obbligo di catturare e trattare tale eccezione. In questo modo è possibile centralizzare la gestione delle eccezioni negli strati più ad alto livello del proprio sistema, rendendo gli strati intermedi “puliti” da blocchi di tipo “try-catch” che normalmente rendono il codice poco comprensibile e “facile” agli errori. La DataAccessException è la classe di base di un “albero” di eccezioni che interpreta la casistica classica di errori che si possono verificare nello strato di persistenza e traduce le eccezioni specifiche della tecnologia di persistenza utilizzata nell‘ambito di questa casistica.
Un progetto di prova
Creiamo in Eclipse un progetto SpringSamples importando nel classpath la libreria spring.jar, la commons-logging.jar e la libreria core di Hibernate hibernate3.jar. Possiamo utilizzare per il nostro esempio un db MySql, anche se i concetti esposti sono validi per qualunque altro DB. Nell‘istanza MySql in esecuzione creiamo uno schema arbitrariamente possiamo chiamare “spring-samples”. Creiamo due tabelle: Aziende e Responsabili. La tabella Aziende ha i campi Denominazione, PartitaIva e IdResponsabile, dove quest‘ultimo è una chiave esterna riferita alla tabella Responsabili. La tabella Responsabili ha i campi Nome, Cognome, CodiceFiscale. Utilizzando il tool java2hbm di Hibernate possiamo poi generare i file di mapping e le classi che rappresentano il modello a oggetti corrispondente, che risulteranno essere dei semplici java beans denominati allo stesso modo delle tabelle suddette: Aziende.java e Responsabili.java. In particolare la classe Aziende.java avrà un metodo getResponsabili() stante a rappresentare nel modello a oggetti il “constraint” di chiave esterna con la tabella Responsabili. Avremmo anche potuto scrivere prima i file di mapping e le classi del modello e poi generare le tabelle del db di conseguenza (con le loro relazioni), ma questo aspetto nella presente esposizione non è essenziale, l‘unica cosa che ci interessa è avere tutto ciò che ci serve per far persistere lo stato del nostro modello a oggetti tramite il motore ORM di Hibernate. Ora in un package che possiamo chiamare spring.esempi.dataaccess creiamo le seguenti interfacce:
import java.util.Collection;
public interface AziendeDAO {
public Collection loadAziende();
public void insertAzienda(Aziende azienda);
public void insertResponsabile(Responsabili responsabile);
}
import java.util.List;
public interface AziendeService {
public void insertResponsabileAndAzienda(Responsabili responsabile, Aziende azienda);
}
Abbiamo una interfaccia DAO che gestisce la persistenza delle due entità Responsabili e Aziende e un‘interfaccia AziendeService che contiene un unico metodo insertResponsabileAndAzienda; quest‘ultimo metodo servirà ad esporre la gestione degli aspetti transazionali del codice. Vedremo come saranno implementate queste interfacce nei successivi paragrafi. Inoltre rendiamo disponibile nel classpath il file context.xml che rappresenta la configurazione di Spring (vedere l‘articolo relativo al core di Spring).
Spring e Hibernate
Spring permette di approntare l‘ambiente adeguato all‘esecuzione di Hibernate mettendone a disposizione le risorse tramite le proprie funzionalità di Dipendency Injection. La factory di Spring, attraverso le informazioni definite in un file xml, può gestire la localizzazione e la configurazione della SessionFactory di Hibernate e del DataSource utilizzato dalla stessa session factory. Offre poi la gestione, trasparente all‘utente, della sessione di Hibernate, mettendola a disposizione nel Thread corrente. Ecco un esempio di configurazione del data source e della SessionFactory di Hibernate che possiamo introdurre nel nostro progetto di prova (nel nostro file di configurazione context.xml):
hibernate.hbm.xml
hibernate.dialect=org.hibernate.dialect.MySQLDialect
...
Viene definito prima il data-source con le relative informazioni di connessione e poi la SessionFactory che contiene un riferimento a quest‘ultimo. La SessionFactory è configurata fornendo il file di configurazione di Hibernate hibernate.hbm.xml che a sua volta conterrà i vari file di mapping, e la tipologia di database utilizzata, tramite la proprietà hibernate.dialect. Notiamo anche che il data source è definito utilizzando un pool di connessioni gestito dal prodotto jakarta-commons-dbcp; è necessario quindi scaricare la relativa libreria dal sito Jakarta http://jakarta.apache.org/commons/dbcp/downloads.html e renderla disponibile nel classpath.
HibernateTemplate
HibernateTemplate è una classe che implementa il pattern Template Method esposto precedentemente. HibernateTemplate fornisce l‘accesso alla sessione di Hibernate, si assicura che la session stessa sia appropriatamente aperta e chiusa, partecipa alle transazioni esistenti e fornisce tutte le funzionalità standard di accesso ai dati tramite un insieme di metodi apposito; inoltre secondo i dettami del pattern stesso tali metodi delegano all‘implementazione di determinate interfacce le logiche di persistenza specifiche. In questo modo l‘implementazione di un generico DAO (Data Access Object) può utilizzare la classe HibernateTemplate per eseguire le proprie operazioni di persistenza, mentre l‘interfaccia dello stesso DAO risulta indipendente da Spring. Le istanze di HibernateTemplate sono thread safe e possono quindi essere usate come variabili di istanza della classe che le contiene. Mostriamo di seguito la definizione del DAO AziendeDAO nel file di configurazione di Spring e la relativa implementazione (trascuriamo qui l‘implementazione dei metodi insertResponsabile e insertAzienda):
...
...
public class AziendeDAOImpl implements AziendeDAO {
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public Collection loadAziende(final String category) throws DataAccessException {
HibernateTemplate ht = new HibernateTemplate(this.sessionFactory);
return (Collection) ht.execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException {
Query query = session.createQuery("from spring-samples.Aziende aziende");
return query.list();
}
});
}
...
}
...
Nell‘esempio viene utilizzato un meccanismo di callback sul metodo execute di HibernateTemplate per eseguire una semplice query. Viene a tal scopo fornita un‘implementazione dell‘interfaccia HibernateCallback sotto forma di classe interna anonima. Possiamo riportare il frammento di configurazione di Spring riportato sopra (includendo anche la definizione della session factory) insieme all‘implementazione del DAO nel nostro progetto di prova e possiamo testare il tutto scrivendo il codice client che recupera dall‘ApplicationContext di Spring il bean aziendeDAO e ne esegue il metodo loadAziende. Per semplificare ulteriormente le cose Spring fornisce anche una classe HibernateDaoSupport che i DAOs possono estendere con il metodo setSessionFactory che sarà utilizzato dal framework per l‘”iniezione” della SessionFactory, e i metodi getSessionFactory e getHibernateTemplate che le sottoclassi possono utilizzare come di seguito mostrato:
public class AziendeDaoImpl extends HibernateDaoSupport implements AziendeDao {
public Collection loadAziende() throws DataAccessException {
return getHibernateTemplate().find(" from spring-samples.Aziende aziende");
}
...
}
Transazionalità programmatica
È possibile demarcare transazioni a un qualunque livello negli strati applicativi. Non ci sono restrizioni particolari sull‘implementazione del componente di business che accede ai dati. Esso deve semplicemente avere accesso a un‘implementazione dell‘interfaccia PlatformTransactionManager. PlatformTransactionManager è l‘astrazione che Spring mette a disposizione per la gestione delle transazioni, descritta di seguito:
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
Esistono implementazioni specifiche di tale interfaccia per i principali tools ORM, come Hibernate e JDO. E‘ possibile passare da un TransactionManager ad un altro semplicemente cambiando la configurazione di Spring. Spring permette di gestire la transazionalità sia con un approccio dichiarativo, mediante elementi di configurazione, sia programmaticamente utilizzando un‘implementazione del pattern Template Method. L‘approccio programmatico nella gestione delle transazioni prevede quindi l‘utilizzo di una classe TransactionTemplate che con un meccanismo di callback analogo a quello che abbiamo già visto nel paragrafo precedente permette di racchiudere in un‘unica transazione un numero arbitrario di operazioni. Nell‘esempio seguente viene mostrata la configurazione di Spring per quanto riguarda il transaction manager specifico di Hibernate che viene poi iniettato in una classe AziendeServiceImpl implementazione dell‘interfaccia AziendeService descritta nel precedente paragrafo. Quest‘ultima utilizza un‘istanza di TransactionTemplate, inizializzata con il transaction manager, per eseguire un‘insieme specifico di operazioni in un contesto transazionale.
...
...
public class AziendeServiceImpl implements AziendeService {
private PlatformTransactionManager transactionManager;
private AziendeDao aziendeDao;
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void setAziendeDao (AziendeDao aziendeDao) {
this. aziendeDao = aziendeDao;
}
public void insertResponsabileAndAzienda(Responsabili responsabile, Aziende azienda) {
TransactionTemplate transactionTemplate
= new TransactionTemplate(this.transactionManager);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
aziendeDao.insertResponsabile(responsabile);
aziendeDao.insertAzienda(azienda);
}
}
);
}
}
...
Qui, analogamente all‘esempio del paragrafo precedente, forniamo al metodo execute della classe TransactionTemplate un‘implementazione dell‘interfaccia TransactionCallbackWithoutResult, utilizzando anche in questo caso per comodità una classe interna anonima. La nostra implementazione del metodo doInTransactionWithoutResult consiste nell‘esecuzione dei metodi insertResponsabile e insertAzienda che vengono eseguiti dal TransactionTemplate in un unico contesto transazionale, come ci aspettavamo. Possiamo verificare nel nostro progetto di esempio il funzionamento corretto del tutto trascrivendo la porzione di configurazione riportata precedentemente nel nostro file context.xml completandola con la configurazione della Session Factory e del Dao, e scrivendo un porzione di codice client che ricavi il bean aziendeService dall‘ApplicationContext di Spring ed esegua il metodo insertResponsabileAndAzienda passandogli in ingresso gli oggetti responsabile e azienda il cui stato si vuole far persistere nel database. Se introduciamo una qualche condizione di errore nell‘esecuzione dell‘uno o dell‘altro metodo dell‘oggetto aziendeDao ci aspettiamo che venga eseguito il rollback.
Transazionalità dichiarativa
È possibile soppiantare la transazionalità programmatica descritta sopra, che necessita di una demarcazione esplicita delle transazioni tramite la classe TransactionTemplate, con un meccanismo di transazionalità dichiarativa. Spring ci ha abituato a costruire i tasselli della nostra applicazione definendone le dipendenze in un file di configurazione. Seguendo questo percorso è possibile definire nella configurazione stessa le caratteristiche transazionali dei propri oggetti. L‘arte di isolare e centralizzare gli aspetti indipendenti di un sistema come la transazionalità è un concetto ormai comune nel mondo informatico che viene sintetizzato nella sigla AOP, ossia Aspect Oriented Programming, che abbiamo già visto nel precedente articolo. Spring utilizza il proprio supporto all‘AOP per gestire gli aspetti transazionali in modo dichiarativo, fornendo un insieme specifico di elementi di configurazione. L‘esempio seguente, che rappresenta un frammento di configurazione di Spring, mostra quanto sia semplice definire le caratteristiche transazionali della propria applicazione, rendendo al contempo il codice “inconsapevole” di tali caratteristiche.
xmlns_xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns_aop="http://www.springframework.org/schema/aop"
xmlns_tx="http://www.springframework.org/schema/tx"
xsi_schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
...
In questo esempio dopo aver definito il transaction manager specifico di Hibernate si configurano le caratteristiche transazionali del servizio AziendeService. Il tag
Conclusioni
Abbiamo visto come il supporto di Spring per la persistenza e gli aspetti transazionali permetta di integrare un tool ORM come Hibernate con poca fatica e di isolare lo strato di accesso ai dati: solamente le classi DAO hanno bisogno di utilizzare esplicitamente le classi di supporto di Spring. L‘integrazione di altri tools oppure direttamente dell‘API JDBC è del tutto analoga: Spring fornisce i “templates” e le classi di supporto per tutti i più comuni strumenti ORM. Migrare il proprio sistema da una tecnologia di persistenza all‘altra è una cosa del tutto lineare, e non comporta grandi sconvolgimenti.
Riferimenti
[1] “Spring Reference” 2.0
http://www.springframework.org/
[2] Craig Walls – Ryan Breidenbach, “Spring in Action”, Manning, 2005
[3] Christian Bauer – Gavin King, “Hibernate in action”, Manning, 2005