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:<
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:
@InterceptorBinding
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TransactionDebugger {
}
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 a try, 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.
@Interceptor
@TransactionDebugger
public class TransactionInterceptor {
private Logger logger = LogManager.getLogger();
@AroundInvoke
public Object runInTransaction(InvocationContext invocationContext) throws Exception {
Object result = null;
try {
result = invocationContext.proceed();
} catch (Exception e) {
logger.error("Error encountered during Transaction.", e);
throw e;
}
return result;
}
}
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:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
version="1.1" bean-discovery-mode="all">
<interceptors>
<class>package.database.TransactionInterceptor</class>
</interceptors>
</beans>
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:
@Transactional @TransactionDebugger
public void init() {
...
}
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.