MokaByte 56 - 8bre  2001
Corso di EJB
VIII parte: la gestione esplicita delle transazioni
di
Giovanni
Puliti
Si conclude questo mese la trattazione su EJB, affrontando l’ultimo argomento rimasto in sospeso, ovvero la gestione manuale delle transazioni

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

Vai alla Home Page di MokaByte
Vai alla prima pagina di questo mese
MokaByte® è un marchio registrato da MokaByte s.r.l.
Java®, Jini®  e tutti i nomi derivati sono marchi registrati da Sun Microsystems; tutti i diritti riservati.
E' vietata la riproduzione anche parziale. Per comunicazioni inviare una mail a info@mokabyte.it