jueves, 7 de julio de 2011

JPA VII - Transacciones

Este tutorial pretende explicar en detalle el tema de las transacciones en cualquier aplicación J2EE.
Como vimos en el tutorial anterior (ver JPA VI - EntityManager) para las situaciones en donde no requerimos los servicios ofrecidos por un EJB3 Container (como por ejemplo JBoss ó WebLogic application server) pero querremos utilizar el modelo de persistencia de JPA, el API JPA provee el Application-managed entity manager donde el ciclo de vida de una transacción es manejada por la aplicación (es decir, el desarrollador iniciara y terminara la transacción).
En este tutorial, vamos a entrar en detalle en aquellas situaciones en donde si se requiera de los servicios de un EJB3 container, para esto el API JPA provee el Container-managed entity manager. 

Al final del capitulo, encontraremos el código fuente para descargar los ejemplos que se vean a lo largo del mismo.

Las transacciones manejadas por el contenedor usan el API de java conocida como JTA.

" Typically, the container begins a transaction immediately before an enterprise bean method starts. It commits the transaction just before the method exits. Each method can be associated with a single transaction. Nested or multiple transactions are not allowed within a method.
Container-managed transactions do not require all methods to be associated with transactions. When deploying a bean, you specify which of the bean's methods are associated with transactions by setting the transaction attributes."
Temas a tratar:
  • Propiedades de las transacciones
  • Atributos de las transacciones
    • Required
    • Required new
    • Supports
    • Not Supported
    • Mandatory
    • Never
  • Ejemplos de como utilizar los atributos de las transacciones
  • Interface SessionSynchronization (3 metodos callback)
    • afterBegin
    • beforeCompletion
    • afterCompletion
  • Doomed Transactions
  • Concurrencia y bloqueos de base de datos (database locking
    • Problemas de las transacciones concurrentes y los niveles de aislamiento
    • Pessimistic locking & Optimistic locking (@Version)
  • BMT - Transacciones demarcadas por el Bean

■  Propiedades de las transacciones

Atomicidad: Todos las tareas que se llevan a cabo dentro de una misma transacciones, o bien terminan con éxito  o bien ninguna termina. Todas las tareas son vistas como una solo unidad atómica. Es decir, esta propiedad no asegura que ante un fallo del sistema no puede quedar a medias.

Consistencia: Una transacción terminara comiteando los valores y dejando en un estado valido a la BD o  bien hará rollback dejando la BD en el estado previo a la ejecución de la transacción (con todos sus pasos o tareas)

Aislamiento: Todos los cambios que se ejecuten contra la BD en una misma transacción no serán visibles por otras transacciones hasta que dichos cambio se comiteen. Es decir, nos segura que una operación no puede afectar a otras.

Durabilidad: Esta propiedad asegura que una vez realizada la operación, ésta persistirá y no se podrá deshacer aunque falle el sistema.

Existen diferentes grados de aislamiento. el máximo nivel de aislamiento no puede ser la más conveniente ya que puede haber una penalización de rendimiento muy alto a pagar.

Cuando el contenedor EJB se encarga de manejar las transacciones, tenemos que decidir cuando es que comenzara y terminara la transaccion, para que esta sea manejada por el conentedor (JBoss o WebLogic entre otros)
Estas políticas de demarcación son definidas por diferentes atributos de una transacción y pueden ser seteados en los métodos o clases de los session beans (EJBs),


■  Atributos de las transacciones (Transaction attributes)

SUPPORTS: Si la llamada se hace desde una transacción activa,  el método con la anotación de dicha propiedad sera ejecutado dentro de la misma transacción activa.
Si la llamada se hace desde un método que no tiene activa una transacción, el método con la anotación de dicha propiedad sera ejecutado sin la creación de una transacción. Es decir, no se creara una transacción para dicha ejecución.

NOT_SUPPORTED: Tanto para el caso en el que la llamada se hace desde una transaccion activa como si se hace desde un método que no tiene ninguna transaccion activa, el metodo con la anotación de dicha propiedad sera ejecutado sin ninguna transaccion. Es decir, sera ejecutado fuera del contexto de transacción.

REQUIRED: Esta es la propiedad por default.  El método con la anotación de dicha propiedad requiere ser ejecutado dentro de una transacción. En caso que la llamada a este método ya haya iniciado la transacción  este método utilizara la misma. En caso que la llamada a este método no haya iniciado una transacción, una nueva transacción se iniciara para la ejecución de este método.

REQUIRES_NEW: El método con la anotación de dicha propiedad requiere ser ejecutado siempre en una nueva transaccion independientemente si ya exista o no una transaccion en el contexto. En caso que ya exista una transacción activa, la transacción original sera reanudada luego de que la nueva transacción efectue el commit o rollback pertinente, 

MANDATORY: El método con la anotación de dicha propiedad requiere que el método que llame a este método ya haya iniciado la transacción. Es decir, cuando se llame al este método ya tiene que existir en el contexto una transacción activa. En caso contrario se lanzara una expectación.

NEVER: El método con la anotación de dicha propiedad requiere que el método que llame a este método no ya haya iniciado ninguna transacción. En caso que este método encuentre una transacción activa, lanzara una excepción.


Transaction Attribute
Caller Transaction Exists
Effect
REQUIRED
No
Container creates a new transaction
Yes
Method joins the callers transaction
REQUIRES_NEW
No
Container creates a new transaction
Yes
Container creates a new transaction and the callers transaction is suspended
SUPPORTS
No
No transaction is used
Yes
Method joins the callers transaction
MANDATORY
No
javax.ejb.EJBTransactionRequiredException is thrown
Yes
Method joins the callers transaction
NOT_SUPPORTED
No
No transaction is used
Yes
The callers transaction is suspended and the method is called without a transaction
NEVER
No
No transaction is used
Yes
javax.ejb.EJBException is thrown

Tabla extraída de la siguiente URL: http://www.datadisk.co.uk/html_docs/ejb/ejb3_transactions.htm


■  Ejemplos de los diferentes atributos de transacciones


Tx: Sinónimo de transacción
Los atributos de las transacciones vamos a poder definirlos a nivel de clase (EJB) o a nivel de los métodos:

    • En caso de estar definido a nivel de clase, todos los métodos serán ejecutados bajo dicha definición salvo aquellos métodos a los cuales se les defina un atributo de transacción distinto
    • El atributo por defecto definido a nivel de clase es el de REQUIRED. Es decir, si tenemos un bean que tiene 3 métodos y solo a un método se le define un atributo de transacción distinto al REQUIRED, este método sera ejecutado bajo esta nueva definición mientras que los otros dos, serán ejecutados bajo la definición de REQUIRED

Los siguientes ejemplos son lanzados desde un SERVLET llamado ECommerceTest el cual llamara a un EJB llamado ECommerceBean y en varias oportunidades, este a su vez llamara a otro EJB llamado AuditServiceBean

1.- Required Tx

Servlet ECommerceTest

private void requiredTransactionAttributTest() {
System.out.println("***** requiredTransactionAttributTest INICIO ******* ");
eCommerceBeanLocal.saveRefereeAndAuditMsgAndRequiredTxAndThrowException(102, "Prueba2");
System.out.println("***** requiredTransactionAttributTest FIN ******* ");
}


EJB ECommerceBean

public void saveRefereeAndAuditMsgAndRequiredTxAndThrowException(int id, String name) {
System.out.println("*********** saveRefereeAndAuditMsgAndRequiredTxAndThrowException INICIO *************");
Referee r1 = new Referee();
r1.setName(name);
r1.setId(id);
em.persist(r1);
auditServiceBean.addAuditMessageWithTransactionRequired(id, name + " [AUDITORIA] ");
throw new RuntimeException("add customer - simulated system failure");
}

EJB AuditServiceBean

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void addAuditMessageWithTransactionRequired(int id, String name) {
System.out.println("***** addAuditMessageWithTransactionRequired INICIO ******");
AuditMessage am = new AuditMessage();
am.setId(id);
am.setName(name);
em.persist(am);
System.out.println("***** addAuditMessageWithTransactionRequired FIN ******");
}


La traza del log es la siguiente:

18:14:44,546 INFO  [STDOUT] ***** requiredTransactionAttributTest INICIO ******* 
18:14:44,625 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
18:14:44,625 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
18:14:44,625 INFO  [STDOUT] *********** saveRefereeAndAuditMsgAndRequiredTxAndThrowException INICIO *************
18:14:44,859 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
18:14:44,859 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
18:14:44,859 INFO  [STDOUT] ***** addAuditMessageWithTransactionRequired INICIO ******
18:14:44,859 INFO  [STDOUT] ***** addAuditMessageWithTransactionRequired FIN ******
18:14:44,890 ERROR [[ECommerceTest]] Servlet.service() for servlet ECommerceTest threw exception
javax.ejb.EJBException: java.lang.RuntimeException: add customer - simulated system failure
at org.jboss.ejb3.tx.Ejb3TxPolicy.handleExceptionInOurTx(Ejb3TxPolicy.java:77)
at org.jboss.aspects.tx.TxPolicy.invokeInOurTx(TxPolicy.java:83)


Se lanza un error de manera intencional para ver como es que este atributo funciona. Aquí podemos ver que tanto, toda la lógica del método que llama como la del  que es llamado, se hace rollback y no se persiste ningún cambio. Esto se debe a que el método addAuditMessageWithTransactionRequired() es ejecutado dentro de la misma transacción que el metodo que lo llamo
(saveRefereeAndAuditMsgAndRequiredTxAndThrowException()).

Las tablas de la BD nos quedaran de la siguiente manera

Tabla Referee:
 ID     COMMENTS     NAME    
 -----  -----------  ------- 

Tabla AuditMessage:
 ID     COMMENTS     NAME                 
 -----  -----------  -------------------- 

2.- Required New Tx

Servlet ECommerceTest

private void requiredNewTransactionAttributTest() {
System.out.println("***** requiredNewTransactionAttributTest INICIO ******* ");
eCommerceBeanLocal.saveRefereeAndAuditMsgAndRequiredNewTxAndThrowException(103, "Prueba3");
System.out.println("***** requiredNewTransactionAttributTest FIN ******* ");
}

EJB ECommerceBean

public void saveRefereeAndAuditMsgAndRequiredNewTxAndThrowException(int id, String name) {
System.out.println("*********** saveRefereeAndAuditMsgAndRequiredNewTxAndThrowException INICIO *************");
Referee r1 = new Referee();
r1.setName(name);
r1.setId(id);
em.persist(r1);
auditServiceBean.addAuditMessageWithTransactionRequired_New(id, name + " [AUDITORIA] ");
throw new RuntimeException("add customer - simulated system failure");
}

EJB AuditServiceBean

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void addAuditMessageWithTransactionRequired_New(int id, String name) {
System.out.println("***** addAuditMessageWithTransactionRequired_New INICIO ******");
AuditMessage am = new AuditMessage();
am.setId(id);
am.setName(name);
em.persist(am);
System.out.println("***** addAuditMessageWithTransactionRequired_New FIN ******");
}

La traza del log es la siguiente:

18:36:36,203 INFO  [STDOUT] ***** requiredNewTransactionAttributTest INICIO ******* 
18:36:36,203 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
18:36:36,203 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
18:36:36,203 INFO  [STDOUT] *********** saveRefereeAndAuditMsgAndRequiredNewTxAndThrowException INICIO *************
18:36:36,234 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
18:36:36,234 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
18:36:36,234 INFO  [STDOUT] ***** addAuditMessageWithTransactionRequired_New INICIO ******
18:36:36,234 INFO  [STDOUT] ***** addAuditMessageWithTransactionRequired_New FIN ******
18:36:36,312 INFO  [STDOUT] Hibernate: 
    insert 
    into
        AuditMessage
        (comments, name, id) 
    values
        (?, ?, ?)
18:36:37,031 ERROR [[ECommerceTest]] Servlet.service() for servlet ECommerceTest threw exception
javax.ejb.EJBException: java.lang.RuntimeException: add customer - simulated system failure
at org.jboss.ejb3.tx.Ejb3TxPolicy.handleExceptionInOurTx(Ejb3TxPolicy.java:77)
at org.jboss.aspects.tx.TxPolicy.invokeInOurTx(TxPolicy.java:83)

Se lanza un error de manera intencional para ver como es que este atributo funciona. Aquí podemos ver que la lógica del método que llama
(saveRefereeAndAuditMsgAndRequiredTxAndThrowException())  se hara rollback mientras que la lógica del método llamado 
(addAuditMessageWithTransactionRequired()) persistira los cambios. Es decir, la BD nos quedara de la siguiente manera

Tabla Referee:
 ID     COMMENTS     NAME    
 -----  -----------  ------- 

Tabla AuditMessage:
 ID     COMMENTS     NAME                 
 -----  -----------  -------------------- 
 103    (null)       Prueba3 [AUDITORIA]  

3.- Not Supported Tx

Servlet ECommerceTest

private void notSupportedTransactionAttributTest() {
System.out.println("***** notSupportedTransactionAttributTest INICIO ******* ");
eCommerceBeanLocal.saveRefereeAndAuditMsgAndNotSupportedTxAndThrowException(104, "Prueba4");
System.out.println("***** notSupportedTransactionAttributTest FIN ******* ");
}

EJB ECommerceBean

public void saveRefereeAndAuditMsgAndNotSupportedTxAndThrowException(int id, String name) {
System.out.println("*********** saveRefereeAndAuditMsgAndNotSupportedTxAndThrowException INICIO *************");
Referee r1 = new Referee();
r1.setName(name);
r1.setId(id);
em.persist(r1);
auditServiceBean.addAuditMessageWithTransactionNotSupported(id, name + " [AUDITORIA] ");
}

EJB AuditServiceBean

@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void addAuditMessageWithTransactionNotSupported(int id, String name) {
System.out.println("***** addAuditMessageWithTransactionNotSupported INICIO ******");
System.out.println("No interactuamos con BD. Logueamos en una Archivo " + id + " - " + name);
}

La traza del log es la siguiente:

19:00:39,500 INFO  [STDOUT] ***** notSupportedTransactionAttributTest INICIO ******* 
19:00:39,515 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
19:00:39,515 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
19:00:39,515 INFO  [STDOUT] *********** saveRefereeAndAuditMsgAndNotSupportedTxAndThrowException INICIO *************
19:00:39,531 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
19:00:39,531 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
19:00:39,531 INFO  [STDOUT] ***** addAuditMessageWithTransactionNotSupported INICIO ******
19:00:39,531 INFO  [STDOUT] No interactuamos con BD. Logueamos en una Archivo 104 - Prueba4 [AUDITORIA] 
19:00:39,546 INFO  [STDOUT] Hibernate: 
    insert 
    into
        Referee
        (comments, name, id) 
    values
        (?, ?, ?)
19:00:39,890 INFO  [STDOUT] ***** notSupportedTransactionAttributTest FIN ******* 

Aquí podemos ver como se le agrega el atributo de NOT_SUPPORTED al método addAuditMessageWithTransactionNotSupported() ya que dicho método no necesitara interactuar con la BD (contexto de persistencia). Por ejemplo, puede que este método interactue directamente con un fichero en el filesystem.

IMPORTANTE: Se estuvo probando de lanzar una excepción en el metodo  addAuditMessageWithTransactionNotSupported() que no se ejecuta dentro de una   transacción y vimos que NO se graba el objeto  referee independientemente de que este, esté en una transacción independiente. Esto también pasa si agregamos el atributo REQUIRED_NEW a dicho metodo (en lugar del atributo NOT_SUPPORTED). Es decir, si agregamos una excepción en el método llamado (método del 2do EJB) es como que corta la ejecución y no persistirá las entidades que tendría que persistir del método llamador (método del 1er EJB).

Tabla Referee:
 ID     COMMENTS     NAME    
 -----  -----------  ------- 
 104    (null)       Prueba4 

Tabla AuditMessage:
 ID     COMMENTS     NAME                 
 -----  -----------  -------------------- 

4.- Supported Tx (veremos dos ejemplos)

Servlet ECommerceTest

private void supportsTransactionAttributTest() {
System.out.println("***** supportsTransactionAttributTest INICIO ******* ");
eCommerceBeanLocal.saveRefereeAndSupportsTx(105, "Prueba5");
System.out.println("***** supportsTransactionAttributTest FIN ******* ");
}

EJB ECommerceBean

@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public void saveRefereeAndSupportsTx(int id, String name) {
System.out.println("*********** saveRefereeAndSupportsTx INICIO *************");
Referee r1 = new Referee();
r1.setName(name);
r1.setId(id);
em.persist(r1);
}

La traza del log es la siguiente:

19:30:16,484 INFO  [STDOUT] ***** supportsTransactionAttributTest INICIO ******* 
19:30:16,484 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
19:30:16,484 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
19:30:16,484 INFO  [STDOUT] *********** saveRefereeAndSupportsTx INICIO *************
19:30:16,500 ERROR [[ECommerceTest]] Servlet.service() for servlet ECommerceTest threw exception
javax.persistence.TransactionRequiredException: EntityManager must be access within a transaction
at org.jboss.jpa.deployment.ManagedEntityManagerFactory.verifyInTx(ManagedEntityManagerFactory.java:155)
at org.jboss.jpa.tx.TransactionScopedEntityManager.persist(TransactionScopedEntityManager.java:186)

Aquí podemos ver como se lanzara una excepción ya que se intenta persistir un referee en la BD sin una transacción inicializada.

Ahora veamos el 2do ejemplo para este atributo:

Servlet ECommerceTest

private void supportsTransactionAttributTest2() {
System.out.println("***** supportsTransactionAttributTest INICIO ******* ");
eCommerceBeanLocal.saveRefereeAndSupportsTx2(105, "Prueba5");
System.out.println("***** supportsTransactionAttributTest FIN ******* ");
}

EJB ECommerceBean

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void saveRefereeAndSupportsTx2(int id, String name) {
System.out.println("*********** saveRefereeAndSupportsTx2 INICIO *************");
Referee r1 = new Referee();
r1.setName(name);
r1.setId(id);
em.persist(r1);
auditServiceBean.addAuditMessageWithTransactionSupports(id, name + " [AUDITORIA] ");
System.out.println("*********** saveRefereeAndSupportsTx2 FIN *************");
}

EJB AuditServiceBean

@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public void addAuditMessageWithTransactionSupports(int id, String name) { System.out.println("***** addAuditMessageWithTransactionSupports INICIO ******");
AuditMessage am = new AuditMessage();
am.setId(id);
am.setName(name);
em.persist(am);
System.out.println("***** addAuditMessageWithTransactionSupports FIN ******");
}

La traza del log es la siguiente:

19:34:52,468 INFO  [STDOUT] ***** supportsTransactionAttributTest INICIO ******* 
19:34:52,484 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
19:34:52,484 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
19:34:52,484 INFO  [STDOUT] *********** saveRefereeAndSupportsTx2 INICIO *************
19:34:52,484 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
19:34:52,484 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
19:34:52,484 INFO  [STDOUT] ***** addAuditMessageWithTransactionSupports INICIO ******
19:34:52,484 INFO  [STDOUT] ***** addAuditMessageWithTransactionSupports FIN ******
19:34:52,484 INFO  [STDOUT] *********** saveRefereeAndSupportsTx2 FIN *************
19:34:52,484 INFO  [STDOUT] Hibernate: 
    insert 
    into
        Referee
        (comments, name, id) 
    values
        (?, ?, ?)
19:34:52,546 INFO  [STDOUT] Hibernate: 
    insert 
    into
        AuditMessage
        (comments, name, id) 
    values
        (?, ?, ?)
19:34:52,562 INFO  [STDOUT] ***** supportsTransactionAttributTest FIN ******* 

Aquí podemos ver como con la anotación SUPPORTS, se hara un join con la transacción inicializada en el método llamador y por ende, el método llamado (2do método del 2do EJB) podrá llevar a cabo la persistencia en la BD.

Tabla Referee:
 ID     COMMENTS     NAME    
 -----  -----------  ------- 
 105    (null)       Prueba5 

Tabla AuditMessage:
 ID     COMMENTS     NAME                 
 -----  -----------  -------------------- 
 105    (null)       Prueba5 [AUDITORIA] 

5.- Mandatory Tx

Servlet ECommerceTest

private void mandatoryTransactionAttributTest() {
System.out.println("***** mandatoryTransactionAttributTest INICIO ******* ");
eCommerceBeanLocal.saveRefereeAndMandatoryTx(106, "Prueba6");
System.out.println("***** mandatoryTransactionAttributTest FIN ******* ");
}

EJB ECommerceBean

@TransactionAttribute(TransactionAttributeType.MANDATORY)
public void saveRefereeAndMandatoryTx(int id, String name) {
System.out.println("***** addAuditMessageWithTransactionMandatory INICIO FIN ******");
}

La traza del log es la siguiente:

22:51:12,531 INFO  [STDOUT] ***** mandatoryTransactionAttributTest INICIO ******* 
22:51:12,546 ERROR [[ECommerceTest]] Servlet.service() for servlet ECommerceTest threw exception
javax.ejb.EJBTransactionRequiredException: public void ejbmodule.ejb.ECommerceBean.saveRefereeAndMandatoryTx(int,java.lang.String)
at org.jboss.ejb3.tx.Ejb3TxPolicy.throwMandatory(Ejb3TxPolicy.java:47)
at org.jboss.ejb3.tx.TxInterceptor$Mandatory.invoke(TxInterceptor.java:141)

Aquí podemos ver como con esta anotación, el metodo saveRefereeAndMandatoryTx() necesitara que exista una transaccion activa para poder ser ejecutado correctamente. En caso que no exista dicha transacción activa como vimos en el ejemplo, se lanzara la excepción EJBTransactionRequiredException.

6.- Never Tx

Servlet ECommerceTest

private void neverTransactionAttributTest() {
System.out.println("***** mandatoryTransactionAttributTest2 INICIO ******* ");
eCommerceBeanLocal.saveRefereeAndNeverTx2(107, "Prueba6");
System.out.println("***** mandatoryTransactionAttributTest2 FIN ******* ");
}

EJB ECommerceBean

public void saveRefereeAndNeverTx2(int id, String name) {
System.out.println("*********** saveRefereeAndNeverTx2 INICIO *************");
Referee r1 = new Referee();
r1.setName(name);
r1.setId(id);
em.persist(r1);
auditServiceBean.addAuditMessageWithTransactionNever(id, name + " [AUDITORIA] ");
System.out.println("*********** saveRefereeAndNeverTx2 FIN *************");
}


EJB AuditServiceBean


@TransactionAttribute(TransactionAttributeType.NEVER)
public void addAuditMessageWithTransactionNever(int id, String name) {
System.out.println("***** addAuditMessageWithTransactionNever INICIO FIN ******");

}

La traza del log es la siguiente:


23:01:02,140 INFO  [STDOUT] ***** mandatoryTransactionAttributTest2 INICIO ******* 
23:01:02,140 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
23:01:02,140 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
23:01:02,156 INFO  [STDOUT] *********** saveRefereeAndNeverTx2 INICIO *************
23:01:02,156 ERROR [[ECommerceTest]] Servlet.service() for servlet ECommerceTest threw exception
javax.ejb.EJBException: Transaction present on server in Never call (EJB3 13.6.2.6)
at org.jboss.ejb3.tx.TxInterceptor$Never.invoke(TxInterceptor.java:61)
at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102)

