martedì 11 novembre 2008

Container Managed Transaction

INTRODUZIONE

Nella teoria dei database si definisce transazione un programma in esecuzione (processo) che opera su un DB.  Classici esempi di transazioni sono la prenotazione di un volo aereo o un bonifico bancario. La corretta prenotazione di un volo aereo richiede che:

  • venga prenotato un posto sull' aereo e contestualmente che venga effettuato il pagamento; se una delle due operazioni fallisce anche l'altra deve essere annullata (Atomicità),
  • dopo la prenotazione sia verificato, tra l'altro, chela somma dei posti liberi e quelli occupati rimanga invariata (Coerenza),
  • sia possibile eseguire una nuova prenotazione aerea parallelamente ad essa e che l'eventuale fallimento di quest'ultima non influisca sull'esito della nuova prenotazione (Isolamento),
  • dopo la prenotazione sia garantito al passeggero che il posto non venga assegnato ad un altro passeggero fino all'imbarco (Durabilità).

I quattro requisiti enumerati sono esempi di proprietà che una qualunque transazione dovrebbe avere e che sono raggruppate nell'acronimo ACID.

  • Atomicità: la transazione deve essere eseguita nella sua interezza o non eseguita per niente, non sono ammesse esecuzioni parziali;
  • Coerenza (o Consistenza): la transazione inizia e finisce trovando e lasciando il database in uno stato consistente, uno stato in cui i dati presenti nel database non violano eventuali vincoli di integrità;
  • Isolamento: ogni transazione deve essere eseguita in modo indipendente dalle altre e non devono essere visibili i suoi risultati intermedi. L'eventuale fallimento di una non deve influire sul buon esito delle altre;
  • Durabilità (o Persistenza): una volta che una transazione è finita i suoi effetti devono perdurare nel tempo.

Un discorso a parte meriterebbe la proprietà di isolamento, qui ci limitiamo solo a definire brevemente i quattro livelli previsti di isolamento con semplici esempi:

  • read uncommitted:
    • consente l'accesso in lettura, bloccando la scrittura, a dati che potrebbero essere modificati da transazioni parallele
    • in questo modo potrebbero verificarsi letture sporche, letture di dati che portebbero sparire in futuro se la transazione che li ha generati abortisce

per esempio consideriamo le seguenti operazioni effettuate dalle transazioni T1 e T2 in ordine cronologico:


T1
T2
insert into mytable(id, name)  values('1','xxx');


select * from mytable where id=1;
rollback


T2 ha avuto accesso ad un dato che non è mai esistito!

  • read committed:
    • prevede il rilascio dei dati in lettura, ritardando quelli in scrittura
    • si potrebbe verificare il problema delle letture non-ripetibili

per esempio consideriamo la seguente situazione

T1
T2
select * from mytable where id=1;


update mytable set mycolumn=1 where id=1;
commit;
select * from mytable where id=1;


due letture consecutive di una stessa riga da parte di T1 producono risultati diversi

  • repeatable read:
    • vengono bloccati gli accessi sia in lettura che in scrittura ma solo delle tuple coinvolte
    • si potrebbe verificare il problema dei fantasmi:

per esempio consideriamo la seguente situazione

T1
T2
select * from mytable where age betwenn 10 AND 30;


insert into mycolumn values(1,"mario",27);
commit;
select * from mytable where age betwenn 10 AND 30;


due query effettuate consecutivamente da T1 restituiscono un numero di righe diverse

  • serializable:
    • garantisce isolamento totale, come se le transazioni vengono effettuate in sequenza
    • poco efficiente


CONTAINER-MANAGED TRANSACTIONS (CMT)

