Tuesday, 7 June 2011

OpenSessionInView - Outside the View Pt. II

This update is a followup to my previous article about extending Hibernate Sessions without using OpenSessionInView - Outside the View . in that article, I introduced the idea of using Spring's HibernateInterceptor and Aspect Oriented Programming to wrap objects that would need extended Hibernate support.

That worked fantastically for us, for about 10 minutes, until we realised that Spring's HibernateInterceptor was only capable of weaving beans referenced in the AppContext. We had a definite requirement to weave objects loaded and instantiated at run-time - for instance objects loaded as part of a Quartz scheduled job.

The solution we came up with was a mix of AspectJ and annotations. We defined a simple annotation called SessionManaged:


public @interface SessionManaged {

}


We will use this annotation to mark classes or methods we want to have Hibernate session scope. We then define our own AspectJ aspect, the HibernateInterceptorAdvice :


@Aspect
@Configurable
public class HibernateInterceptorAdvice {

private static Logger logger = Logger.getLogger(HibernateInterceptorAdvice.class);

@Autowired
private SessionFactory sessionFactory;

HibernateInterceptorAdvice() {
}

// Only execute around @SessionManaged annotated methods/objects
@Around("execution(@com.essensys.bluefin.annotations.SessionManaged * *(..)) && !within(HibernateInterceptorAdvice)")
public Object interceptCall(ProceedingJoinPoint joinPoint) throws Throwable {


/** Perform the Pre-execution logic **/
// Fetch new Session
Session session = SessionFactoryUtils.getSession(sessionFactory, true);

// Get Session Holder
SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager
.getResource(sessionFactory);

// Check for existing session
boolean existingTransaction = (sessionHolder != null && sessionHolder
.containsSession(session));

if(logger.isDebugEnabled())
logger.debug("Existing Session: "+existingTransaction);

// If we have no existing session, create a new one and bind it
if (!existingTransaction) {
if (sessionHolder != null) {
sessionHolder.addSession(session);
} else {
TransactionSynchronizationManager.bindResource(sessionFactory,
new SessionHolder(session));
}
}


/** Perform the Business Logic call and return it's Value **/
// Now we have an opened session, proceed with the execution
try {

Object retVal = joinPoint.proceed();
return retVal;

// Re-throw any exceptions, but make note in the Aspect
} catch (Exception e) {

if(logger.isDebugEnabled())
logger.debug("Exception Encountered in Aspect",e);

throw e;


/** Perform the Post-execution Logic **/
} finally {

// Check to see if we used an existing session, if so do nothing
// if we used a new session, unbind it.
if (existingTransaction) {

if(logger.isDebugEnabled())
logger.debug("Not Unbinding Existing Session");

} else {

// Close Session
SessionFactoryUtils.closeSession(session);

// Check Session is still bound
if (sessionHolder == null
|| sessionHolder.doesNotHoldNonDefaultSession()) {

if(logger.isDebugEnabled())
logger.debug("Unbinding Session");

// Unbind Session
TransactionSynchronizationManager.unbindResource(sessionFactory);
}
}

}

}

}


The reason we use AspectJ for this is that we can weave target objects at load-time, rather than at run-time like the Spring AOP. To make sure this happens, you may need to download a Spring Instrumented classloader for your application server (see the Spring documentation under Load-Time Weaving), and you'll need the following in your AppContext:


<context:component-scan base-package="pkg" />
<context:annotation-config/>

<context:load-time-weaver/>
<context:spring-configured/>


And you'll need to create an aop.xml file in your META-INF directory that will map out your aspects and define which classes in the package are to be weaved:


<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver options="-verbose ">
<!-- only weave classes in our application-specific packages -->
<include within="pkg.console..*"/>
</weaver>

<aspects>
<!-- weave in just this aspect -->
<aspect name="pkg.HibernateInterceptorAdvice"/>
</aspects>
</aspectj>



When a weaved class is loaded, either at run-time or at load-time, this setup should take care of the Hibernate session. If there is an existing one, it will use that, if not it will create a new one, and close it after the execution of the annotated method.