La
volta scorsa abbiamo visto come sia possibile fornire un adeguato supporto
per le transazioni ad una applicazione basata su EJB semplicemente facendo
ricorso al motore transazionale offerto dal server EJB.
Una
volta ancora è bene ricordare che questo è sicuramente la
soluzione consigliata specialmente per i programmatori alle prime armi,
e che l’alternativa costituita dalla gestione manuale delle transazioni
è una strada da seguire solo se si hanno esigenze molto particolari
ed una buona conoscenza sia della teoria transazionale che della specifica
EJB.
Quindi,
appare ovvio che la gestione diretta delle transazioni è una scelta
raramente perseguita: ciononostante, sia per completezza sia perché
didatticamente l’argomento è molto interessante per approfondire
la teoria di EJB, affronteremo anche questo argomento.
Ovviamente
cercheremo di limitare la trattazione al caso della gestione delle transazioni
in EJB, rimandando alla bibliografia chi volesse approfondire maggiormente
gli argomenti relativi alla teoria transazionale.
La
specifica EJB prevede che ogni server fornisca un supporto transazionale
a livello di metodi del bean. Questa scelta offre già di per se
una possibilità di configurazione dalla granularità
molto fine, e solo in rari casi si rende necessario scendere ad un
livello di dettaglio maggiore.
Inoltre,
dato che la definizione del comportamento transazionale del bean
avviene in fase di deploy, si può contare su una netta separazione
fra il contesto transazionale e quello di business logic. Una eventuale
modifica effettuata in fase di deploy infatti permette di modificare radicalmente
il comportamento o le prestazioni del bean senza la necessità di
dover modificare la business logic del componente.
La gestione esplicita
delle transazioni
La
gestione manuale, detta anche esplicita, delle transazioni si basa in genere
su un qualche motore sottostante basato sul modello Object Transaction
Model (OMT) definito da OMG: nel caso di Java ad esempio si potrebbe utilizzare
il sistema JTS, che però per quanto potente offre una API piuttosto
complessa e necessita di una chiara visione dell’obiettivo che si vuole
raggiungere.
Una
soluzione sicuramente più semplice è quella offerta dalla
Java Transaction Api (JTA), la quale essendo uscita dopo la versione 1.0
di EJB, non era disponibile inizialmente. Attualmente tale API è
da preferirsi sia che si utilizzi la versione 1.0 che la 1.1.
Dato
che entrambi i sistemi fanno uso della interfaccia UserTransaction i pezzi
di codice che si affronteranno saranno validi in entrambi i casi, a parte
piccole modifiche di configurazione.
La
API JTA è strutturata su due livelli, di cui il più alto
e generico è quello normalmente utilizzato dal programmatore di
applicazioni.
Per
comprendere meglio quando possa essere realmente utile gestire manualmente
le transazioni si prenda in esame il caso in cui un client debba
eseguire le invocazioni dei due metodi remoti del bean prima di procedere
e considerare realmente conclusa l’operazione.
La
transazione logica in questo caso è rappresentata dall’esecuzione
di entrambi i metodi.
Appare
quindi piuttosto evidente quale sia il vantaggio di questa soluzione: il
client (al solito una applicazione o un altro bean) può effettuare
un controllo diretto e preciso sui singoli passi della transazione, avendo
sotto controllo l’inizio e la fine della stessa.
Indipendentemente
dal tipo di client l’operazione da svolgere è la stessa, e si basa
sull’utilizzo di un oggetto UserTransaction, anche se il modo per ottenere
tale oggetto è piuttosto differente a seconda che il client
sia una applicazione stand alone o un bean.
Con
il rilascio della piattaforma J2EE Sun ha ufficialmente indicato
come una applicazione debba ricavare una ricavare UserTransaction
grazie a JNDI: quindi nel caso in cui la specifica di riferimento sia
EJB 1.1 si potrebbe avere del codice di questo tipo”
Context
jndiCtx = new InitialContext();
UserTransaction
usrTrans;
usrTrans
= UserTransaction)jndiCntx.looup("java:comp/UserTRansaction");
usrTrans.begin();
usrTrans.commit();
Nel
caso di EJB 1.0 invece dato che non è stato esplicitamente
indicato il modo per ricavare il riferimento alla UserTransaction, si dovrà
ricorrere agli strumenti messi a disposizione dal server EJB, come API
proprietarie o altri sistemi.
Fortunatamente
nella maggior parte dei casi i vari produttori ricorrono ugualmente a JNDI
per cui il codice precedente potrebbe diventare
UserTransaction
usrTrans;
usrTrans
=(UserTransaction)
jndiCntx.looup("javax.trasaction.UserTransaction");
Detto
questo è interessante notare che anche un bean può gestire
le transazioni in modo esplicito. In EJB 1.1 i cosiddetti “bean
managed transaction ” (in questo caso non è necessario specificare
gli attributi transazionali per i metodi del bean) sono quei session bean
il cui valore dell’attributo transaction-type sia settato in deploy al
valore “bean”: ad esempio
<ejb-jar>
<enterprise-bean>
…
<session>
…
<transaction-type>Bean</transaction-type>
…
Invece
gli entity non possono gestire la modalità “bean managed transaction”.
In
JB 1.0 sia i bean entity che i session possono gestire direttamente
le transazioni in modo diretto ed esplicito, a patto che i vari metodi
siano impostati con il valore transazionale TX_BEAN_MANAGED
Un
bean per gestire la propria transazione deve ricavare al solito un reference
ad un UserTransaction direttamente dall’EJBContext come ad esempio
public
class MyBean extends SessionBean{
SessionContext
ejbContext;
public
void myMethotd(){
try{
UserTransaction usrTrans ;
usrTrans=ejbContext.getUserTransaction();
UsrTrans.begin();
…
UsrTrans.commit();
}
catch(IllegalStateException ise){…}
// gestione di altre eccezioni
…
}
}
In
questo caso si fa uso del contesto di esecuzione del bean per ricavare
direttamente l’oggetto UserTransaction: in EJB 1.1 si sarebbe potuto utilizzare
indifferentemente la tecnica basata su JNDI come si è visto in precedenza.
Nei
session stateless bean (e negli entity in EJB 1.0) una transazione gestita
direttamente tramite l’oggetto UserTransction deve cominciare e terminare
all’interno dello stesso metodo, dato che le stesse istanze dei vari bean
in esecuzione sul server possono essere gestite in modo concorrente
da più client.
Gli
stateful invece, essendo dedicati a servire un solo client per volta, potranno
avere transazioni che iniziano in un metodo e terminano in un altro. Nel
caso in cui il client, appartenente ad un determinato scope transazionale,
invochi su un metodo di un bean in cui sia effettuata una gestione diretta
delle transazioni, si otterrà una sospensione della transazione
del client fino a quando il metodo remoto del bean non abbia terminato;
questo sia che la transazione del metodo del bean inizi all’interno del
metodo stesso, sia che sia iniziata precedentemente all’interno di un altro
metodo.
Quest’ultima
considerazione fa comprendere che la gestione delle transazioni su
più metodi sia fortemente da evitare, dato che introduce un fattore
di complessità sicuramente molto più grande.
La gestione delle
transazioni dal punto di vista del server
Il
server EJB per fornire il supporto transazionale ai vari bean deve fornire
una implementazione delle interfacce UserTransaction e Status:
non è necessario quindi che il server supporti il resto della API
JTA ne che sia utilizzato il sistema JTS.
L’interfaccia
UserTransaction ha la seguente definizione
interface
javax.transaction.UserTransaction {
public
abstract void begin();
public
abstract void commit();
public
abstract void rollback();
public
abstract int getStatus();
public
abstract void setRollbackOnly();
public
abstract void setTransactionTimeout(int secs):
}
Ecco
il significato dei vari metodi e cosa svolgono:
-
begin():
fa partire la transazione ed associa il thread di esecuzione con la transazione
appena creata. Possono essere generate eccezioni di tipo NotSupportedException
(nel caso in cui il thread sia associato ad un’altra transazione)
o di tipo SystemException nel caso in cui il transaction manager incontri
un problema imprevisto.
-
commit():
completa la transazione associata al thread il quale poi non apparterrà
a nessuna transazione. Tale metodo può generare eccezioni di tipo
IllegalStateException (nel caso in cui il thread non appartenesse a nessuna
transazione iniziata in precedenza), oppure SystemException
se come in precedenza dovessero insorgere problemi inaspettati. Una TransactionRolledBackxception
verrà generata se la transazione viene interrotta o se il client
invoca il metodo UserTransaction.rollBackOnly().
Nel
caso peggiore verrà prodotta una HeuristicRollbackxception ad indicare
l’insorgere di una cosiddetta decisione euristica: questo particolare tipo
di evento corrisponde alla decisione presa da uno qualsiasi degli elementi
che prendono parte alla transazione senza nessuna autorizzazione
ne indicazione da parte del transaction manager, effettua una commit o
una rollback. In questo caso la transazione perde ogni livello di atomicità
e la consistenza dei dati non può essere in alcun modo essere considerata
affidabile.
-
rollback():
provoca una rollback della transazione. Una SecurityException verrà
prodotta se il thread non è autorizzato ad effettuare la rollback;
anche in questo caso verrà generata una IllegalStateException, se
il thread non è associato a nessuna transazione.
-
setRollBackOnly():
imposta la modalità rollback forzata, provocando obbligatoriamente
la generazione di una rollback in ogni caso. Come in precedenza verrà
generata una IllegalStateException, se il thread non dovesse essere associato
a nessuna transazione, una SystemException invece verrà lanciata
se il transaction manager dovesse incontrare un problema imprevisto.
-
setTransactionTimeout(int
secs): imposta il tempo massimo entro il quale la transazione debba
essere conclusa. Se tale valore non viene impostato, il server utilizzerà
quello di default che è dipendente dalla particolare implementazione.
Una SystemException verrà lanciata se il transaction manager dovesse
incontrare un problema imprevisto
-
getStatus():
per chi volesse scendere ad un livello più dettagliato di controllo
tramite tale metodo è possibile ricevere un valore intero da confrontare
con le costanti memorizzate nella classe Status la cui definizione
è riportata di seguito
interface
javax.transaction.Status{
public
final static int STATUS_ACTIVE;
public
final static int STATUS_COMMITTED;
public
final static int STATUS_COMMITTING;
public
final static int STATUS_MARKED_ROLLBACK;
public
final static int STATUS_NO_TRANSACTION;
public
final static int STATUS_PREPARED;
public
final static int STATUS_PREPARING;
public
final static int STATUS_ROLLED_BACK;
public
final static int STATUS_ROLLING_BACK;
public
final static int STATUS_UNKNOWN;
}
Il
significato di tali valori dovrebbe essere piuttosto intuitivo, per cui
non ci dilungheremo oltre nella loro analisi.
Considerazioni
sulla gestione manuale delle transazioni
Come
si è avuto modo di constatare la gestione diretta delle transazioni
sicuramente rappresenta un meccanismo potente per controllare più
nel dettaglio i vari aspetti della applicazione sia nella parte di business
logic che relativamente alla parte di gestione del ciclo di vita
del bean (metodi di callback).
Tale
potenza espressiva introduce un livello di complessità che tipicamente
non trova giustificazione nella maggior parte dei casi: oltre a richiedere
una maggiore conoscenza della teoria transazionale, questa soluzione va
contro il principio di separazione fra business logic e motore transazionale
come invece avviene quando si rimanda la definizione del comportamento
transazionionale alla fase di deploy tramite il deployment descriptor.
Questo
è forse il maggior aspetto di cui tener conto, dato che rappresenta
anche la maggior potenza del modello EJB.
In
definitiva l’esperienza insegna che nella stragrande maggioranza dei casi
è meglio affidarsi ai sistemi offerti dal server EJB, eventualmente
giocando in modo opportuno sulla configurazione dei vari bean.
La gestione delle
eccezioni nell’ambito delle transazioni
Per
le eccezioni in Java la classificazione principale vede due principali
categorie, le checked e le unchecked. Nell’ambito di EJB ed in particolare
della gestione delle transazioni, è utile analizzare le cose sotto
un punto di vista leggermente differente, distinguendo fra application
exception e system exception (tutte quelle che derivano da RuntimeException
RemoteException e sotto tipi come le EJBException).
Nel
caso di una system exception viene effettuato un rollback automatico della
transazione, cosa che non avviene nel caso di application exception. Questo
vale sia per le eccezioni prodotte all’interno dei metodi di business logic
che nel caso dei metodi di callback.
In
ogni caso la specifica 1.1 impone al server di effettuare un log dell’evento
generatosi, anche se poi per la particolare tecnica scelta dipende molto
dalla particolare implementazione.
Quando
una system exception viene generata il bean viene dereferenziato e rimosso
dalla memoria: questa operazione ha importanti conseguenze a seconda che
si tratti di un entity di un session stateless o di uno stateful.
Nei
primi due casi infatti il client non riceve nessuna informazione circa
questo evento, ne il flusso delle operazioni potrebbe risentirne,
visto che tali bean non sono dedicati esclusivamente ad un particolare
client e, secondo la logica dello scambio di contesto visto in precedenza,
servono più client.
Il
bean verrà quindi rimosso ed al suo posto ne verrà messo
un nuovo appena istanziato.
Nel
caso invece di uno stateful le cose sono completamente differenti, visto
che esiste un legame stretto fra il bean ed il client: essendo il bean
non più presente in memoria, tutte le invocazioni sui metodi da
parte del client porteranno a sua volta alla generazione di una eccezione
di tipo NoSuchObjectException.
Quando
una system exception viene generata, ed il bean viene rimosso, il client
riceve sempre una eccezione remota (RemoteExcetpion o sottotipo a seconda
del caso).
Se
era stato il client a far partire la transazione, allora l’eccezione generata
dal metodo del bean verrà intrappolata e reinnoltrata sotto forma
di TransactionRolledbackException. Tutti gli altri casi il container effettuerà
la stessa operazione, ma inoltrerà una più generica RemoteException.
Le
application exceptions invece si verificano in corrispondenza di errori
legati alle operazioni di business logic: queste sono in genere direttamente
inoltrate al client e non provocano la rollback della transazione; il client
può quindi agire in modo da recuperare lo stato effettuato le opportune
operazioni di emergenza.
In
questo caso il progettista della business logic dovrà fare particolare
attenzione a porre i passaggi a rischio (dove potrebbero verificarsi
le application exception) prima dell’esecuzione delle operazioni relative
al sistema sottostante, al fine di effettuare operazioni inutili o comunque
dannose.
Bibliografia
[EJB]
“Enterprise Java Bean™ Tecnoloy”
http://java.sun.com/products/ejb/index.html
[EJB2]
”Enterprise Java Beans, 2’ Edizione” – di Richard Monson Haefel – Ed.
O’Reilly
[JNDI]
“JNDI documentation”
http://java.sun.com/products/jndi/docs.html
[BAS]
“Sviluppo di EJB con JBuilder “
http://community.borland.com/java/all/1,1435,c|3|13,00.html
[BEA]
Documentazione Bea su WebLogic ed EJB http://www.weblogic.com/docs/resources.html
[EJBDesign1]
– “ EJB’s Design Techniques – I parte” – di Raffaele Spazzoli,
MokaByte
47 – http://www.mokabyte.it/2001/01
[EJBDesign]
– “ EJB’s Design Techniques – II parte” – di Raffaele Spazzoli,
MokaByte
48 – http://www.mokabyte.it/2001/02 |