Fun with Spring’s OpenSessionInViewFilter and Wicket

I have been spending some time exploring Spring’s OpenSessionInViewFilter to use as part of our persistence framework for some of our Wicket web applications (particularly for small projects where cranking something out quick is critical).

Here’s some of that exploration.

Open Session In View

Let’s start with how to use the OpenSessionInViewFilter as recommended by Spring:

  • First set up the filter in your web.xml
    <filter>
    		<filter-name>openSessionInViewFilter</filter-name>
    		<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
    		<init-param>
    			<param-name>sessionFactoryBeanName</param-name>
    			<param-value>sessionFactory</param-value>
    		</init-param>
    	</filter>
    • This opens a HibernateSession (in my case…other persistent frameworks can be used) and ties it to the ThreadLocal for the duration of the request.
    • Spring’s Transaction Managers are smart enough to detect and use the session on the Thread when begin() is called.
    • In addition this session is available for your view jsp’s, wicket pages, etc to use hence the name.
  • Secondly, open and close transactions as you would normally. (In my experience this is usually using Declarative Transactions around services…but it doesn’t matter as long as you use the Spring Transaction Manager)

Here’s essentially how it works:

Problems:

There are a few issues with the OpenSessionInViewFilter approach and they’re so well documented in the javadoc I will repeat what they have:

/**
 * Servlet 2.3 Filter that binds a Hibernate Session to the thread for the entire
 * processing of the request. Intended for the "Open Session in View" pattern,
 * i.e. to allow for lazy loading in web views despite the original transactions
 * already being completed.
 *
 * <p>This filter makes Hibernate Sessions available via the current thread, which
 * will be autodetected by transaction managers. It is suitable for service layer
 * transactions via {@link org.springframework.orm.hibernate3.HibernateTransactionManager}
 * or {@link org.springframework.transaction.jta.JtaTransactionManager} as well
 * as for non-transactional execution (if configured appropriately).
 *
 * <p><b>NOTE</b>: This filter will by default <i>not</i> flush the Hibernate Session,
 * with the flush mode set to <code>FlushMode.NEVER</code>. It assumes to be used
 * in combination with service layer transactions that care for the flushing: The
 * active transaction manager will temporarily change the flush mode to
 * <code>FlushMode.AUTO</code> during a read-write transaction, with the flush
 * mode reset to <code>FlushMode.NEVER</code> at the end of each transaction.
 * If you intend to use this filter without transactions, consider changing
 * the default flush mode (through the "flushMode" property).
 *
 * <p><b>WARNING:</b> Applying this filter to existing logic can cause issues that
 * have not appeared before, through the use of a single Hibernate Session for the
 * processing of an entire request. In particular, the reassociation of persistent
 * objects with a Hibernate Session has to occur at the very beginning of request
 * processing, to avoid clashes with already loaded instances of the same objects.
 *
 * <p>Alternatively, turn this filter into deferred close mode, by specifying
 * "singleSession"="false": It will not use a single session per request then,
 * but rather let each data access operation or transaction use its own session
 * (like without Open Session in View). Each of those sessions will be registered
 * for deferred close, though, actually processed at request completion.
 *
 * <p>A single session per request allows for most efficient first-level caching,
 * but can cause side effects, for example on <code>saveOrUpdate</code> or when
 * continuing after a rolled-back transaction. The deferred close strategy is as safe
 * as no Open Session in View in that respect, while still allowing for lazy loading
 * in views (but not providing a first-level cache for the entire request).
 *
 * <p>Looks up the SessionFactory in Spring's root web application context.
 * Supports a "sessionFactoryBeanName" filter init-param in <code>web.xml</code>;
 * the default bean name is "sessionFactory". Looks up the SessionFactory on each
 * request, to avoid initialization order issues (when using ContextLoaderServlet,
 * the root application context will get initialized <i>after</i> this filter).
 *
 * @author Juergen Hoeller
 * @since 1.2
 * @see #setSingleSession
 * @see #setFlushMode
 * @see #lookupSessionFactory
 * @see OpenSessionInViewInterceptor
 * @see org.springframework.orm.hibernate3.HibernateInterceptor
 * @see org.springframework.orm.hibernate3.HibernateTransactionManager
 * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession
 * @see org.springframework.transaction.support.TransactionSynchronizationManager
 */

Using OpenSessionInViewFilter’s deferred close mode (singleSession”=”false”)

As mentioned above in the documentation you might have problems with the fact that there is only one session used for the entire request. In this case you can set the singleSession property to false and the Filter will perform more like this diagram describes:

One commit for the entire request?