In un session o message-driven bean si può delegare la gestione delle transazioni al EJB container. Questo facilita lo sviluppo di un EJB in quanto non bisogna definire esplicitamente quando inizia e finisce una una transazione. Tipicamente il container inizia una transazione prima di eseguire il contenuto di un metodo e la termina dopo aver eseguito l'ultima istruzione del metodo stesso. In questo modo una transazione corrisponde ad un metodo anche se non vale necessariamente il viceversa cioè che ad ogni metodo corrisponda una transazione. Negli EJB che implementano CMT non è possibile usare alcuna istruzione che interferisca con la gestione delle transazioni da parte del container, per esempio i metodi commit, setAutoCommit e rollback di java.sql.Connection e commit e rollback di javax.jms.Session. Non è inoltre posisbile usare l'interfaccia javax.transaction.UserTransaction.

Dato un metodo che definisce una transazione è possibile controllare lo scope di tale transazione attraverso degli attributi


Data una situazione in cui method-a() di Bean-1 inizia una transazione e invoca method-b() di Bean-2, a seconda delle esigenze è necessario definire un attributo di transazione per method-b(), per esempio se il container deve iniziare una nuova transazione in cui eseguire  method-b().

Attributi di transazione

L'attributo di transazione può assumere uno dei seguenti valori possibili:
  • Required: prevede che method-b() sia eseguito all'interno della transazione creata per method-a(), se method-a() non è associato ad alcuna transazione preesistente allora il container inizia una nuova transazione prima di eseguire method-b(). Required rappresenta l'attributo di default per CMT e in genere non si specifica se non quando si ha la necessità di sovrascrivere un'altro attributo di transazione. 
  • RequiresNew: prevede che quando method-a() invoca method-b(), il container effettui i seguenti passi:
    1. sospende la transazione di method-a()
    2. inizia una nuova transazione
    3. delega la chiamata a method-b()
    4. riesuma la transazione di method-a() dopo che method-b() è stato completato
Se method-a() non è associato ad alcuna transazione, il container inizia in ogni caso una nuova transazione prima di eseguire il method-b(). Tipicamente si usa RequiresNew quando si vuole assicurare che method-b() sia eseguito sempre all'interno di una nuova transazione
  • Mandatory: se method-a() ha iniziato una transazione e invoca method-b(), il metodo viene eseguito all'interno della stessa transazione, altrimenti il container solleva una TransactionRequiredException.
  • NotSupported: se method-a() ha iniziato una transazione prima di invocare method-b(), tale transazione viene sospesa e ripresa  dopo che method-b() viene terminato. Se method-a() non ha iniziato alcuna transazione, il container non ne crea una nuova. Questo attributo viene usato quando method-b() non ha necessità di gestire una transazione. Considerando che in genere l'uso delle transazioni è molto costoso in termini di risorse, l'attributo NotSupported potrebbe incrementare le performance.
  • Suports: se method-a() ha iniziato una transazione prima di invocare method-b(), tale metodo viene eseguito all'interno della stessa transazione; viceversa method-b() non viene eseguito all'interno di una transazione. Considerando che il comportamento del container nell'esecuzione di method-b() può variare, è necessario usare questo attributo con molta cautela.
  • Never: se method-a() ha iniziato una transazione prima di invocare method-b(), il container solleva un'eccezione RemoteException, altrimenti il container non inizia una nuova transazione prima di eseguire method-b().

La seguente tabella riassume gli effetti dell'utilizzo dei vari attributi di transazione.


Transaction Attribute
Client's Transaction
Business Method's Transaction
Required

None
T2
T1
T1
RequiresNew

None
T2
T1
T2
Mandatory

None
error
T1
T1
NotSupported

None
None
T1
None
Supports

None
None
T1
T1
Never

None
None
T1
Error



Sia T1 che T2 sono transazioni controllate dal container. La transazioe T1 è associata a un metodo method-a() di Bean-1 che invoca il metodo method-b() il quale in genere può essere definito in un nuovo EJB Bean-2. T2 viene iniziato dal container perima dell'esecuzione di method-b(). Nell'ultima colonna la parola None indica che method-b() non viene eseguito all'interno di una transazione controllata dal container. In ogni caso le chiamate a DB in tale metodo potrebbero essere controllate dal transaction manager del DBMS.

