Problema

In un'applicazione J2EE, una transazione è un'operazione che deve soddisfare alcuni importanti requisiti:

- deve essere atomica;
- tipicamente deve essere associata ad una unità di lavoro come ad esempio un thread (vedi JTA);
- Non deve dipendere dal data source (DB2, Oracle, MySQL, XML e così via)

Soluzione

Fornire un' interfaccia TransactionManager la cui responsabilità è quella di eseguire start/stop di transazioni.
Fornire un'interfaccia Transaction che rappresenti una generica transazione.

public interface TransactionManager {

    void begin() throws TransactionException;
    void end() throws TransactionException;
    void commit() throws TransactionException;
    void rollback() throws TransactionException;
    Transaction getTransaction();
}

public interface Transaction {

    void commit() throws TransactionException;
    void rollback() throws TransactionException;
}

In un'implementazione JDBC, il transaction manager associa la transazione al thread corrente, nel caso in cui si voglia supportare transazioni nested, allora uno stack di transazioni deve essere gestito. Nel nostro esempio considereremo uno schema che non supporta le transazioni nested.

Supponendo ora che il back end sia un database DB2, ecco un'ipotetica implementazione del transaction manager.

public class DB2TransactionManager implements TransactionManager {

    private static ThreadLocal local = new ThreadLocal();

    public void begin() throws TransactionException {

      DB2Transaction transaction = (DB2Transaction) local.get();

      if (transaction == null) {

        transaction = new DB2Transaction();
        local.set(transaction);
      } else {

        throw new NotSupportedException("Nested Transaction not supported");
      }
    }

    public void end() throws TransactionException {

      DB2Transaction transaction = (DB2Transaction) local.get();

      if (transaction != null) {

        transaction.releaseconnection();
      }
    }

    public void commit() throws TransactionException {

      DB2Transaction transaction = (DB2Transaction) local.get();

      if (transaction == null) {

        throw new IllegalStateException("Try to commit a not existing transaction")
      } else {

        transaction.commit();
      }
    }

    public void rollback() throws TransactionException {

      DB2Transaction transaction = (DB2Transaction) local.get();

      if (transaction != null) {

        transaction.rollback();
      }
    }

    public Transaction getTransaction() {

      DB2Transaction transaction = (DB2Transaction) local.get();

      if (transaction == null) {

        throw new IllegalStateException("Try to retrieve a not existing transaction")
      } else {

        return transaction;
      }
    }
}

Mentre la transazione potrebbe essere implementata in questo modo.

public class DB2Transaction implements Transaction {

    private java.sql.Connection connection = null;

    public DB2Transaction() throws TransactionException {

      connection = // get connection from datasource (autocommit = false)
    }

    public void commit() throws TransactionException {

      try {

        connection.commit();
      } catch(SQLException exc) {

        // re-throw TransactionException
      }
    }

    public void rollback() throws TransactionException {

      try {

        connection.rollback();
      } catch(SQLException exc) {

        // re-throw TransactionException
      }
    }

    java.sql.Connection getConnection() {

      return connection;
    }

    java.sql.Connection releaseConnection() {

      // release connection from datasource
    }
}

Dal codice si evince che il transaction manager associa al thread corrente la transazione attraverso la classe ThreadLocal. Si osservi come nella classe DB2Transaction i metodi getConnection e releaseConnection hanno solo visibilità a livello di package.

Vediamo ora come un ipotetico servizio possa utilizzare le interfacce sopra definite.

public class BusinessService {

    private DAOBusiness dao;

    public BusinessService() {

      this.dao = DAOFactory.makeFactory().getDAOBusiness();
    }

    public void business() throws BusinessException {

      TransactionManager tm = DAOFactory.makeFactory().getTransactionManager();
      try {

        tm.begin();
        dao.business();
        tm.commit();

      } catch(TransactionException e1) {

        tm.rollback();
        // re-throw Business Exception

      } catch(DAOException e2) {

        tm.rollback();
        // re-throw Business Exception

      } catch(RuntimeException e3) {

        tm.rollback();
        // re-throw Runtime Exception

      } finally {

        tm.end();
      }
    }
}

Bisogna ricordare che per ogni backend l'invocazione di DAOFactory.makeFactory().getTransactionManager() restituisce sempre un unica istanza di transaction manager (come se fosse un singleton).

La domanda che ci poniamo ora è: come fa il DAO DB2 ad accedere alla connessione JDBC visto che la transazione non gli viene passata in input? Facile, sfruttiamo le caratteristiche della classe ThreadLocal. Visto che stiamo sempre nello stesso thread e che abbiamo accesso alle variabili locali al thread (dove viene conservata la transazione), potremo scrivere il seguente codice:

public class DB2DAO implements DAO {

    public void business() throws DAOException {

      DB2Transaction tx = (DB2Transaction) DAOFactory.makeFactory().getTransactionManager().getTransaction();
      Connection conn = tx.getConnection();
      // start JDBC code here
    }
}

Il DAO sarà, quindi, responsabile di creare/distruggere JDBC statement e result set, mentre le transazioni le gestirà il metodo di business. Il DAOFactory implementa il factory method makeFactory che crea un DAO Factory specifico per DB2 (nel caso di più backend il metodo potrebbe leggere da file di configurazione il backend da instanziare). Questa factory implementerà il metodo getTransactionManager in modo tale da restituire un DB2TransactionManager.


Page Information

  • 2 years ago [history]
  • View page source
  • You're not logged in
  • No tags yet learn more

Wiki Information

Recent PBwiki Blog Posts