Aquí podemos ver como con esta anotación, el método
addAuditMessageWithTransactionNever() necesitara que NO exista una transacción activa para poder ser ejecutado correctamente. En caso, como existe una transaccion debido al metodo que lo llama, se lanzara la excepción EJBException.


■  SessionSynchronization Interface

Existen situaciones en las cuales necesitamos que el application server notifique al EJB cuando las transacciones empiezan o cuando terminan. Los EJB del tipo Stateful pueden recibir este tipo de notificaciones implementando la interface javax.ejb.SessionSynchronization. 
Dicha interface obligara a implementar 3 metodos que se detallan a continuación:
  • afterBegin: Indica que una transacción esta por comenzar.
  • beforeCompletion: Indica que una transacción esta por finalizar (mediante commit o rollback)
  • afterCompletion: Indica que una transacción ha finalizado (ya sea si se hizo commit o rollback)
There are occasions when we want the application to have some control over commits and rollbacks in container managed stateful session beans. For example, in a shopping cart application we may decide to cache a running total of purchased items in one transaction and commit them to the database after all items have been purchased. Let's assume that after writing the cache to the database but before committing we decide to rollback the transaction. The database will be rolled backed successfully but the stateful session bean cache, which is non transactional, will still contain the purchased items. We need a mechanism to synchronize the database with the session
Texto extraído del libro [1] - EJB 3 Developer Guide

