Thursday, 6 January 2011

OpenSessionInView - Outside the View

OpenSessionInView is a well known session management pattern, used to open and close sessions in a web applicaiton at the start/end of an HTTP session. This assumes that the HTTP session actually represents a viable unit of work, and as such it allows things such as Object Relational Mapping managers to work within a single session for the duration of the user's HTTP session. Why would this be necessary? The key driver in my work for using OpenSessionInView has been the use of Lazy Initialization for Hibernate relationships. For instance, Object A has a relation to a set of Object B. Loading Object A through a Hibernate DAO is fine. But passing Object A to Object C, which then tries to call A.getB(), will throw a LazyInit exception with a 'no session' error. Using OpenSessionInView allows the same session to be used for the duration of the HTTP request that triggered this set of work, meaning that you can lazily load any depth of relationships (A.getB().getD().getE()... ) without worrying. Of course, you could always just tell all of your relationships to Eagerly load, but if you have a tightly related data model you may well end up loading your whole database into memory everytime you pull an object from a DAO.

So OpenSessionInView works very nicely with servelet/HTTP based systems, and it is very easy to set up in Spring, there's loads of documentation on line, and at its core it just needs you to add a Servlet Filter to your web.xml :


<filter>
<filter-name>openSessionInViewFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
</filter-class>
<init-param>
<param-name>singleSession</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>flushMode</param-name>
<param-value>AUTO</param-value>
</init-param>
<init-param>
<param-name>sessionFactoryBeanName</param-name>
<param-value>sessionFactory</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>openSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>



But what if the actions you want to take are not initiated by a request from the View? In that case, a servlet filter can't help - it will never be fired by the filter chain as there's never a servlet called! What we need is a way to wrap our Session around a particular chain of execution. Some areas that I have come across this requirement are when calling some functionality at load time (usually during development), and when calling functionality through a scheduling API like Quartz. In these cases, we will get Session errors due to lazy loading even though our OpenSessionInView filter is working fine.

Thankfully Spring provides a variation on the Servlet filter that can be applied as an Aspect to any given Object, without needing any re-coding, and without needing to roll our own session handling code in any business object that needs this functionality. This is called the OpenSessionInView Interceptor, and is functionally the same as the servlet filter. To do this, you'll need to add two new beans to your app context:


<bean id="hibernateInterceptor"
class="org.springframework.orm.hibernate3.HibernateInterceptor">
<property name="sessionFactory">
<ref bean="sessionFactory" />
</property>
</bean>

<bean id="autoProxyCreator"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="interceptorNames">
<list>
<idref local="hibernateInterceptor" />
</list>
</property>
<property name="beanNames">
<list> <!--this proxies every bean with the specified pattern -->
<value>testbean</value>
</list>
</property>
</bean>


The first object, HibernateInterceptor, creates the Aspect itself. This does assume you are using Hibernate 3. The second is a Spring AutoProxyCreator. Essentially, this bean takes a list of bean names to proxy, which can be explicit names or patterns to match. For each, when instatiated it wraps the bean in a proxy object, to which it applies the HibernateInterceptor. This effectively binds the Hibernate Session for the lifetime of that proxied Bean, allowing it to operate as though it had been invoked through an OpenSessionInView handled Servlet.

No comments: