In Java EE applications you are safe to consider that every method in a session bean has an associated transaction, since there is an implicit declaration of the transaction attribute required. If you like to change this behavior you have to configure this proactively by adding the annotation @TransactionAttribute
with another value (see enum TransactionAttributeType
). Context and Dependency Injection (CDI) does not have such an implicit declaration and no direct container managed support for transactions. But it has a very nice realization of the interceptor concept. This post shows, how to facilitate an interceptor in order to add a transaction to every (or a selection) method in a CDI bean, if it does not already exist. This is the default behavior of required.
In CDI an interceptor can be binded to an annotation, so that annotating, e.g., a method or type adds an @AroundInvoke
advice to the method or all methods of the type respectively. The binding annotation may look like this:
@InterceptorBinding @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface RequiredTx { }
The annotation @InterceptorBinding
declares, that this annotation binds an interceptor. We will see this later on. Next we define the interceptor:
@RequiredTx @Interceptor public class RequiredTransactionInterceptor implements Serializable { @Resource private UserTransaction tx; @AroundInvoke public Object beginTransactionIfNotActive(InvocationContext ic) throws Throwable { boolean newTransaction = false; if (tx.getStatus() != Status.STATUS_ACTIVE) { utx.begin(); newTransaction = true; } Object retVal = null; try { retVal = ic.proceed(); if (newTransaction) { utx.commit(); } } catch (Throwable t) { if (newTransaction) { tx.rollback(); } throw t; } return retVal; } }
The interceptor is annotated with the @Interceptor
annotation and the binding annotation, in order to tell the container that this interceptor is binded with the latter and the method annotated with @AroundInvoke
will be executed before the intercepted method. The wrapped method is invoked via ic.proceed()
.
Now we can bind the interceptor to our CDI beans like this:
@Named("dummyController") @RequestScoped @RequiredTx public class DummyControllerImpl implements DummyController { // ... }
It is possible to annotate a selection of methods in a bean, too. One ugly thing has to be done, though. Although everything has been annotated correctly and the information would be sufficient for the CDI container, it is necessary to activate the interceptor in the beans.xml
situated in the META-INF
folder (in maven projects in src/main/resources
):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"> <interceptors> <class>de.dummy.core.RequiredTransactionInterceptor</class> </interceptors> </beans>
Unlike in Java EE, with implicit transaction handling, you can now add time measurement for the method and the transaction commit separately. This is a very nice feature for validating non-functional properties of your application.
Interesting post! Despite all the involved indirection, I like the elegance of CDI.
@Sven: Indirection “as it is” is a nice thing, but in a container environment like an application server you get it all “for free”, i.e., you don’t want all of it, but hey it’s there…
CDI on the other hand gives you the control to add indirection and therefor container facilities step by step. If you think: “enough is enough” you may stop adding more indirection. But all that glistens is not gold.
Nice post!
BTW: that is going to be a bitch if transaction is in state STATUS_MARKED_ROLLBACK or other states in which you cannot start a new transaction:
if (tx.getStatus() != Status.STATUS_ACTIVE) {
utx.begin();
}
@Daniel: Uh, you are right my little example snippet is not feature complete. I would advice to throw an appropriate exception in that case… What do you think?