We are using CDI with CMT (container managed transactions) to connect to the database in the web app and mark methods called from the front-end that require a transaction with:
@Transactional(value=TxType.REQUIRES_NEW)
This will create a new CDI transaction, however now if an exception occurs doing this code block or any other code block called from this method it will throw the error message:
javax.transaction.TransactionalException: Managed bean with Transactional annotation and TxType of REQUIRES_NEW encountered exception during commit javax.transaction.RollbackException: Transaction marked for rollback.
...
Caused by: javax.transaction.TransactionalException: Managed bean with Transactional annotation and TxType of REQUIRES_NEW encountered exception during commit javax.transaction.RollbackException: Transaction marked for rollback.
...
Caused by: javax.transaction.RollbackException: Transaction marked for rollback.
Is there anyway to get CDI to re-throw the nested error so that you can easily debug what the real cause of the rollback was?
(Running on Java-EE7, Glassfish 4.0, JSF 2.2.2)
It seems the easiest way to do this is by using a CDI Interceptor to catch the exceptions. We can define the CDI Interceptor as follows:
Once we have defined the CDI Interceptor, we need to create class that is executed when the Interceptor annotation is used. We define an @AroundInvoke so that our code is called before the code in the method we annotate.
invocationContext.proceed()
will call the method we annotate and give us the result (if any) it returned. So we can put atry, catch (Exception)
around this call to catch any kind of Exception that is thrown. Then we can log this exception using the logger (log4j used here), and re-throw the Exception so that any upstream code is also informed of it.Re-throwing the Exception will also allow us to use CMT (Container Managed Transactions) as then ultimately the container will catch the Exception and throw a transaction RollbackException. However, you could easily use UserTransactions with this also and perform a manual rollback when an exception is caught instead of re-throwing it.
Next we must include our new Interceptor in our beans.xml (generally located in src/META-INF), since Interceptors are not enabled by default in CDI. This must be done in ALL projects where the annotation is used, not just the project where the annotation is defined. This is because CDI initializes Interceptors on a per project basis:
Finally, we must annotate the methods that we call with our new CDI Interceptor. Here we annotate them with @Transactional to start a transaction, and @TransactionDebugger to catch any Exception that occurs within the transaction:
This will now log ANY error that occurs while executing the init() code. The logging granularity can be changed by changing the try, catch from Exception to a sub-class of Exception in the Interceptor implementation class TransactionInterceptor.
The
TransactionalException
is thrown at the commit time, i.e. after your code has been fully executed. Since the transaction is marked for rollback, commit cannot take place and the exception is thrown.However, the transaction was marked for rollback sometime during the execution. I assume you do not mark it for rollback manually, therefore an exception must have been thrown. The exception is either a
RuntimeException
or it is annotated with@ApplicationException(rollback = true)
. Since you don't get this exception, the exception must have been caught somewhere. Are you sure you do not catch exceptions thrown from a business method in your code?To answer the question... No, I don't think it is possible to rethrow the original exception, because it is thrown at different time and place.