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.