SeamFramework.orgCommunity Documentation

Capitolo 9. Seam e Object/Relational Mapping

9.1. Introduzione
9.2. Transazioni gestite da Seam
9.2.1. Disabilitare le transazioni gestite da Seam
9.2.2. Configurazione di un gestore di transazioni Seam
9.2.3. Sincronizzazione delle transazioni
9.3. Contesti di persistenza gestiti da Seam
9.3.1. Utilizzo di un contesto di persistenza gestito da Seam con JPA
9.3.2. Uso delle sessioni Hibernate gestite da Seam
9.3.3. Contesti di persistenza gestiti da Seam e conversazioni atomiche
9.4. Usare il JPA "delegate"
9.5. Uso di EL in EJB-QL/HQL
9.6. Uso dei filtri Hibernate

Seam fornisce un supporto esteso alle due maggiori e più popolari architetture per la persistenza in Java: Hibernate3 e Java Persistence API introdotta con EJB 3.0. L'architettura unica di Seam per la gestione dello stato consente l'integrazione dei più sofisticati ORM di ogni framework per applicazioni web.

Seam è nato dalla frustrazione del team di Hibernate per l'assenza di contesto stateless tipica delle precedenti generazioni di architetture nelle applicazioni Java. L'architettura della gestione dello stato di Seam è stata originariamente progettata per risolvere problemi relativi alla persistenza — in particolare i problemi associati all'elaborazione ottimistica delle transazioni. Le applicazioni online scalabili usano sempre transazioni ottimistiche. Una transazione atomica di livello (database/JTA) non dovrebbe propagare l'interazione dell'utente amenoché l'applicazione sia progettata per supportare solo un piccolo numero di client concorrenti. Ma quasi tutto il lavoro interessante coinvolge in primo luogo la visualizzazione dei dati all'utente, e poi, immediatamente dopo, l'aggiornamento dei dati stessi. Quindi Hibernate è stato progettato per supportare l'idea del contesto di persistenza che propaga una transazione ottimistica.

Sfortunatamente le cosiddette architetture "stateless" che precedettero Seam e EJB 3.0 non avevano alcun costrutto per rappresentare una transazione ottimistica. Quindi, invece, queste architetture fornivano contesti di persistenza con scope a livello di transazione atomica. Sicuramente questo portava diversi problemi agli utenti ed è la causa numero uno per le lamentele riguardanti Hibernate: la temuta LazyInitializationException. Ciò di cui si ha bisogno è un costrutto per rappresentare una transazione ottimistica a livello applicazione.

EJB 3.0 riconosce il problema e introduce l'idea di componente stateful (un bean di sessione stateful) con un contesto di persistenza esteso con scope legato al ciclo di vita del componente. Questa è una soluzione parziale al problema (ed è un utile costrutto), comunque ci sono due problemi:

Seam risolve il primo problema fornendo conversazioni, componenti bean di sessione stateful con scope di conversazione. (La maggior parte delle conversazioni in verità rappresentano transazioni ottimistiche a livello dei dati). Questo è sufficiente per molte semplici applicazioni (quali la demo prenotazione di Seam) dove non serve la propagazione del contesto di persistenza. Per applicazioni più complesse, con molti componenti interagenti in modo stretto in ciascuna conversazione, la propagazione del contesto di persistenza tra componenti diventa un problema importante. Quindi Seam estende il modello di gestione del contesto di persistenza di EJB 3.0 per fornire contesti di persistenza estesi e con scope di conversazione.

I bean di sessione EJB includono la gestione dichiarativa delle transazioni. Il container EJB è capace di avviare una transazione in modo trasparente quando viene invocato il bean, e terminarla quando termina l'invocazione. Se si scrive un metodo di un bean di sessione che agisce come action listener JSF, si può fare tutto il lavoro associato all'azione in una transazione, ed essere sicuri che venga eseguito il commit od il rollback quando l'azione viene terminata. Questa è grande funzionalità ed è tutto ciò che serve ad alcune applicazioni Seam.

Comunque c'è un problema con tale approccio. Un'applicazione Seam potrebbe non eseguire l'accesso a tutti i dati per una richiesta da una chiamata di un singolo metodo a un bean di sessione.

Più transazioni per richiesta ci sono, più è probabile che si incontrino problemi di atomicità e isolamento quando l'applicazione processa molte richieste concorrenti. Certamente tutte le operazioni di scrittura devono avvenire nella stessa transazione!

Gli utenti di Hibernate hanno sviluppato il pattern "open session in view" per aggirare questo problema. Nella comunità Hibernate, il pattern "open session in view" è stato storicamente anche più importante poiché framework come Spring usano contesti di persistenza con scope transazionale. In tal caso il rendering della vista causerebbe eccezioni di tipo LazyInitializationException, qualora si accedesse a delle relazioni non caricate in precedenza.

Questo pattern di solito è implementato come una singola transazione che si estende per l'intera richiesta. Vi sono parecchi problemi connessi a questa implementaziome, il più serio dei quali sta nel fatto che non è possibile essere sicuri che una transazione sia andata a buon fine finché non se ne fa il commit — ma prima che la transazione gestita secondo tale pattern sia stata sottoposta a commit, la pagina sarà stata completamente disegnata, e la risposta relativa potrebbe essere già stata inviata al client. Come è possibile notificare l'utente che la sua transazione non ha avuto successo?

Seam risolve sia il problema dell'isolamento della transazione sia il problema del caricamento delle associazioni, evitando i quelli associati al pattern "open session in view". La soluzione è costituita da due parti:

Nella prossima sezione, esamineremo come utilizzare un contesto di persistenza conversazionale. Ma prima occorre vedere come abilitare la gestione delle transazioni di Seam. Si noti che è possibile usare contesti di persistenza conversazionale senza usare la gestione delle transazioni di Seam, e ci sono buoni motivi per utilizzare la gestione delle transazioni di Seam anche se non si stanno utilizzando contesti di persistenza gestiti da Seam. Comunque, queste due funzionalità sono state progettate per operare assieme, e usate assieme danno il meglio.

La gestione delle transazioni di Seam è utile anche se vengono usati contesti di persistenza gestiti da un container EJB 3.0. Ma in particolare essa è utile quando Seam è usato fuori dall'ambiente Java EE 5, o in ogni altro caso dove si usi un contesto di persistenza gestito da Seam.

Seam fornisce un'astrazione della gestione della transazione che permette di iniziarla, farne il commit e il rollback e di sincronizzarsi con essa. Di default Seam usa un componente transazionale JTA che si integra con transazioni EJB gestite dal programma o dal container. Se si sta lavorando in un ambiente Java EE 5, occorre installare il componente di sincronizzazione EJB in components.xml:


<transaction:ejb-transaction />

Comunque, se si sta lavorando in un container non conforme a J2EE 5, Seam cercherà di rilevare automaticamente il meccanismo di sincronizzazione da usare. Comunque, qualora Seam non fosse in grado di rilevarlo, potrebbe essere necessario configurare una delle seguenti proprietà:

Si configuri la gestione delle transazioni RESOURCE_LOCAL JPA aggiungendo il seguente a components.xml dove #{em} è il nomedel componente persistence:managed-persistence-context. Se il contesto di persistenza gestito è chiamato entityManager, si può optare di lasciare vuoto l'attributo entity-manager. (Si veda contesti di persistenza gestiti da Seam )


<transaction:entity-transaction entity-manager="#{em}"/>

Per configurare le transazioni gestite da Hibernate si dichiari il seguente in components.xml dove #{hibernateSession} è il nome del componente del progetto persistence:managed-hibernate-session. Se la sessione Hibernate è chiamata session, si può optare di lasciare vuoto l'attributo session. (Si veda contesti di persistenza gestiti da Seam )


<transaction:hibernate-transaction session="#{hibernateSession}"/>

Per disabilitare esplicitamente le transazioni gestite da Seam si dichiari in components.xml:


<transaction:no-transaction />

Per configurare le transazioni gestite da Spring si veda uso di Spring PlatformTransactionManagement .

Se si usa Seam fuori dall'ambiente Java EE 5, non si può fare affidamento al container per gestire il ciclo di vita del contesto di persistenza. Anche in ambiente Java EE 5, si potrebbero avere applicazioni complesse con molti componenti disaccoppiati che collaborano assieme nello scope di una singola conversazione, ed in questo caso si potrebbe ritenere che la propagazione del contesto di persistenza tra componenti sia insidiosa ed incline a errori.

In entrambi i casi occorre usare nei componenti un contesto di persistenza gestito (per JPA) od una sessione gestita (per Hibernate). Un contesto di persistenza gestito da Seam è soltanto un componente predefinito di Seam che gestisce un istanza di EntityManager o Session nel contesto di conversazione. Questo può essere iniettato con @In.

I contesti di persistenza gestiti da Seam sono estremamente efficienti in un ambiente cluster. Seam è capace di eseguire un'ottimizzazione che la specifica EJB 3.0 non consente di usare ai container per contesti di persistenza estesi gestiti dal container. Seam supporta un failover trasparente dei contesti di persistenza estesi, senza il bisogno di replicare i contesti di persistenza tra i nodi. (Si spera che nella prossima revisione della specifica EJB questo problema venga corretto.)

E' facile configurare un contesto di persistenza gestito. Si scriva in components.xml:


<persistence:managed-persistence-context name="bookingDatabase" 
                                  auto-create="true"
                   persistence-unit-jndi-name="java:/EntityManagerFactories/bookingData"/>

Questa configurazione crea un componente Seam con scope conversazione chiamato bookingDatabase, il quale gestisce il ciclo di vita delle istanze EntityManager per l'unità di persistenza (istanza EntityManagerFactory) con il nome JNDI java:/EntityManagerFactories/bookingData.

Certamente occorre assicurarsi che EntityManagerFactory sia stato associato a JNDI. In JBoss si può fare ciò aggiungendo la seguente proprietà a persistence.xml.


<property name="jboss.entity.manager.factory.jndi.name" 
          value="java:/EntityManagerFactories/bookingData"/>

Ora si può iniettare EntityManager usando:

@In EntityManager bookingDatabase;

Se si usa EJB3 e si marca una classe od un metodo con @TransactionAttribute(REQUIRES_NEW) allora la transazione ed il contesto di persistenza non dovrebbero essere propagati sulle chiamate del metodo sull'oggetto. Comunque il contesto di persistenza gestito da Seam viene propagato a qualsiasi componente dentro la conversazione, verrà propagato ai metodi marcati con REQUIRES_NEW.QUindi, se si marca un metodo con REQUIRES_NEW, allora bisognerebbe accedere all'entity manager usando @PersistenceContext.

I contesti di persistenza con scope conversazione consentono di programmare transazioni ottimistiche che propagano richieste multiple al server senza il bisogno di usare l'operazione merge(), senza il bisogno di ricaricare i dati all'inizio di ogni richiesta, e senza il bisogno di scontrarsi con LazyInitializationException o NonUniqueObjectException.

Come ogni altra gestione ottimistica delle transazioni , l'isolamento e la consistenza delle transazioni può essere ottenuta tramite l'uso del lock ottimistico. Fortunatamente sia Hibernate che EJB 3.0 semplificano l'uso del lock ottimistico, fornendo l'annotazione @Version.

DI default il contesto di persistenza viene "flushato" (sicronizzato con il database) alla fine di ogni transazione. Questo è a volte il comportamento desirato. Ma molto spesso si preferisce che tutti i cambiamenti siano mantenuti in memoria e scritti nel database solo quando la conversazione termina con successo. Questo consente conversazioni veramente atomiche. Come risultato di una decisione molto stupida e poco lungimirante da parte di alcuni (non-JBoss, non-Sun e non-Sybase) membri del gruppo esperti EJB 3.0, non c'è attualmente nessun modo semplice, utilizzabile e portabile per implementare conversazioni atomiche usando la persistenza EJB 3.0. Comunque Hibernate fornisce questa funzionalità come estensione vendor a FlushModeType definito dalla specifica, e ci si attende che altri vendor presto forniscano una simile estensione.

Seam consente di specificare FlushModeType.MANUAL all'inizio di un conversazione. Attualmente questo funziona solo quando Hibernate è il provider di persistenza sottostante, ma si è pianificato di supportare altre estensioni dei vendor.

@In EntityManager em; //Seam-managed persistence context


@Begin(flushMode=MANUAL)
public void beginClaimWizard() {
    claim = em.find(Claim.class, claimId);
}

Ora l'oggetto claim viene gestito nel contesto di persistenza per il resto della conversazione. Si possono apportare modifiche a claim:

