CERCA SITEMAP FEED RSS 1280
Ultimo aggiornamento: 30 Agosto 2009

Java Transaction API (JTA)

La gestione delle transazioni costituisce un aspetto importante di qualsiasi applicazione enterprise.
Una transazione è un insieme di task che devono essere processati come se costituissero una singola unità di elaborazione.
Questo essenzialmente significa che affinchè una transazione abbia successo è necessario che ogni singolo task che la costituisce sia portato a compimento, altrimenti la transazione fallisce.
Se una transazione ha successo i suoi risultati diventano persistenti, altrimenti viene effettuato il roll back e viene ripristinato lo stato antecedente alla transazione come se nulla fosse successo.
Tutti i sistemi transazionali esibiscono quattro caratteristiche note con l’acronimo ACID: atomicità, consistenza, isolamento, durevolezza.
Atomicità significa che i task che costituiscono la transazione costituiscono un’unica unità di elaborazione pertanto devono essere eseguiti tutti affinchè la transazione possa considerarsi completata positivamente.
Consistenza significa che se il sistema si trova in uno stato consistente con le regole di business prima dell’invocazione della transazione, allora successivamente all’invocazione dovrà trovarsi ancora in uno stato consistente.
L’isolamento è quella proprietà che assicura che le transazioni non si influenzino vicendevolmente.
Essenzialente il transaction manager assicura che nessuno tocchi i dati di una transazione quando questa viene eseguita: tale concetto è fondamentale nell’ambito dei sistemi concorrenti dove un certo numero di processi possono accedere agli stessi dati nello stesso tempo.
Di solito l’isolamento è garantito dall’uso di meccanismi di lock dei dati nascosti allo sviluppatore così che nessuno possa manipolare i dati finchè la transazione non è finita.
Duravolezza significa che se una transazione che è stata completata con successo i suoi effetti diventano permanente.
Il componente che si occupa di gestire le transazioni per una particolare risorsa è detto resource manager: in generale una risorsa può essere un database, un message server o un enterprise information system.
Una transazione che fa uso di una sola risorsa è detta locale ma è comune il caso in cui una transazione coinvolga l’utilizzo di più risorse.
Affinchè sia possibile gestire risorse multiple con una singola transazione è necessario sviluppare una qualche forma di astrazione: il transaction manager è un componente che coordina una transazione su risorse distribuite multiple.
Dal punto di vista dell’applicazione, il transaction manager è l’application server (o qualceh componente esterno) che fornisce servizi di transazione semplificati.
L’applicazione chiede al transaction manger di iniziare, confermare o effettuare il roll back di transazioni.
Il transaction manger coordina tali richieste verso più resource manager che gestiscono le risorse locali.

Two-phase commit

Il modo in cui le transazioni sono gestite in ambienti distribuiti che coinvolgono più di una risorsa è estremamente interessante.
Il protocollo comunemente usato è detto two-phase commit.
Supponiamo che una transazione comporti operazioni su più database e supponiamo che le operazioni sul primo database abbiano successo mentre quelle sul secondo falliscano.
Dal momento che la transazione nel suo complesso fallisce è opportuno annullare le modifiche apportate sul primo database. Affinchè ciò sia possibile, il protocollo two-phase commit opera nel seguente modo: durante il primo step, ogni resource manager coinvolto viene interrogato circa la possibilità di poter concludere con successo la transazione, se almeno uno dei resource manager indica che la transazione non può essere completata allora la transazione fallisce altrimenti la transazione viene eseguita chiedendo a tutti i resource manager di effettuarne il commit.

Gestione delle transazioni in Enterprise JavaBeans

Il supporto delle transazioni in EJB è fornito da Java Transaction API, una piccola API di alto livello che espone le funzioni del transaction manager tipicamente fornite dall’application server.
Il container in generale mantiene nascosti la maggior parte dei dettagli relativi alla gestione delle transazioni così che allo sviluppatore sia sufficiente indicare esclusivamente l’inizio e la fine della transazione e l’eventuale roll back/commit della stessa. Esistono due distinti modi di utilizzare le transazioni in EJB.
Il primo consiste nel gestire le transazioni dichiarativamente mediante CMT (container-managed transaction): ciò può essere fatto mediante annotazioni o mediante il deployment descriptor.
Il secondo consiste nel gestire le transazioni da programma mediante BMT (bean-manager transaction).
IN EJB 3 solo session bean e message-driven bean sopportano BMT e CMT.

