Tuesday, November 27, 2007

Extending the transaction boundaries beyond the EJB layer

A recent e-mail from a reader gave me the chance to write this post about transaction boundary extension. The configuration used is jboss 4.2.2, ejb3/jpa(over hibernate) with stripes as the web tier. Session bean methods that return pojos to the web tier always return detached objects.This is because the automatic transaction management starts the transaction when you call a session bean method and commits the transaction (and closes the associated hibernate session) when the session bean method returns. This means that you have to initialize every lazy field that you may use in the view layer, otherwise a lazy loading exception is waiting for you around the corner. One solution, therefore, is to initialize the lazy fields and do a very good design of your API taking care to return correctly initialized objects for each of your views. Another "lazy" solution is to extend the transaction boundary to the view layer thus keeping the peristence context active throughout each http request. This seems wrong at first glance (it violates layering !!!!), but for small applications I don't think that might be a problem. Nevertheless, each http request completes very quickly so that the transaction is not extended for large periods of time.

One way to do this extension is to use a servlet filter that manages the transactions at the request boundary.

public class TransactionFilter implements Filter {

    /**
     * Logger for the class
     */
    private final static Logger logger = Logger.getLogger(TransactionFilter.class);

    /**
     * @param request The request that we serve
     * @param response The response that we create
     * @param chain The chain of other filters that may have been defined
     * @throws IOException wraps any I/O related exception thrown during
     *             processing
     * @throws ServletException wraps any exception thrown during processing
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest) {
            UserTransaction utx = null;
            try {
                Context ctx = new InitialContext();
                Object ref = ctx.lookup("UserTransaction");
                utx = (UserTransaction) PortableRemoteObject.narrow(ref, UserTransaction.class);
                utx.begin();
                chain.doFilter(request, response);
                int status = utx.getStatus();
                if (status == Status.STATUS_ACTIVE)
                    utx.commit();
                else
                    utx.rollback();
            }
            catch (Throwable e){
                try {
                    if ((utx != null) && (utx.getStatus() == Status.STATUS_ACTIVE))
                        utx.rollback();
                }
                catch (Throwable e1) {
                    logger.error("Cannot rollback transaction", e1);
                }
                if (e instanceof ServletException)
                    throw (ServletException) e;
                if (e instanceof IOException)
                    throw (IOException) e;
                if (e instanceof RuntimeException)
                    throw (RuntimeException) e;
                throw new ServletException(e);
            }
        }
    }

    /* (non-Javadoc)
     * @see javax.servlet.Filter#destroy()
     */
    public void destroy() {
    }

    /* (non-Javadoc)
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    public void init(FilterConfig arg0) throws ServletException {
    }
}


The filter begins a transaction at the beginning of each http request and commits at the end. Each call to session bean methods has already an active transaction associated so the session bean does not start a new one (if it is configured with TransactionAttribute REQUIRED). Thus the transaction and the associated hibernate session are active throughout the request and we can use the pojos in the action classes (struts, stripes) or in jsps without caring about uninitialized lazy fields.

Don't forget to configure the filter to intercept every request associated with the view layer e.g.

    <filter-mapping>
        <filter-name>TransactionFilter</filter-name>
        <url-pattern>*.jsp</url-pattern>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>


    <filter-mapping>
        <filter-name>TransactionFilter</filter-name>
        <servlet-name>StripesDispatcher</servlet-name>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>

2 comments:

Anonymous said...

Neat, I was thinking about how to solve the issues with lazily loading entities from the view layer (this is just *too* convenient to do without), but then I found this blog post which should cure all my problems :)

However, apparently this behaviour shouldn't be taken as granted -- apparently according to a presentation by Sun engineers at http://developers.sun.com/learning/javaoneonline/2007/pdf/TS-4593.pdf there is no guarantee that whatever is behind the filter actually gets executed in the same thread as the filter.

Unknown said...

True. It is mentioned in the presentation that the servlet 's service method may run in a thread different than the filter 's. I just can't see why. Anyway, I forgot to mention that I use jboss and until now it seems that each request is served by a single thread, so with jboss there should not be a problem.