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:

  1. public @interface SessionManaged {  
  2.   
  3. }  


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 :

  1. @Aspect  
  2. @Configurable  
  3. public class HibernateInterceptorAdvice {  
  4.   
  5.  private static Logger logger = Logger.getLogger(HibernateInterceptorAdvice.class);  
  6.    
  7.  @Autowired  
  8.  private SessionFactory sessionFactory;  
  9.   
  10.  HibernateInterceptorAdvice() {  
  11.  }  
  12.   
  13.  // Only execute around @SessionManaged annotated methods/objects  
  14.  @Around("execution(@com.essensys.bluefin.annotations.SessionManaged * *(..)) && !within(HibernateInterceptorAdvice)")  
  15.  public Object interceptCall(ProceedingJoinPoint joinPoint) throws Throwable {  
  16.   
  17.     
  18.   /** Perform the Pre-execution logic **/  
  19.   // Fetch new Session  
  20.   Session session = SessionFactoryUtils.getSession(sessionFactory, true);  
  21.     
  22.   // Get Session Holder  
  23.   SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager  
  24.     .getResource(sessionFactory);  
  25.   
  26.   // Check for existing session  
  27.   boolean existingTransaction = (sessionHolder != null && sessionHolder  
  28.     .containsSession(session));  
  29.   
  30.   if(logger.isDebugEnabled())  
  31.    logger.debug("Existing Session: "+existingTransaction);  
  32.      
  33.   // If we have no existing session, create a new one and bind it  
  34.   if (!existingTransaction) {  
  35.    if (sessionHolder != null) {  
  36.     sessionHolder.addSession(session);  
  37.    } else {  
  38.     TransactionSynchronizationManager.bindResource(sessionFactory,  
  39.       new SessionHolder(session));  
  40.    }  
  41.   }  
  42.   
  43.     
  44.   /** Perform the Business Logic call and return it's Value **/  
  45.   // Now we have an opened session, proceed with the execution  
  46.   try {  
  47.   
  48.    Object retVal = joinPoint.proceed();  
  49.    return retVal;  
  50.      
  51.    // Re-throw any exceptions, but make note in the Aspect  
  52.   } catch (Exception e) {  
  53.   
  54.    if(logger.isDebugEnabled())  
  55.     logger.debug("Exception Encountered in Aspect",e);  
  56.      
  57.    throw e;  
  58.   
  59.     
  60.   /** Perform the Post-execution Logic **/  
  61.   } finally {  
  62.      
  63.    // Check to see if we used an existing session, if so do nothing  
  64.    // if we used a new session, unbind it.  
  65.    if (existingTransaction) {  
  66.       
  67.     if(logger.isDebugEnabled())  
  68.      logger.debug("Not Unbinding Existing Session");  
  69.       
  70.    } else {  
  71.       
  72.     // Close Session  
  73.     SessionFactoryUtils.closeSession(session);  
  74.      
  75.     // Check Session is still bound  
  76.     if (sessionHolder == null  
  77.       || sessionHolder.doesNotHoldNonDefaultSession()) {  
  78.        
  79.      if(logger.isDebugEnabled())  
  80.       logger.debug("Unbinding Session");  
  81.        
  82.      // Unbind Session  
  83.      TransactionSynchronizationManager.unbindResource(sessionFactory);  
  84.     }  
  85.    }  
  86.      
  87.   }  
  88.   
  89.  }  
  90.   
  91. }  


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.