Container-managed transactions (CMT)

In una container-managed transaction, il container si occupa di iniziare la transazione ed effettuarne il commit e il rollback al posto dello sviluppatore.
I confini della transazione sono marcati dall’inizio e la fine di un metodo business di un EJB: più precisamente il container inizia la transazione prima che il metodo sia invocato, invoca il metodo e quindi in base a cosa accade ne effettua il commit o il roll back.
Tutto ciò che bisogna fare è dire al container come deve gestire le transazioni mediante l’uso delle annotazioni o del deployment descriptor e chiedere di affettuare il roll back quando è necessario.
Di default si assume che si stia facendo uso di CMT su tutti i metodi di business.
Supponiamo di voler implementare un semplice sistema per il noleggio delle automobili.
Disponiamo quindi di un piccolo database costituito da due tabelle: una tabella cliente che ha un idcliente e una tabella auto che ha un idauto e un campo disponibilità che contiene l’id del cliente che ha noleggiato l’auto o null se l’auto è disponibile.
Il noleggio delle automobili comporta la modifica delle due tabelle: la tabella clienti infatti dovrà contenere l’id del nuovo cliente che noleggia l’automobile e la tabella auto dovrà aggiornare il campo disponibilità dell’automobile noleggiata con l’id del cliente che la sta noleggiando.
Se l’automobile è stata noleggiata da un altro cliente e quindi il campo disponibilità non è null allora nessuna delle due tabelle deve essere modificata.
E’ possibile implementare questa semplice operazione definendo un session bean stateless la cui interfaccia espone un metodo pubblico noleggiaAuto transazionale:
package miopackage;
import javax.ejb.Local;
   
@Local
public interface NoleggiaBeanLocal 
{
  public void noleggiaAuto(String idcliente, String idauto);
}
L’implementazione dell’interfaccia è la seguente:
package miopackage;
   
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import javax.annotation.Resource;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionManagementType;
import javax.ejb.TransactionManagement;
import javax.sql.DataSource;
   
@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
@TransactionAttribute(TransactionAttributeType.REQUIRED)    
public class NoleggiaBean implements NoleggiaBeanLocal
{
  @Resource(name="jdbc/miodb", mappedName="java:jdbc/miodb")
  private DataSource datasource;
  
  @Resource
  private SessionContext sessioncontext;
         
  public NoleggiaBean() 
  {
  }
      
  public void noleggiaAuto(String idcliente, String idauto)
  {
    try
    {
      insertCliente(idcliente);
      updateAuto(idauto,idcliente);
    }
    catch(Exception ecc)
    {
      sessioncontext.setRollbackOnly();   
    }
  }
      
  private void updateAuto(String id, String idcliente) throws Exception
  {
    Connection connessione = datasource.getConnection();
    Statement statement = connessione.createStatement();
    ResultSet resultset = statement.executeQuery("select disponibilita from auto where id='"+id+"' and disponibilita is null");
    
    if (resultset.first())
    {
      statement.executeUpdate("update auto set disponibilita = '"+idcliente+"' where id='"+id+"'");    
      connessione.close();                        
    }
    else
    {  
      connessione.close();
      throw new Exception("Eccezione auto non disponibile");
    }           
  }
      
  private void insertCliente(String id) throws Exception
  {
    Connection connessione = datasource.getConnection();
    Statement statement = connessione.createStatement();
    int esito =statement.executeUpdate("insert into cliente values('"+id+"')");
    
    if (esito==0)
    {
      connessione.close();         
      throw new Exception("cliente già presente");
    }
         
    connessione.close();
  } 
}
Come vediamo il session bean stateless è marcato con le annotazioni @TransactionManagement e @TransactionAttribute.
L’annotazione @TransactionManagement specifica se si vuole fare uso di CMT o BMT: nel nostro caso specificando il valore TransactionManagementType.CONTAINER stiamo utilizzando CMT, alternativamente avremmo dovuto utilizzare TransactionManagementType.BEAN.
L’attributo @TransactionAttribute serve a comunicare al container come dovrebbe gestire la transazione e può essere applicato ad un singolo metodo o all’intero bean: in quest’ultimo caso viene applicato a tutti i metodi di business.

