Spring and Hibernate are increasingly used together in Java web applications. Spring is used as the MVC and dependency-injection framework and also provides support for data access, transaction management etc. Hibernate is usually used alongside Spring as the object-relational mapping framework. Spring's support for Hibernate is impressive through it's DAO templating mechanism. This truly simplifies matters as all the routine infrastructure plumbing code is taken care of by Spring.
Spring intercepts requests made to the webapp and through dependency-injection makes objects available in order to fulfil the request. This includes creating the Hibernate Session
object used by the DAOs. The Session
object is lightweight and created and destroyed for every request. Hibernate sessions are not threadsafe and should be used by only one thread at a time. In order to keep the session open throughout the lifetime of a request we tie it to the view. This is done either by using Spring's OpenSessionInViewInterceptor
or OpenSessionInViewFilter
as below:
<bean id="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor"> <property name="sessionFactory" ref="sessionFactory" /> <property name="flushModeName" value="Flush_AUTO" /> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> ... </bean> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> ... </bean>
Most requests are synchronous and completed before the response is returned to the client. However, in the case of long-running processes, a new thread is created for execution of the process. The response is returned back to the client and the process runs independently on the server. Hibernate sessions obtained through OpenSessionInViewInterceptor
are no longer available to the process as they are closed once the view has returned. This throws a LazyInitializationException
complaining that the owning session was closed.
The problem can be overcome by directly accessing the Hibernate SessionFactory
bean and binding the session to the long-running process thread. Hence the session is available and open within the thread itself. The UML diagram below describes our objects.
TestController
is the Controller
class that handles the request. Since it implements the ServletContextAware
interface, Spring automatically passes the ServletContext
object to it. This is used to obtain the ApplicationContext
object. Next we create an instance of our LongProcessInvoker
class, pass ApplicationContext
to it and finally start execution using an Executor
.
// TestController.java public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { try { ApplicationContext ac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); LongProcessInvoker lpi = new LongProcessInvoker(); lpi.setApplicationContext(ac); Executor ex = Executors.newSingleThreadExecutor(); ex.execute(lpi); } catch(...) { ... } }
In LongProcessInvoker
we obtain the Session
using SessionFactoryUtils
and bind it to the current thread. Next the long running process is invoked and once complete we execute some clean-up code by releasing and closing the Session
.
// LongProcessInvoker.java public void run() { try { // Bind session object. SessionFactory sessionFactory = (SessionFactory) applicationContext.getBean("sessionFactory"); Session session = SessionFactoryUtils.getSession(sessionFactory, true); TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session)); LongProcess lp = (LongProcess) applicationContext.getBean("longProcess"); lp.runLongProcess(); // Release session object. session.flush(); TransactionSynchronizationManager.unbindResource(sessionFactory); SessionFactoryUtils.closeSession(session); } catch(...) { ... } }
Using the above approach, we can now run long-running processes by obtaining the Hibernate session outside the view.
No comments:
Post a Comment