SeamFramework.orgCommunity Documentation
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:
Il ciclo di vita del bean di sessione stateful deve essere gestito manualmente via codice a livello web (risulta che questo è un problema sottile e molto più difficile in pratica di quanto sembri).
La propagazione del contesto di persistenza tra componenti stateful nella stessa transazione ottimistica è possibile, ma pericolosa.
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.
La richiesta può comportare di essere processata da diversi componenti poco accoppiati, ciascuno dei quali viene chiamato indipendentemente dal layer web. E' comune vedere parecchie chiamate per richiesta dal layer web ai componenti EJB in Seam.
La generazione della vista può richiedere il lazy fetching delle associazioni.
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:
occorre utilizzare un contesto di persistenza esteso con scope conversazionale, invece che transazionale
occorre usare due transazione per richiesta; la prima si estende dall'inizio della fase di ripristino della vista, o "restore view phase", (qualche transaction manager inizia la transazione più tardi, all'inizio della fase di applicazione dei valori della richiesta, o "apply request values phase") alla fine della fase di chiamata all'applicazione, o "invoke application phase"; la seconda copre la fase di rendering della risposta, o "render response phase"
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.
La gestione delle tarnsazioni di Seam è abilitato di default per tutte le richieste JSF. Se si desidera disabilitare questa funzionalità, è possibile farlo in components.xml
:
<core:init transaction-management-enabled="false"/>
<transaction:no-transaction />
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à:
Transazioni JPA di tipo RESOURCE_LOCAL con interfaccia javax.persistence.EntityTransaction
. EntityTransaction
inizia la transazione all'inizio della fase "apply request values".
Transazioni gestite da Hibernate con l'interfaccia org.hibernate.Transaction
. HibernateTransaction
da inizio alla transazione all'inizio della fase "apply request values".
Transazioni gestite da Spring con l'interfaccia org.springframework.transaction.PlatformTransactionManager
. Il gestore PlatformTransactionManagement
di Spring può cominciare la transazione all'inizio della fase "apply request values" se è stato valorizzato l'attributo userConversationContext
.
Disabilitare esplicitamente le transazioni gestite da Seam
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 .
La sincronizzazione della transazioni fornisce callback per gli eventi relazionati alle transazioni come beforeCompletion()
e afterCompletion()
. Di default, Seam usa un proprio componente per la sincronizzazione delle transazioni, il quale richiede un uso esplicito del componente per le transazioni di Seam quando si esegue il commit di una transazione per assicurarsi che le callback vengano correttamente eseguite. Se si è in ambiente Java EE 5, il componente <transaction:ejb-transaction/>
dovrebbe essere dichiarato in components.xml
per assicurarsi che le callback per la sincronizzazione di Seam vengano correttamente chiamate se il container esegue il commit di una transazione non nota a Seam.
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.
Le sessioni Hibernate gestite da Seam sono simili. In components.xml
:
<persistence:hibernate-session-factory name="hibernateSessionFactory"/>
<persistence:managed-hibernate-session name="bookingDatabase"
auto-create="true"
session-factory-jndi-name="java:/bookingSessionFactory"/>
Dove java:/bookingSessionFactory
è il nome della session factory specificata in hibernate.cfg.xml
.
<session-factory name="java:/bookingSessionFactory">
<property name="transaction.flush_before_completion"
>true</property>
<property name="connection.release_mode"
>after_statement</property>
<property name="transaction.manager_lookup_class"
>org.hibernate.transaction.JBossTransactionManagerLookup</property>
<property name="transaction.factory_class"
>org.hibernate.transaction.JTATransactionFactory</property>
<property name="connection.datasource"
>java:/bookingDatasource</property>
...
</session-factory
>
Si noti che Seam non esegue il flush della sessione, quindi occorre sempre abilitare hibernate.transaction.flush_before_completion
per assicurarsi che di eseguire il flush della sessione prima che venga fatto il commit della transazioni JTA.
Ora si può iniettare nei componenti JavaBean una Session
di Hibernate gestita usando il seguente codice:
@In Session bookingDatabase;
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; //a 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
>