El siguiente ejemplo es lanzado desde un SERVLET llamado ECommerceTest el cual llamara a un EJB llamado ECommerceBean y este a su vez llamara a otro EJB llamado ShoppingCartBean


Servlet ECommerceTest

public void doShopping() {
System.out.println("********** ECommerceTest doShopping INICIO ************");
eCommerceBeanLocal.doShopping();
System.out.println("********** ECommerceTest doShopping FIN ************");
}

EJB ECommerceBean 

public void doShopping() {
System.out.println("********** ECommerceBean doShopping() INICIO *************");
shoppingCart.addItem("Bread");
shoppingCart.addItem("Milk");
shoppingCart.addItem("Tea");
System.out.println("********** ECommerceBean doShopping() FIN *************");
}

EJB ShoppingCartBean

@Stateful
public class ShoppingCartBean implements ShoppingCartBeanLocal, SessionSynchronization {


@PersistenceContext(unitName="examplePersistenceUnit")
private EntityManager em;
private Shopping sc;
private List<String> items;


@PostConstruct
public void initialize() {
items = new ArrayList<String>();
}

public void addItem(String item) {
items.add(item);
}

/**
* Este netodo se ejecuta antes de llamar al primer metodo de la transaccion. 
*/
public void afterBegin() {

System.out.println("afterbegin method invoked");
}

/**
* Este netodo se ejecuta antes de ejecutar el commit o rollback
*/
public void beforeCompletion() {

System.out.println("beforeCompletion method invoked");
int itemId = 0;
for (String item : items) {
itemId++;
sc = new Shopping();
sc.setId(itemId);
sc.setItem(item);
em.persist(sc);
}
}

/**
* Este netodo se ejecuta despues de ejecutar el commit o rollback
*/
public void afterCompletion(boolean committed) {

System.out.println("afterCompletion method invoked");
if (committed == false) {
// Estos casos ocurre cuando se hizo rollback de la transaccion.
System.out.println("committed = false");
items = null;
}
}
}