public void addPartyToClaim() {

    Party party = ....;
    claim.addParty(party);
}

Ma questi cambiamenti non verranno eseguiti nel database finché non si forza esplicitamente il flush:

@End

public void commitClaim() {
    em.flush();
}

Certamente si può impostare flushMode a MANUAL da pages.xml, per esempio in una regola di navigazione:


<begin-conversation flush-mode="MANUAL" />

Si può impostare qualsiasi Contesto di Persistenza Gestito da Seam alla modalità flush manuale:

<components xmlns="http://jboss.com/products/seam/components"
   xmlns:core="http://jboss.com/products/seam/core">
   <core:manager conversation-timeout="120000" default-flush-mode="manual" />
</components
>

L'interfaccia EntityManager consente di accedere all'API specifica dei vendor tramite il metodo getDelegate(). Naturalmente il vendor più interessante è Hibernate, e l'interfaccia delegate più potente è org.hibernate.Session. Se si deve usare un diverso provider JPA si veda Uso di provider JPA alternativi.

Ma indipendentemente dal fatto che si stia usando Hibernate (se siete dei geni!) o altro (se siete dei masochisti, o semplicemente non siete troppo svegli), quasi sicuramente, di quando in quando, nei componenti Seam si vorrà usare il delegato. Un approccio potrebbe essere il seguente:

@In EntityManager entityManager;


@Create
public void init() {
    ( (Session) entityManager.getDelegate() ).enableFilter("currentVersions");
}

Tuttavia i cast fra tipi sono senza discussione la sintassi più repellente del linguaggio java, così che la maggior parte della gente li evita quando possibile. Ecco un modo diverso per ottenere il delegato. Innanzitutto si aggiunga la linea seguente in components.xml:


<factory name="session" 
         scope="STATELESS" 
         auto-create="true" 
         value="#{entityManager.delegate}"/>

Ora si può iniettare la sessione direttamente:

@In Session session;


@Create
public void init() {
    session.enableFilter("currentVersions");
}

Seam fa da proxy all'oggetto EntityManager o all'oggetto Session ogni volta che si utilizza un contesto di persistenza gestito da Seam o si inietta un contesto di persistenza gestito dal container usando @PersistenceContext. Ciò permette di utilizzare le espressioni EL nelle stringhe delle query, in modo sicuro ed efficiente. Per esempio:

User user = em.createQuery("from User where username=#{user.username}")

         .getSingleResult();

è equivalente a:

User user = em.createQuery("from User where username=:username")

         .setParameter("username", user.getUsername())
         .getSingleResult();

Certamente non si dovrà mai e poi mai scrivere qualcosa del tipo:

User user = em.createQuery("from User where username=" + user.getUsername()) //BAD!

         .getSingleResult();

(è inefficiente e vulnerabile ad attacchi di SQL injection.)

La funzionalità più bella e unica di Hibernate sono i filtri. I filtri permettono di fornire una vista ristretta dei dati esistenti nel database. E' possibile scoprire di più riguardo ai filtri nella documentazione di Hibernate. Abbiamo tuttavia pensato di menzionare un modo facile di incorporare i filtri in un'applicazione Seam, un modo che funziona particolarmente bene con il "Seam Application Framework".

I contesti di persistenza gestiti da Seam possono avere una lista di filtri definiti, che verrà abilitata quando viene creato un EntityManager od una Session di Hibernate. (Certamente possono essere utilizzati solo quando Hibernare è il provider di persistenza sottostante.)


<persistence:filter name="regionFilter">
    <persistence:name
>region</persistence:name>
    <persistence:parameters>
        <key
>regionCode</key>
        <value
>#{region.code}</value>
    </persistence:parameters>
</persistence:filter>

<persistence:filter name="currentFilter">
    <persistence:name
>current</persistence:name>
    <persistence:parameters>
        <key
>date</key>
        <value
>#{currentDate}</value>
    </persistence:parameters>
</persistence:filter>

<persistence:managed-persistence-context name="personDatabase"
    persistence-unit-jndi-name="java:/EntityManagerFactories/personDatabase">
    <persistence:filters>
        <value
>#{regionFilter}</value>
        <value
>#{currentFilter}</value>
    </persistence:filters>
</persistence:managed-persistence-context
>