Blog

EJB y @TransactionAttribute

EJB y @TransactionAttribute
Publicado por: Gerardo Arroyo Arce

En este artículo expondremos los diferentes tipos de @TransactionAttribute que se pueden utilizar en un EJB.

Normalmente al programar un EJB se tiende a olvidar el mecanismo que se emplea para administrar la propagación de las transacciones en un modelo CMT (Container Managed Transactions). En este artículo explicaremos los diferentes tipos que existen.

@TransactionAttribute

Esta anotación se puede establecer tanto a nivel de la clase o del método; y su valor por omisión es REQUIRED.
¿Qué quiere decir esto? Lo que significa es que si explícitamente no se define un atributo transaccional, los métodos del EJB serán por default REQUIRED.

REQUIRED

Veamos el siguiente ejemplo:

{% highlight java %}
@Stateless
public class Service01 {
public void insertar(Prueba prueba) {

}

  public List<Prueba> getAll() {
      .....
  }

}
{% endhighlight %}

Este EJB, sería complementamente idéntico a haber escrito lo siguiente:
{% highlight java %}
@Stateless
public class Service01 {
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void insertar(Prueba prueba) {

}

  @TransactionAttribute(TransactionAttributeType.REQUIRED)
  public List<Prueba> getAll() {
      .....
  }

}
{% endhighlight %}

o bien
{% highlight java %}
@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Service01 {

  public void insertar(Prueba prueba) {
     ....
   }

  public List<Prueba> getAll() {
      .....
  }

}
{% endhighlight %}

Debemos tener muy claro que el REQUIRED lo que quiere decir es que se tiene la garantía que el método será ejecutado dentro de una transacción. Si el método se invoca fuera de una transacción, el contenedor va a iniciar una nueva transacción, ejecutar el método y luego comprometer la misma o hacer un rollback según corresponda.

Consideremos el siguiente código:

{% highlight java %}
@Stateless
public class Service01 {

@PersistenceContext
private EntityManager em;

@Inject
private Service02 servicioDos;

public void insertar(Prueba prueba) {
    em.persist(prueba);
    try {
        servicioDos.testRequired();
    } catch (Exception ex) {
      //.....
    }
}

}

@Stateless
public class Service02 {
public void testRequired() throws DemoException {
throw new DemoException();
}
{% endhighlight %}

De acuerdo con el ejemplo, al momento de invocar el método insertar en el EJB Service01 se abre una transacción, se hace un persist de un nuevo registro en la tabla Prueba y de seguido se invoca al método testRequired del EJB Service02 -ese método no tiene ninguna anotación explícita, por lo que es REQUIRED.

El contenedor determina que es ejecutado dentro de una transacción (por lo que no se debe abrir una nueva) y en nuestro ejemplo se genera una Exception. Por tanto, eso causa que la transacción sea reversada como un todo, así que el intento de inserción sobre Prueba no queda registrado.

Es importante recordar que cualquier unchecked exception causaría un rollback, o bien excepciones configuradas como application exceptions, como la siguiente:

{% highlight java %}
@ApplicationException(rollback = true)
class DemoException extends Exception {

public DemoException() {
    super();
}

}
{% endhighlight %}

De particular importancia es el rollback = true; lo que hace que la transacción actual sea reversada cuando se hace un throw de la misma.

REQUIRED_NEW

Si se emplea este atributo el contenedor pondría en pausa la transacción existente y crearía una nueva para la ejecución del método. Es decir, siempre se va a abrir una nueva transacción y el resultado de esa transacción no afectaría a la transacción que queda en pausa.
Por ejemplo:

{% highlight java %}
@Stateless
public class Service01 {

@PersistenceContext
private EntityManager em;

@Inject
private Service02 servicioDos;

public void insertar(Prueba prueba) {
    em.persist(prueba);
    try {
        servicioDos.testRequiredNew();
    } catch (Exception ex) {
      //.....
    }
}

}

@Stateless
public class Service02 {
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void testRequiredNew() throws RuntimeException {
throw new RuntimeException(“Required new”);
}
{% endhighlight %}

En este escenario, aunque el método testRequiredNew genera una excepción el dato que se registra en el metodo insertar de Service01 queda comprometido en la base de datos.

¿Porqué? Por que se abrió una transacción independiente para ejecutar la lógica del método testRequiredNew.

NEVER

En este caso, se tiene la certeza que el método nunca será ejecutado dentro de una transacción. Si quien invoca lo hace dentro de una transacción, el contenedor bloquearía la invocación y lanzaría una excepción. A manera de ilustración:

{% highlight java %}
@Stateless
public class Service01 {

@PersistenceContext
private EntityManager em;

@Inject
private Service02 servicioDos;

public void insertar(Prueba prueba) {
    em.persist(prueba);
    try {
        servicioDos.testNever();
    } catch (Exception ex) {
      //.....
    }
}

}

@Stateless
public class Service02 {
@TransactionAttribute(TransactionAttributeType.NEVER)
public void testNever() throws RuntimeException {
throw new RuntimeException(“Required new”);
}
{% endhighlight %}

Si observamos el log, encontraríamos un error similar a este:

{% highlight java %}
java.rmi.RemoteException: [EJB:011264]Method testNever() is marked TX_NEVER, but was called within a transaction.
{% endhighlight %}

SUPPORTS

Este atributo garantiza que se adoptará el estado transaccional del invocador. Es decir, puede ser invocado con o sin una transacción; el contenedor no hará nada para cambiarlo.

Por ejemplo:

{% highlight java %}
@Stateless
public class Service02 {
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public List<Prueba> getPruebas() throws RuntimeException {

}
{% endhighlight %}

NOT_SUPPORTED

Este atributo lo que establece es que se tiene la garantía que el método no será ejecutado dentro de una transacción. Si el invocador lo hace teniendo una transacción abierta, esta es suspendida; luego se ejecuta el método y posteriormente se continua la transacción del invocador.

En lo personal, tradicionalmente seleccionó ese atributo para todos los métodos que retornan registros de la base de datos. Esto ya que no requiero que haya o no una transacción para lograr el resultado esperado y se evita un overhead.

MANDATORY

El comportamiento que se exhibe con MANDATORY es que debe existir una transacción abierta, de lo contrario una excepción sería lanzada por el contenedor. Es responsabilidad de quien invoca el EJB de suplir tal transacción.

{% highlight java %}
@Stateless
public class Service01 {

@PersistenceContext
private EntityManager em;

@Inject
private Service02 servicioDos;

@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public void insertar(Prueba prueba) {
    ....
    servicioDos.testMandatory();

}

}

@Stateless
public class Service02 {
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public void testMandatory() throws RuntimeException {

}
{% endhighlight %}

Esto nos generaría el siguiente error:
{% highlight java %}
javax.ejb.EJBTransactionRequiredException: Method testMandatory() is deployed as TX_MANDATORY, but it was called without a transaction.
{% endhighlight %}

Tomen en cuenta que en el Servicio01, el invocador tiene la anotación SUPPORTS, por lo que no ha abierto una transacción y en consecuencia se recibe ese error.

Conclusión

Esperamos que con este artículo se aclaren las dudas que generalmente surgen al emplear EJB y los diferentes comportamientos transaccionales que se tienen disponibles. Además de conocer el comportamiento por omisión en un EJB y determinar cual es el más adecuado para cada método que esten programando.

EJB y @TransactionAttribute

Gerardo Arroyo Arce

CEO & Co-Founder

  • ​AWS Community Builder & Ambassador
  • ​AWS Solution Architect - Professional
  • AWS Certified Database – Specialty
  • AWS Certified Security – Specialty
  • AWS Solution Architect - Associate
  • AWS Certified Developer Associate
  • AWS Certified SysOps Administrator Associate
  • Ingeniero en Software. ITCR.
  • Master en Computación en Informática. UCR.