La traza del log es la siguiente:

12:10:02,413 INFO  [STDOUT] ********** ECommerceTest doShopping INICIO ************
12:10:02,413 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
12:10:02,413 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
12:10:02,413 INFO  [STDOUT] ********** ECommerceBean doShopping() INICIO *************
12:10:02,413 INFO  [STDOUT] afterbegin method invoked
12:10:02,413 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
12:10:02,413 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
12:10:02,413 INFO  [STDOUT] ********** ECommerceBean doShopping() FIN *************
12:10:02,413 INFO  [STDOUT] beforeCompletion method invoked
12:10:02,413 INFO  [STDOUT] Hibernate: 
    insert 
    into
        Shopping
        (comments, item, id) 
    values
        (?, ?, ?)
12:10:02,413 INFO  [STDOUT] Hibernate: 
    insert 
    into
        Shopping
        (comments, item, id) 
    values
        (?, ?, ?)
12:10:02,413 INFO  [STDOUT] Hibernate: 
    insert 
    into
        Shopping
        (comments, item, id) 
    values
        (?, ?, ?)
12:10:02,413 INFO  [STDOUT] afterCompletion method invoked
12:10:02,413 INFO  [STDOUT] ********** ECommerceTest doShopping FIN ************



■  Doomed Transactions


We have seen two ways in which a container-managed transaction can be rolled back:

One way is when the EJB container throws a RemoteException or RunTimeException for system exceptions such as database or network connection errors. When a system exception occurs, the container will automatically rollback the transaction. 
Como del lado cliente no se espera manejar excepciones de sistema la aplicación simplemente deberá catchear la excepcion de systema y relanzar una EJBException.

The second way a container-managed transaction can be rolled back is to throw an application exception with the annotation @ApplicationException(rollback=true). An application exception is an exception that occurs while executing business logic, typically some kind of validation. The client application is expected to handle an application exception. We may optionally decide to rollback an application exception, using the rollback=true qualifier, depending on the application's business logic.

The third way a container-managed transaction can be rolled back is to use the SessionContext interface setRollbackOnly() method. The transaction is not rolled back immediately but on completion of the method. 
We call this dooming a transaction.

Ejemplo:

  ....
  ....
 @PersistenceContext(unitName="examplePersistenceUnit")
private EntityManager em;
private Shopping sc;
private List<String> items;

public void addItem(String item) {
     // con esto indicamos que la transaccion se transforma en una doomed transaction,
     ctx.setRollbackOnly();

}

■  Concurrencia y bloqueos de base de datos (database locking)


A. Problemas de las transacciones concurrentes y los niveles de aislamiento


El aislamiento es uno de los 4 atributos (ACID) de toda transaccion. 
Este atributo cobra fuerza ante los problemas de concurrencia que surgen cuando dos o mas transacciones operan sobre los mismos datos.

Existen 3 problemas de aislamiento con las transacciones concurrentes.
  • Dirty read
    • An account entity initially has a balance of 100.
    • Transaction 1 updates the balance to 200.
    • Transaction 2 reads new balance of 200.
    • Transaction 1 rolls back restoring the balance to the original value of 100.
    • Transaction 2 now has an incorrect, or dirty, value of 200.
  • Unrepeatable read
    • Transaction 1 reads account id and balance for all accounts with balance less than 150.
    • Transaction 2 updates an account entity changing its balance from 100 to 200.
    • Transaction 1 reads the data again including detailed information such as account name
    • An account which appeared in the initial summary read is not present in the second detailed read.
  • Phantom read
    • Transaction 1 reads account ID and balance for all accounts with balance less than 150.
    • Transaction 2 adds a new account entity with a balance of 100.
    • Transaction 1 reads the data again including detailed information such as account name
    • A new account now appears in the second detailed read which was not present in the initial summary read.
La Phantom read es similar a Unrepeateble read solo que los datos son insertados en lugar de ser actualizados por la segunda transacción.

