Java EE EJB transaction propagation (@TransactionAttribute) tutorial
Introduction
While dealing with Container Managed Transactions (CMT) in EJB, the application developer is able to declaratively define how an arbitrary call to an EJB method should behave in terms of transaction propagation. In other words, the developer is able to define if a previously existing transaction can be used, or if a new transaction should be opened leaving the existing one suspended, etc.
In this tutorial we will see the distinct transaction propagation behaviour provided by an EJB container.@TransactionAttribute annotation
The @TransactionAttribute annotation may be used both at the type level and method level. When defined at the type level it states that all methods in a given EJB should have the declared transaction propagation behaviour. When applied to a single method, only that method will have that propagation behaviour, overriding the type level definition (if any).
@Stateless @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public class InnerBean { // ... }
@Stateless public class InnerBean { @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void testRequiresNew() { // ... } }
REQUIRED behaviour
The REQUIRED behaviour is the default behaviour for EJB methods. This means that if we don't explicitly define the @TransactionAttribute annotation, the defined EJB method will have REQUIRED as transactional attribute value.
@Stateless public class OuterBean { @PersistenceContext private EntityManager em; @EJB private InnerBean innerBean; @TransactionAttribute(TransactionAttributeType.REQUIRED) public void insertUser(User user){ em.persist(user); try { innerBean.testRequired(); } catch (TestException e) { // handle exception } } }
@Stateless public class InnerBean { @TransactionAttribute(TransactionAttributeType.REQUIRED) public void testRequired() throws TestException { throw new TestException(); } }
In this specific example, the OuterBean persists an entity - user - and then calls a method from an inner EJB: InnerBean. The inner EJB method is annotated with REQUIRED behaviour so this means that it will use the same transaction that was opened in the outer EJB. Since the inner EJB is throwing an exception that is configured to rollback existing transactions (we will also see this shortly) it will cause the transaction to completely rollback as a whole, so the entity persisted by the outer bean will not be persisted in the database.
During EJB method execution, there are a couple of exception types that cause a transaction to rollback. One is unchecked exceptions (like RuntimeException). The other are all the exceptions that are configured as application exceptions that cause transactions to rollback. We used TestException in the previous sample. This exception is defined as the following:
import javax.ejb.ApplicationException; @ApplicationException(rollback=true) public class TestException extends Exception { private static final long serialVersionUID = 1L; public TestException(){ super(); } }
Note the @ApplicationException annotation with rollback=true. This configures the exception in cause to be considered an application exception that causes a current transaction to rollback when thrown.
REQUIRES_NEW behaviour
The REQUIRES_NEW behaviour will pause any existing transaction and will create a new one for the current method execution. The newly created transaction outcome will not affect the outer transaction outcome. The existing transaction outcome will also not impact the newly created transaction outcome: They behave independently. When the inner method completes and its transaction is also completed, the outer transaction will then be resumed.
@Stateless public class OuterBean { @PersistenceContext private EntityManager em; @EJB private InnerBean innerBean; @TransactionAttribute(TransactionAttributeType.REQUIRED) public void insertUser(User user){ em.persist(user); try { innerBean.testRequiresNew(); } catch (TestException e) { // handle exception } } }
@Stateless public class InnerBean { @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void testRequiresNew() throws TestException { throw new TestException(); } }
The inner EJB method is annotated with REQUIRES_NEW behaviour so it will run in an newly created transaction. Meanwhile the outer bean transaction will be paused. This means that the exception being thrown inside testRequiresNew() method will cause the inner bean transaction to rollback but the outer bean transaction will remain unaffected, so the entity being persisted in the outer bean will be persisted to the database after transaction conclusion.
If there is not any opened transaction when a method annotated with REQUIRES_NEW is executed, the container will open a new one and use it.
MANDATORY behaviour
The MANDATORY behaviour states that an existing opened transaction must already exist. If it doesn't an exception will be thrown by the container.
NEVER behaviour
The NEVER behaviour states that an existing opened transaction must not already exist. If an opened transaction already exists the container will throw an exception.
NOT_SUPPORTED behaviour
The NOT_SUPPORTED behaviour will execute outside of the scope of any transaction. If an opened transaction already exists it will be paused and later resumed after method execution.
SUPPORTS behaviour
The SUPPORTS behaviour will execute in the scope of a transaction if an opened transaction already exists. If there isn't an already opened transaction the method will execute anyway but in a non-transactional way.