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.