Existen 4 niveles de aislamiento de las transacciones:
  • Read Uncommitted
  • Read Committed
  • Repeatable Read
  • Serializable
Nivel de aislamientoDirty readUnrepeteable readPhantom read
SERIALIZABLENo es posibleNo es posibleNo es posible
REPEATABLE READNo es posibleNo es posibleEs posible
READ COMMITTEDNo es posibleEs posibleEs posible
READ UNCOMMITTEDEs posibleEs posibleEs posible
Estos niveles de aislamiento son parte del estandar SQL92 para base de datos relacionales (RDBMS).

El nivel mas estricto de aislamiento (serializable) ataca con los 3 primeros problemas (dirty read, Unrepeteable read y Phantom read), pero trae aparejado grandes problemas de performance.mientras que un nivel menos estricto, son los que se usan en la practica.
La mayoría de las BD no proveen el nivel de aislamiento Read Uncommitted.

El nivel por defecto es Read Committed o Repeatable Read. En el caso de Oracle, por ejemplo, trae por defecto el nivel Read Committed

El API de EJB3 no permite setear el nivel de aislamiento. La especificación asume que un EJB3 Container tiene un nivel de asilamiento de Read Committed


B.  Pessimistic locking & Optimistic locking (@Version)


Un clásico problema es que dos transacciones actualicen el mismo dato (registro en BD).
Ejemplo:
  • Transaction 1 reads the Customer entity
  • Transaction 2 reads the same Customer entity
  • Transaction 1 updates its copy of firstName to MICHAEL
  • Transaction 2 updates its copy of lastName to ANGELO
  • Transaction 2 commits data - value on database is LEONARDO ANGELO
  • Transaction 1 commits data - value on database is MICHAEL DAVINCI