Esistono sei possibili valori per tale attributo:
  • REQUIRED: è il valore di default e quello più utilizzato, questo valore specifica che il metodo EJB dovrebbe essere invocato sempre con una transazione pertanto se il metodo viene invocato da un client non transazionale, il container avvia una transazione prima dell’invocazione del metodo e la conclude quando l’esecuzione del metodo si conclude.
    Se il metodo indica di effettuare il roll back dell’azione, il container non solo effettua il roll back ma genera pure una javax.transaction.RollbackException.
  • REQUIRES_NEW: indica al container che dovrebbe sempre iniziare una nuova transazione prima di invocare il metodo EJB.
    Se il client ha già una transazione questa viene sospesa finchè il metodo non viene completato.
    Questo significa che il successo o il fallimento della transazione non ha effetto sulla transazione esistente.
  • SUPPORTS: significa che il metodo EJB erediterà l’ambiente transazionale del chiamante: se il client chiamante non ha una transazione allora il metodo sarà chiamato senza transazione altrimenti il metodo sarà eseguito all’interno della transazione esistenze la quale non verrà sospesa
  • MANDATORY: significa che se il client è transazionale il metodo verrà eseguito all’interno della transazione esistente altrimenti verrà generata una EJBTransactionRequiredException.
  • NOT_SUPPORTED: evita che il metodo EJB possa essere invocato in un contesto transazionale: ciò significa che nel caso di client transazionali la transazione verrà sospesa, il metodo verrà eseguito e la transazione verrà ripristinata al completamento.
  • NEVER: viene utilizzato se si vuole evitare che il metodo EJB sia invocato da un client transazionale: se ciò accade viene generata una javax.ejb.EJBException.
MDB supportano soltanto gli attributi REQUIRED e NOT_SUPPORTED: ciò è dovuto al fatto che il client non può invocare un MDB direttamente perché è il container ad invocare i metodi di un MDB quando riceve un messaggio.
Il metodo principale noleggiaAuto riceve come argomenti l’id del cliente che deve noleggiare l’auto e l’id dell’auto che deve essere noleggiata e invoca i metodi inserCliente e updateAuto che si occupano di rispettivamente di inserire l’idcliente nella tabella cliente e di modificare l’attributo disponibilità della tabella auto con l’id del cliente.
In particolare il metodo updateAuto genera un’eccezione se l’auto che si vuole noleggiare è già occupata (campo disponibilità diverso da null).
L’eccezione viene propagata al metodo noleggiaAuto il quale la gestisce effettuando il rollback della transazione mediante l’invocazione del metodo setRollbackOnly dell’interfaccia SessionContext.
Il rollback della transazione fa si che l’eventuale insert nella tabella clienti venga annullata.
Viceversa se non si verifica alcuna eccezione, entrambe le tabelle vengono aggiornate con il commit della transazione.

Gestione delle eccezioni e rollback delle transazioni

Se non si verificano le condizioni affinchè la transazione abbia successo, un metodo CMT può chiedere al container di effettuare il roll back della transazione non appena possibile (ciò viene effettuato settando un opportuno flag del container mediante il metodo setRollbackOnly).
@Resource
private SessionContext context;

...

