Nested Transaction and EJBContext's setRollbackOnly()

Tapas Bose picture Tapas Bose · Dec 29, 2013 · Viewed 13.4k times · Source

I am reading the Transaction Management of Java EE 7 and I get confused by the concept of nested transaction and the functionality of EJBContext#setRollbackOnly().

Say I have two Session Beans, Bean1Impl and Bean2Impl and their signatures are:

@Stateless 
@TransactionManagement(TransactionManagementType.CONTAINER)
public class Bean1Impl implements Bean1 {

    @Resource 
    private EJBContext context;

    @TransactionAttribute(REQUIRED)
    public void method1() {
        try {
            //some operations such as persist(), merge() or remove().
        }catch(Throwable th){
            context.setRollbackOnly();
        }
    }
}

@Stateless 
@TransactionManagement(TransactionManagementType.CONTAINER)
public class Bean2Impl implements Bean2 {

    @Resource 
    private EJBContext context;

    @TransactionAttribute(REQUIRED)
    public void method2() {
        try {
            //some operations such as persist(), merge() or remove().
            //an exception has been thrown
        }catch(Throwable th){
            context.setRollbackOnly();
        }
    }
}

As stated in Java EE 7 Tutorial:

51.3.1.1 Required Attribute

If the client is running within a transaction and invokes the enterprise bean's method, the method executes within the client's transaction. If the client is not associated with a transaction, the container starts a new transaction before running the method.

The Required attribute is the implicit transaction attribute for all enterprise bean methods running with container-managed transaction demarcation. You typically do not set the Required attribute unless you need to override another transaction attribute. Because transaction attributes are declarative, you can easily change them later.

In this case I don't need to specify @TransactionAttribute(REQUIRED) annotation declaration in the methods Bean1Impl#method1() and Bean2Impl#method2(). Am I right?

So in the above code the transaction of Bean2Impl#method2() would be running within the transaction of Bean1Impl#method1().

Can I consider it as a Nested Transaction?

If there is an Exception has been thrown inside the method Bean2Impl#method2() which eventually would lead to a call to the method EJBContext.setRollbackOnly() from the catch block and as expected it should roll back the operations performed within the try block of this method. In this case what would happen to the transaction and as well as the Bean1Impl#method1(). Would it be rolled back as well? What I mean is:

What would happen if there is a call of EJBContext.setRollbackOnly() from Bean2Impl#method2() and

  • Bean2Impl#method2() is called from the method Bean1Impl#method1() before any database operation like persist, merge or remove.
  • Bean2Impl#method2() is called from the method Bean1Impl#method1() after any database operation like persist, merge or remove.

And lastly what would happen if the method Bean2Impl#method2() get executed successfully but EJBContext.setRollbackOnly() is called from Bean1Impl#method1() after successful return of Bean2Impl#method2()?

Answer

kostja picture kostja · Dec 29, 2013

To add to @Philippe Marshall's correct answer and your comment - REQUIRES_NEW will create a new transaction, independent from the first one. They are not nested. The first transaction is suspended while the second is active. Once the second transaction commits, the first one is resumed.

You do not have to setRollbackOnly() manually. Most PersistenceExceptions will do that if needed. You will gain nothing by rolling back transactions that you don't have to. For example, when querying data, you may get a NoResultException or a NonUniqueResultException. They do not cause a transaction to be rolled back as there is no risk of inconsistencies between the persistence context and the DB.

You do not need to specify neither @TransactionAttribute(REQUIRED) nor @TransactionManagement(TransactionManagementType.CONTAINER)- both are the default settings.

EDIT: to answer your further questions:

I am assuming @TransactionAttribute(REQUIRES_NEW) on method2 and therefore two separate transactions.

If there is an Exception that leads to the rollback of the transaction in method2, the transaction from method1 will not be rolled back if the Exception is caught. If the Exception is not caught, both transactions will be rolled back.

When setting the rollback flag on a transaction, it does not matter whether it happens before or after DB operations, since the entire transaction is rolled back.

Once method2 returns, it's transaction is committed. Rolling back or committing the transaction from method1 afterwards has no influence on the results of the first transaction.

A general advice - do not catch Throwable - it is much too broad and you might swallow exceptions which you would rather let propagate to the surface.