The javadoc for the closeSession() method in OpenSessionInViewFilter suggest that you can subclass the filter and override the getSession() and closeSession() to flush the session and essentially achieve something close to one commit for the entire request.

  • Here’s the javadoc that suggests this:
    /**
    	 * Close the given Session.
    	 * Note that this just applies in single session mode!
    	 * <p>Can be overridden in subclasses, e.g. for flushing the Session before
    	 * closing it. See class-level javadoc for a discussion of flush handling.
    	 * Note that you should also override getSession accordingly, to set
    	 * the flush mode to something else than NEVER.
    	 * @param session the Session used for filtering
    	 * @param sessionFactory the SessionFactory that this filter uses
    	 */
    	protected void closeSession(Session session, SessionFactory sessionFactory) {
    		SessionFactoryUtils.closeSession(session);
    	}
  • Here’s the code as we implemented it:
    public class OpenSessionInViewFilter extends org.springframework.orm.hibernate3.support.OpenSessionInViewFilter {
    
    	@Override
    	protected Session getSession(SessionFactory sessionFactory)	throws DataAccessResourceFailureException {
    		Session session = SessionFactoryUtils.getSession(sessionFactory, true);
    		session.setFlushMode(FlushMode.AUTO);
    		return session;
    	}
    
    	@Override
    	protected void closeSession(Session session, SessionFactory sessionFactory) {
    		try {
    			if (isSessionActive(session)) {
    				session.flush();
    			}
    		} finally {
    			super.closeSession(session, sessionFactory);
    		}
    	}
    
    	boolean isSessionActive(Session session) {
    		return session != null && session.isOpen() && session.isConnected();
    	}
    }
    • Along with the web.xml updated:
      <filter>
      		<filter-name>openSessionInViewFilter</filter-name>
      		<filter-class>ca.intelliware.kinetic.hibernate.OpenSessionInViewFilter</filter-class>
      		<init-param>
      			<param-name>sessionFactoryBeanName</param-name>
      			<param-value>sessionFactory</param-value>
      		</init-param>
      	</filter>
  • Here’s a diagram that essentially explains how it would work:

Could this be a good option for smaller projects without a lot of budget?

  • I’m not sure if I could answer that but there is some danger in doing this.
  • For instance Spring’s co-creator Juergen Hoeller says this about it (http://forum.springsource.org/archive/index.php/t-12414.html)
    OpenSessionInViewFilter is supposed to be combined with Spring middle tier transactions. Only for a non-read-only transaction, the Session is turned to FlushMode.AUTO; it's reverted back to FlushMode.NEVER after each transaction. There is a corresponding note at the end of the javadoc.
    
    If we wouldn't do this, OpenSessionInViewFilter's single session would not get flushed at well-controlled points in request processing. We want to avoid flushing after view rendering, as this could lead to side effects if objects get modified in the view for rendering purposes etc.
    
    With transactions, you'll also get proper rollback in case of failure: database operations will be rolled back, and the Session will be cleared. It's highly recommended to perform all data access in transactions, in particular write operations.
    
    If you absolutely want to use OpenSessionInViewFilter without middle tier transactions, follow the guideline at the end of the javadoc: override closeSession and call Session.flush() there, maybe in combination with overriding getSession and setting the Session to FlushMode.AUTO.

PROBLEM in wicket:

  • One issue I had to overcome when tinkering with this using wicket is when there is an exception thrown in the code. Wicket catches the exception and displays the error either as a stack trace if debug is on, or as a user friendly page that you can define. This is a problem because at the filter is at the web container layer and because the exception is caught below it can’t tell that it shouldn’t commit. The code as it is commits anyways. NOT GOOD
  • I could think of three things to fix this:
    • 1.Implement the filter yourself down at the wicket level (By essentially overriding the Wicket class WebRequestCycle’s onRequestBegin() and onRequestEnd() to achieve the same as the Spring’s OpenSessionInViewFilter). LOTS OF WORK.
    • 2.Throw the fact that an exception was thrown on a ThreadLocal and check this in the filter (don’t flush() if that ThreadLocal is set?)
    • 3. Override the onRequestEnd() in Wicket’s WebRequestCycle
      • This is the option I implemented.
      • Here’s the subclass:
        public class MyWebRequestCycle extends WebRequestCycle {
        	private boolean hasRuntimeException;
        
        	public MyWebRequestCycle(WebApplication application, WebRequest request, Response response) {
        		super(application, request, response);
        	}
        
        	@Override
        	protected void onEndRequest() {
        		if (hasRuntimeException) {
        			HibernateTransactionManager transactionManager = AuthenticatedApplication.get().getTransactionManager();
        			Session currentSession = SessionFactoryUtils.getSession(transactionManager.getSessionFactory(), false);
        			if (isSessionActive(currentSession)) {
        				currentSession.close();
        			}
        		}
        		super.onEndRequest();
        	}
        	@Override
        	public Page onRuntimeException(Page page, RuntimeException e) {
        		hasRuntimeException = true;
        		return super.onRuntimeException(page, e);
        	}
        
        	boolean isSessionActive(Session session) {
        		return session != null && session.isOpen();
        	}
      • To hook the new RequestCycle into the Wicket Application just override the newRequestCycle() method in your Wicket WebApplication class:
        public class MyAuthenticatedApplication extends AuthenticatedWebApplication {
        
        	@Override
        	public RequestCycle newRequestCycle(Request request, Response response) {
        		return new MyWebRequestCycle(this, (WebRequest) request, (WebResponse) response);
        	}

It's only fair to share...
Share on FacebookGoogle+Tweet about this on TwitterShare on LinkedIn

Leave a Reply