Como podemos ver, la actualizacion de la transaccion 2 se pierde o pisa por la actualizacion de la transacción 1 quedando en el registro el valor de MICHAEL DAVINCI y perdiendo el valor de LEONARDO ANGELO

Para solucionar esto existen dos estrategias que se pueden tomar:

  • Bloqueo Pesimista: En este caso, se bloquea el registro para su uso exclusivo hasta que haya terminado con el. Tiene mucho mejor integridad que el bloqueo optimista, pero es necesario tener cuidado con el diseño de su aplicación para evitar los deadlocks (situación en la que dos o mas acciones quedan esperando a que termine de ejecutarse otra acción).
Para el ejemplo anterior, la transacción 1 bloqueara el Customer entity y solo cuando termine su ejecucion recién la transacción 2 podrá ejecutar su commit. Con lo cual el valor que va a quedar en ese registro sera el de LEONARDO ANGELO.

Una implementacion de este bloqueo es mediante la sintaxis SELECT FOR UPDATE

  • Bloqueo Optimista: Es una estrategia que lee un registro, toma nota de un número de versión y comprueba que la versión no ha cambiado antes de escribir el registro nuevo. Al escribir el registro comprueba que no se haya actualizado entre cuando se compruebe la versión y escribe el registro en el disco.Si  la versión es diferente a la suya anula la transacción.Esta estrategia es más aplicable a sistemas de gran volumen y arquitecturas de tres niveles.

El bloqueo optimista es implementado por el API de EJB3 mediante el campo Version y la anotación @Version

Ejemplo:

@Entity
public class Customer implements java.io.Serializable {

private int id;
private String firstName;
private String lastName;
private int version;
...


@Version
protected int getVersion() { 
return version; 
}

protected void setVersion(int version) {
this.version = version;
}

...
}

En este ejemplo, elegimos que sea un int, pero también podria ser Integer, Short, short, Long, long y Timestamp


■  BMT - Transacciones demarcadas por el Bean

El comportamiento por default de un EJB Container es utilizar el CMT como manejador de transacciones. Sin embargo es posible decirle a un EJB que sea el quien se encargue de demarcar las transacciones (es decir, cuando empieza y cuando termina una transacción). Para esto, se utilizara BMT. Para tal propósito se utiliza JTA. Específicamente, JTA es implementado por la interface javax.transaction.UserTransaction

Ejemplo:

public class AddCustomerServlet extends HttpServlet {
@PersistenceUnit
private EntityManagerFactory emf;
@Resource
private UserTransaction utx;


try{

EntityManager em = emf.createEntityManager();
...
...
utx.commit();

} catch (Exception e) {
try {
utx.rollback();
} catch (Exception ee) {}
throw new ServletException(e);
} finally {
em.close();
}
}
}








Descargas de ejemplos de transacciones EJB3-JPA-Hibernate

Por favor, haga clic aqui para descargar los ejemplos de este tutorial.


Bibliografia



* EJB 3 Developer Guide - A Practical Guide for developers and architects to the Enterprise Java Beans
   Standard - Michael Sikora - 2008 [1]

No hay comentarios:

Publicar un comentario