Monday, December 7, 2009

CDI and Declarative Transactions...

Ok, so since I'm already on the books for declaring that CDI/JSR-299 will become the dominant framework of the Java EE standards, while pushing EJB 3 off to the side, let's start to figure out how this could work... I don't think too many people would argue that the number one reason for using EJB 3.x Session beans are the Declarative Transaction support -- that is, being able to create and commit a new Transaction around a method call by annotating it with @TransactionAttribute(TransactionAttributeType.REQUIRED) -- and compared to the boilerplate code that needs to be put in place to do it programatically, it is indeed a pretty nice feature...

So how can this be done in CDI? Simple -- with Interceptors... CDI actually reuses the interceptor support from EJB 3, but it takes it a step further -- it allows you to use a custom annotation to bind your code to the interceptor definition in a pretty slick way... but first, consider the following interceptor, which might be the most basic implementation of Declarative Transactions:

//import removed...

@RequiredTx @Interceptor
public class RequiredTransactionInterceptor implements Serializable {
@Resource
private UserTransaction utx;

@AroundInvoke
public Object openIfNoTransaction(InvocationContext ic) throws Throwable {
boolean startedTransaction = false;
if(utx.getStatus() != Status.STATUS_ACTIVE) {
utx.begin();
startedTransaction = true;
}

Object ret = null;
try {
ret = ic.proceed();

if(startedTransaction)
utx.commit();
} catch(Throwable t) {
if(startedTransaction)
utx.rollback();

throw t;
}

return ret;
}
}


You'll notice that most of this looks familiar -- we're not implementing any particular Interface, we can use any old name for our wrap-around method, we use the @AroundInvoke annotation to indicate which method is to be used for intercepting, and how it's used, etc. The only thing new here comes on the first line -- the dual annotations of @RequiredTx and @Interceptor... so far this is wicked easy!

So what the heck are those annotations? Well, the @Interceptor annotation is part of the javax.interceptor package, and was added as part of the CDI spec -- clever how they snuck a new annotation in but didn't put it under any CDI-specific package, eh?

The other annotation is one of my own, and when used in concert with @Interceptor, it acts as the 'binding definition' of the interceptor -- you can then put the @RequiredTx annotation on any method or class of any CDI bean, and it will be as if you've annotated an EJB with '@Interceptors(RequiredTransactionInterceptor.class)', only not as wordy (and why would you write a transactional interceptor that mimics the EJB usage, and then use it on an EJB?)

Without further ado, the definition of our annotation:

//more imports removed...

@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiredTx {
}


Not much here -- the only thing you may not have seen before is that InterceptorBinding annotation, which simply declares that this annotation is used to bind an interceptor to a method or class... simple -- but how is it used? This is the cool part -- just annotate any old CDI bean with this, and you're good to go -- like this:

//yep -- no imports here either...

@Named
public class UserGetterer {
@RequiredTx
public User loadUser() {
\\Do some fancy JPA loadin' here
}
}



Load that up in your webapp, point your fancy JSF 2.0 preRenderView event to "#{userGetterer.loadUser()}", and voila -- you are loading your data in a transaction... sweet!

Doh -- almost forgot
What's that? It didn't work? Crap... oh wait, one last thing -- we need to enable our interceptor... This part is easy to forget, but easy to do -- you need to add an <interceptors> element to your beans.xml... (you did add the beans.xml file, right?)... try this:


<beans>
<interceptors>
<class>com.your.package.RequiredTransactionInterceptor</class>
</interceptors>
</beans>



Yeah, I know -- it feels kind of like we're exposing your implementation detail by asking the user to enable the interceptor instead of the annotation... kind of annoying, but I can deal with it... another thing to note is that if you pack your interceptors into a shareable library file, all other library files in your application will still need to do this, even if you enable it in your .jar file -- that's because there's no concept of a global interceptor in CDI, but to be safe, each library is forced to enable all interceptors in use, so that they can enforce the ordering of interceptors in a very intuitive manner... I can accept this reasoning simply because they have defined it, rather than letting it be an undefined mess, so again, it's more of annoyance for me -- hopefully there will be other options in the future... (Note - due to a bug in the current version of Weld, Glassfish and JBoss will both have a problem with this -- the work around at the moment is to include all of your <interceptors> definitions in one library file, or your .war file directly)

Sum It Up
So there you have it -- a bare minimum implementation of Declarative Transaction support for CDI that can be applied to both classes or methods in less than 40 lines of code... easy, and very reusable! It would be similarly easy to implement for the other EJB TransactionAttributeTypes (SUPPORTS, REQUIRES_NEW, etc), although there is some coordination to be done to support putting one annotation on a class and another on a method without having them tripping over each other...

All in all, it provides a very straight forward programming model that is at least as clean as the EJB 3.0 model -- cleaner in many ways, actually, as you'll have @RequiredTx instead of @Interceptors(RequiredTransactionInterceptor.class), better context management with @RequestScoped, @SessionScoped and @ConversationScoped instead of just @Stateful, and much better integration with JSF... Plus, you'll have the benefit of being able to customize it if you need to, adding support for things like timeouts, isolation levels, or custom logging... very nice, indeed!

M



12 comments:

RickHigh said...

The link to my site is broken. You added an extra t. :)

--Rick Hightower

Matt Corey said...

Dang, that means it's been like that for over a year :)... should be better now -- sorry about that!

M

Anonymous said...

It is rather interesting for me to read that article. Thanks for it. I like such topics and everything connected to this matter. I would like to read more soon.
Alex
Cell phone blocker

Andy said...

Great example, I was playing around with it, and found that when it is applied to a method, my @PersistenceContext annotated entity manager is coming out null.

It's like it executes the intercepted method before its finished setting up the bean?

Did you actually manage to get this code running?

N W said...

Seems like you have to come up with this boilerplate Interceptor code when if you just made it and SSB with @Stateless you would get the TX management for free. I am not seeing the value here.

Anonymous said...

hi

thanks for this example how to implement such an interceptor for transaction support. It works very well. :)

Now I want the transaction support also for methods in my business logic which are already annotated with @PostConstruct and @ApplicationScoped. In this case the interceptor isn't called, isn't it?

Is there a difference between lifecylce methods and "normal" methods from this perspective?


thanks a lot

Florian

Anonymous said...

I think this author is mad!! Why is he writing a BOILERPLATE code for transaction management while there is a way for getting it without any code at all (by using EJB)!!

Anonymous said...

The author is not mad at all, he is just trying to avoid to have to write an ejb method for every button click in his webapp. He is writing boilerplate code once, you are writing it all the time :-)

My predictions for JEE7: you will see that you can specify transaction attributes on servlet and jsf methods, and there will basically be no more need for explicit stateless local ejbs.

Why do you want to avoid ejbs? Because you can't easily inject jsf stuff into it, which leads to the need to pass things by parameters.

Btw, you could actually implement the interceptor as an ejb3 which then calls proceed() from an ejb method with the required transactionality, like this we are using ejb's declarative transaction management once, and reuse it everywhere.

Anonymous said...

Some people call this approach as "BOILER-PLATE CODE", some others call this as "REINVENTING THE WHEEL" but I call this as "FUCKING STUPID METHOD".

Anonymous said...

Thanks for good code!

Anonymous said...

Thanks for this useful article.

Transaction management with CDI is actually quite tempting is that means you can throw away your EJBs

Unknown said...

This is great article! Saved my few days after moving out my project from Seam3