Settaggio degli attributi di transazione

Nelle specifiche EJB gli attributi di transazione vengono definite usando l'annotazione javax.ejb.TransactionAttribute, s settandola ad una delle costanti javax.ejb.TransactionAttributeType. L'annotazione può essere usata per tutta la classe o per il singolo metodo; nel primo caso il TransactionAttributeType viene applicato a tutti i business method della stessa classe. Se l'annotazione viene definita sia sulla classe che su un particolare metodo, viene applicato il TransactionAttributeType definito per il metodo, in modo tale da poter definire una regola generale per il bean e eccezioni per i singoli metodi. Di seguito viene elencato per ogni attributo di transazione il TransactionAttributeType corrispettivo:
  • Required: TransactionAttributeType.REQUIRED
  • RequireNew: TransactionAttributeType.REQUIRES_NEW
  • Mandatory: TransactionAttributeType.MANDATORY
  • NotSupported: TransactionAttributeType.NOT_SUPPORTED
  • Supports: TransactionAttributeType.SUPPORTS
  • Never: TransactionAttributeType.NEVER

Il seguente frammento di codice illustra come usare l'annotazione TransactionAttribute:

@TransactionAttribute(NOT_SUPPORTED)
@Stateful
public class TransactionBean implements Transaction {
...
@TransactionAttribute(REQUIRES_NEW)
public void firstMethod() {...}
   
@TransactionAttribute(REQUIRED)
public void secondMethod() {...}
   
public void thirdMethod() {...}
   
public void fourthMethod() {...}
}

In questo esempio, l'attributo di transazione per la classe TransactionBean è stato settato a NotSuported. Questo vuol dire che per ogni metodo dello stesso EJB, dove non specificato diversamente, vale il TransactionAttributeType NOT_SUPPORTED. firstMethod è stato settato a RequiresNew, e secondMethod è stato settato a Required. Considerando che il TransactionAttribute del metodo sovrascrive il TransactionAttribute della classe, una chiamata a firstMethod implicherà la creazione di una nuova transazione, mentre una chiamata  a secondMethod implicherà che tale metodo verrà eseguito nella stessa transazione del metodo in cui avviene l'invocazione, se esiste, o in una nuova transazione, se non esiste. Le chiamate a firstMethod e firstMethod avverrano senza alcuna transazione.
Esistono due modi di effettuare il Roll-back di una transazione CMT:
  • attraverso un'eccezione di sistema, nel qual caso il container automaticamente effettua il roll-back della transazione,
  • invocando il metodo setRollbackOnly dell'interfaccia EJBContext. In questo caso il bean istruisce il container di effettuare il roll-back. Se il bean solleva una eccezione a livello si applicazione, il roll-back non è automatico, ma può essere iniziato attraverso una chiamata a setRollbackOnly.


Implementando l'interfaccia SessionSynchronization è possibile sincronizzare le variabili d'istanza di un session bean attraverso messaggi di notifica di sincronizzazione di una transazione. Per esempio, attraverso questo meccanismo, è possibile sincronizzare le variabili di un EJB con i corrispettivi valori presenti nel DB. Il container invoca i metodi afterBegin, beforeCompletion e afterCompletion, esportati dall'interfaccia  SessionSynchronization ad ogni stato principale di una transazione. In particolare:
  • il metodo afterBegin informa l'istanza del EJB che una nuova transazione è stata iniziata; il container invoca questo metodo subito prima di eseguire il business method.
  • il metodo beforeCompletion viene invocato dal container appena il business method è stato completato ma prima di 'committare' la transazione. In questo modo il bean ha l'ultima opportunità di effettuare il rollback della transazione con una chiamata a setRollbackOnly.
  • il metodo afterCompletion indica che la transazione è stata completata. Ha un solo parametro di tipo boolean il cui valore è true se la transazione è andata a buon fine, false altrimenti.





Nessun commento: