Friday, 20 March 2009

Lightweight transaction handling in Tomcat

With Enterprise Java Beans (EJB) used with Container-Managed Transactions (CMT), transactions are transparently handled by the application server. When a method is marked with @TransactionAttribute(REQUIRED) then it starts a transaction (if one is not already started) and on competition of the method commits it (if it created the transaction). If an exception occurs, then the container rolls back the transaction. The beauty of it, is that it requires no extra code, just a single annotation.

However, if running outside an application server, e.g. in a Tomcat only environment, then container managed transactions cannot be used. The common approach would be to use Spring.
Spring provides the same functionality as EJB's but with a few drawbacks. It requires a large number of libraries to be included in your classpath plus a cumbersome to maintain configuration file just to set up some basic transaction handling. Spring does have its advantages but is overkill in a lot of cases.

Enter Google Guice. Google's dependancy injection 'framework'.

Guice and be configured as simply or as complex as you want.
In the most simple case, guice requires very little configuration. Unless specified, all beans have no scope. That is, they are single use - new instances are created for each injection. POJO services that don't implement an interface don't even need to be named. If you so chose, you could create an empty configuration Module and let everything auto-wire.

Onto transaction handling.
Guice 1 doesn't provide any mechanism to perform transaction handling. However it turns out that doesn't matter as it takes very little effort to implement a simple transaction handler.
The trick is to define an annotation, then bind an interceptor to all methods that are annotated. The interception is done at load time, so all the complex matching is done once with Guice's internal CGLib proxies created.

First create the annotation to use (you could use JEE ones if you wanted but would need to put them in the class path).
@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.METHOD, ElementType.TYPE })
public @interface Transactional
{
}


Next define your Module. Modules are Guice's configuration.
import static com.google.inject.matcher.Matchers.annotatedWith;
import static com.google.inject.matcher.Matchers.subclassesOf;

import org.owatta.service.AbstractService;
import org.owatta.annotation.Transactional;

import com.google.inject.AbstractModule;

public class ServiceModule extends AbstractModule
   {
   @Override
   protected void configure()
   {
      //this line tells guice to bind TransactionInterceptor to any class
      //that is a subclass of AbstractService and is annotated with @Transactional
      //the subclassesOf() could be replaced with Matchers.any()
      bindInterceptor(subclassesOf(AbstractService.class), annotatedWith(Transactional.class), new TransactionInterceptor());
   }
}

Next create your interceptor. Since I only need to handle transaction directly on a connection my transaction interceptor requires little work.
/**
*
*/
package org.owatta.modules;

import java.sql.Connection;
import java.sql.SQLException;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Logger;
import org.owatta.web.FacesFilter;

/**
* Simple transaction interceptor. Commits on success, rolls back on error.
* @author Chris Watts
*/
public class TransactionInterceptor implements MethodInterceptor
{
   private final static Logger log = Logger.getLogger(TransactionInterceptor.class);

   public Object invoke(MethodInvocation arg0) throws Throwable
   {
      Object returnValue = null;
      try
      {
         returnValue = arg0.proceed();
         Connection con = FacesFilter.getConnection();
         if (con == null)
         {
            log.warn("No bound connection to rollback");
         }
         else
         {
            try
            {
               con.commit();
            }
            catch (SQLException e)
            {
               log.error("Error commiting transaction, rolling back", e);
               //make sure the caller knows the commit failed
               throw new RuntimeException(e);
            }
         }
         return returnValue;
      }
      catch (Throwable t)
      {
         rollback();
         throw t;
      }
   }

   /**
    * perform rollback
    */
   void rollback()
   {
      Connection con = FacesFilter.getConnection();
      if (con == null)
      {
         log.warn("No bound connection to rollback");
         return;
      }
      log.info("Rolling back transaction");
      try
      {
         con.rollback();
      }
      catch (SQLException e)
      {
         log.warn(e);
      }
   }
}

Since I'm using JSF, I'm using GuiceSF as a variable resolver in order to inject the managed beans. I put the following in my web.xml:
<!-- guice / jsf -->
<context-param>
   <param-name>com.guicesf.Modules</param-name>
   <param-value>com.google.inject.servlet.ServletModule,org.owatta.modules.ServiceModule</param-value>
</context-param>

<!-- Entity Manager Filter declaration -->
<filter>
   <filter-name>GuiceFilter</filter-name>
   <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>

<filter-mapping>
   <filter-name>GuiceFilter</filter-name>
   <url-pattern>*.faces</url-pattern>
</filter-mapping>

This is actually 2 parts. The first part configures GuiceSF, the 2nd configures Guice so it can use request / session scoped beans. Depending on your needs this may not be necessary.
The final JSF step is to configure the variable resolver, add the following to faces-config.xml:
<application>
<!-- injector for managed beans -->
<el-resolver>com.guicesf.GuiceResolver</el-resolver>
</application>
The final step is to put the required jars on the class path:
  • guice-1.0.jar
  • aopalliance.jar
  • guice-servlet-1.0.jar (only required if you want servlet scope handling)
  • guicesf-0.1.jar (for JSF integration)
The elegance of this solution is that it is VERY lightweight, very little lock-in and complete control.

2 comments:

  1. Container managed transaction is possible with tomcat or not. I am new to this concept. please help.

    ReplyDelete
  2. Tomcat is not a full JEE server (it only implements the servlet/jsp api). Hence the above article on doing my own transaction handling.

    If you want to do CMT then you'll need to run a full JEE/EJB server. Or your could run something like OpenEJB inside tomcat.

    ReplyDelete