Member Menu
 
 Monthly JBoss newsletter:
 
Hibernate Books
CaveatEmptor

Session handling with AOP

The following example uses JBoss AOP to apply a custom interceptor to any Java method, in any runtime environment.

This is a more flexible alternative to a servlet filter (i.e. the Open Session in View pattern), or most other kinds of interceptors. It wraps a Hibernate Session and a database transaction around any pointcut. Usually you would wrap this interceptor around a service facade method that needs a persistence context, or, in other words, that uses DAOs.

This AOP interceptor should be used if your hibernate.current_session_context_class configuration option is set to thread and you are not using JTA and/or CMT. However, you could easily rewrite it to work with JTA and programmatic transaction demarcation with the UserTransaction API. If your application uses EJBs and container-managed transactions, you don't need any of this. Sessions and transactions is required reading to understand this interceptor and where it is applicable.

Note that this filter always propagates a transaction if one wrapped method would call another wrapped method - the "current" Session is also propagated automatically.

public class SessionTransactionInterceptor
    implements org.jboss.aop.advice.Interceptor {

    private static Log log = LogFactory.getLog(SessionTransactionInterceptor.class);

    private static SessionFactory sf = HibernateUtil.getSessionFactory();

    public String getName() {
        return "SessionTransactionInterceptor";
    }

    public Object invoke(Invocation invocation) throws Throwable {

        try {
            boolean isActive = sf.getCurrentSession().getTransaction().isActive();
            if ( !isActive) {
               log.debug("Starting a database transaction");
               sf.getCurrentSession().beginTransaction();
            }

            log.debug("Invoking the aspectized service method");
            Object result = invocation.invokeNext();

            // Commit and cleanup
            if (!isActive) {
               log.debug("Committing the database transaction");
               sf.getCurrentSession().getTransaction().commit();
            }

            return result;

        } catch (StaleObjectStateException staleEx) {
            log.error("This interceptor does not implement optimistic concurrency control!");
            log.error("Your application will not work until you add compensation actions!");
            // Rollback, close everything, possibly compensate for any permanent changes
            // during the conversation, and finally restart business conversation. Maybe
            // give the user of the application a chance to merge some of his work with
            // fresh data... what you do here depends on your applications design.
            throw staleEx;
        } catch (Throwable ex) {
            // Rollback only
            try {
                log.warn("Trying to rollback database transaction after exception");
                sf.getCurrentSession().getTransaction().rollback();
            } catch (Throwable rbEx) {
                log.error("Could not rollback transaction after exception!", rbEx);
            }
            // Let others handle it... maybe another interceptor for exceptions?
            throw ex;
        }
    }

}

Now define the AOP pointcuts in your META-INF/jboss-aop.xml descriptor:

<aop>

    <!--
        Wrap all execute() methods in all Action (interface) implementors inside a database transaction
        and a thread-bound persistence context. If one execute() method should call another
        execute() method, both transaction and persistence context are propagated.
    -->
    <bind pointcut="execution(* $instanceof{my.actions.Action}->execute(..))">
        <interceptor class="my.persistence.SessionTransactionInterceptor"/>
    </bind>

</aop>

To weave the interceptor into your bytecode, use either build time instrumentation in your Ant build.xml:

<taskdef name="aopc" classname="org.jboss.aop.ant.AopC">
    <classpath refid="project.libs"/>
</taskdef>

<target name="instrument.aop" depends="compile"
        description="Run the AOP compiler to weave interception code">

    <aopc classpath="${classes.dir}">
        <compilerclasspath><path refid="project.libs"/></compilerclasspath>
        <src path="${classes.dir}"/>
        <include name="**/*.class"/>
    </aopc>
</target>

Or inject the bytecode through a custom boot classloader:

<target name="classloaderaop.run" depends="compile"
    description="Run with load-time AOP instrumentation">
    <echo>To run on JDK 1.4 download JBoss AOP and don't use lib/api/jboss-aop-jdk5.jar!</echo>

    <!-- Compile a custom classloader first, for JDK 5.0 (see note above) -->
    <java
        classname="org.jboss.aop.hook.GenerateInstrumentedClassLoader"
        fork="yes"
        failonerror="true">
        <arg value="${build.dir}/custom_aop_loader"/>
        <classpath refid="project.libs"/>
    </java>

    <!-- Need custom bootclasspath... -->
    <path id="custom.bootclasspath">
        <fileset dir="${lib.dir}">
            <include name="**/jboss-ejb3-all*.jar"/>
            <include name="**/thirdparty-all*.jar"/>
        </fileset>
        <pathelement path="${build.dir}/custom_aop_loader"/>
    </path>
    <property name="bootclasspath" refid="custom.bootclasspath"/>

    <!-- Run TestNG tests with custom classloader in boot classpath, prepended -->
    <mkdir dir="${testng.out.dir}"/>
    <testng outputDir="${testng.out.dir}">
        <jvmarg value="-Xbootclasspath/p:${bootclasspath}"/>
        <jvmarg value="-Djboss.aop.exclude=org.hibernate.impl"/>
        <!-- Ignoring these prevents warnings -->
        <jvmarg value="-Djboss.aop.ignore=*ByCGLIB$$*,*dom4j*,*oracle*,*objectweb*"/>
        <classpath>
            <path refid="project.libs"/>
            <pathelement path="${classes.dir}"/>
        </classpath>
        <xmlfileset dir="${etc.dir}">
            <include name="testsuite-aop.xml"/>
        </xmlfileset>
    </testng>
</target>
© Copyright 2006, Red Hat Middleware, LLC. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc. [Privacy Policy]