try 
{
  ...
} 
catch (CreditValidationException cve) 
{
  context.setRollbackOnly();
}
...
Naturalmente se il metodo viene invocato in un contesto non transazionale, invocare tale metodo produce una java.lang.IllegalStateException.
Il metodo getRollbackOnly restituisce un boolean che se posto a true comunica il fatto che la transazione è stata già marcata per il rollback.
Un’importante novità introdotta da EJB 3 è il controllo transazionale al di fuori del metodo che avviene mediatne l’annotazione @javax.ejb.ApplicationException
@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class NoleggiaBean implements NoleggiaBeanLocal
{
  @Resource(name="jdbc/miodb", mappedName="java:jdbc/miodb")
  private DataSource datasource;

  @Resource
  private UserTransaction usertransaction;
         
  public NoleggiaBean() 
  {
  }
      
  public void noleggiaAuto(String idcliente, String idauto)
  {
    try
    {
      usertransaction.begin();
      insertCliente(idcliente);
      updateAuto(idauto,idcliente);
      usertransaction.commit();
    }
    catch(Exception ecc)
    {
      usertransaction.rollback;
    }
  }
      
  private void updateAuto(String id, String idcliente) throws Exception
  {
    Connection connessione = datasource.getConnection();
    Statement statement = connessione.createStatement();
    ResultSet resultset = statement.executeQuery("select disponibilita from auto where id='"+id+"' and disponibilita is null");
   
    if (resultset.first())
    {
      statement.executeUpdate("update auto set disponibilita = '"+idcliente+"' where id='"+id+"'");
      connessione.close();
    }
    else
    {
      connessione.close();
      throw new Exception("Eccezione auto non disponibile");
    }      
  }
      
  private void insertCliente(String id) throws Exception
  {
    Connection connessione = datasource.getConnection();
    Statement statement = connessione.createStatement();
    int esito =statement.executeUpdate("insert into cliente values('"+id+"')");
    
    if (esito==0)
    {
      connessione.close();
      throw new Exception("cliente già presente");
    }
    
    connessione.close();
  } 
}
L’interfaccia UserTransaction incapsula le funzionalità di base fornite dal transaction manager Java EE.
Nel caso di bean fuori dal container (dove la DI non è supportata) è possibile utilizzare al psoto dell’annotazione @Resource l’operazione di lookup JNDI:
Context context = new InitialContext();
UserTransaction usertransaction =
(UserTransaction) context.lookup("java:comp/UserTransaction");
usertransaction.begin();
   
...
   
usertransaction.commit();
E’ possibile anche ottenere una UserTransaction invocando il metodo getUserTransaction dell’interfaccia EJBContext: questo approccio è indicato nel caso in cui si sta usando il SessionContext o il MessageDrivenContext .
@Resource
private SessionContext context;

...

UserTransaction usertransaction = context.getUserTransaction();
usertransaction.begin();

...

usertransaction.commit();

Interfaccia UserTransaction

Oltre a begin, commit e rollback, l’interfaccia UserTransaction ha altri metodi utili:
public interface UserTransaction 
{
  void begin() throws NotSupportedException, SystemException;
  void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, IllegalStateException, SystemException;
  void rollback() throws IllegalStateException, SecurityException, SystemException;
  void setRollbackOnly() throws IllegalStateException, SystemException;
  int getStatus() throws SystemException;
  void setTransactionTimeout(int seconds) throws SystemException;
}
  • begin crea una nuova transazione associandola al thread corrente: JavaEE non supporta transazioni innestate quindi invocare due volte begin comporta una NotSupportedException.
  • getStatus restituisce lo stato della transazione, l’interfaccia javax.transaction.Status definisce quali sono questi stati:
    • STATUS_ACTIVE: la transazione è in uno stato attivo
    • STATUS_MARKED_ROLLBACK: la transazioneè stata marcata per il roll back
    • STATUS_PREPARED: tutte le risorse sono preparate per il commit
    • STATUS_COMMITTED: è stato effettuato il commit della transazione
    • STATUS_ROLLEDBACK: è stato effettuato il roll back della transazione
    • STATUS_UNKNOWN: non si conosce lo stato della transazione
    • STATUS_PREPARING: la transazione si sta preparando per il commit e attende una risposta dalle risorse subordinate
    • STATUS_COMMITTING: la transazione sta effettuando il commit
    • STATUS_ROLLING_BACK: la transazione sta effettuando il roll back
  • commit invia un segnale di successo al transaction manager, rollback abbandona la transazione corrente.