Saturday, July 7, 2007

Open Session In View

I was a part of a team that have developed several applications using Struts, Spring and Hibernate together, and one of the problems that have faced us while using Hibernate was the rendering of the view. The problem is that when you retrieve an object 'a' of persistence class 'A' that has an instance 'b' of persistence class 'B', and this relation is lazily loaded, the value of 'b' will be 'null'. This will cause a "LazyInitializationException" while rendering the view (if you need the value of 'b' in the view of course).

A quick and easy solution to that is to set the "lazy" attribute to "false" so that 'b' would be initialized while fetching 'a', but this is not always a good idea. In case of many-to-many relationships, using non-lazy relations might result in loading the entire database into the memory using a great number of "select" statements, which will result to very poor performance, and to massive memory consumption.

Another solution is to open another unit of work in the view, which is really bad for several reasons. First of all, as a design concept, the layers of your application should be loosely coupled, and by doing the previous practice you have coupled the presentation layer with you DB layer, which is bad. Another thing is that this destroys the separation of concerns concept.

The solution to this problem can be done by keeping the hibernate session alive until the view is rendered, and this is what Hibernate introduced as the Open Session In View Design Pattern. Since the Hibernate session will be opened, trying to retrieve 'b' in the view will cause Hibernate to go and fetch it from the DB. In a web application, this can be done through a filter/interceptor.

Spring framework comes with both a filter and an interceptor, so that you don't have to write your own. The problem that might face you, if you're using spring's HibernateTemplate,without doing your own session and transaction management, is that you will not be able to save, edit or delete anything, since both the filter and the interceptor provided by spring set the flush mode of the session to "NONE".

A solution to that, which I've learned from a friend of mine recently is to extend the filter provided by spring, override the getSession method to set a different flush mode, and override the closeSession method to flush the session before closing it. The sample code is shown below:

public class HibernateFilter extends OpenSessionInViewFilter {

 @Override
 protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
  Session session = SessionFactoryUtils.getSession(sessionFactory, true);
  //set the FlushMode to auto in order to save objects.
  session.setFlushMode(FlushMode.AUTO);
  return session;
 }
 
 
 @Override
 protected void closeSession(Session session, SessionFactory sessionFactory) {
  try{
   if (session != null && session.isOpen() && session.isConnected()) {
    try {
     session.flush();
    } catch (HibernateException e) {
     throw new CleanupFailureDataAccessException("Failed to flush session before close: " + e.getMessage(), e);
    } catch(Exception e){}
    }
  } finally{
   super.closeSession(session, sessionFactory);
  }
 }
}

By using this filter, you will be able to render the view easily, without having to set the "lazy" attribute to "false", or to open a hibernate session in the view, but you have to take care not to change any values of the persistence object in the view, because those changes will be saved to the DB at the end of the request. This is the main reason why the flush mode is set to "NONE" in the original filter and interceptor.
By Alaa Nassef

5 comments:

Barry said...

Hi,

Just wondering have you encountered problems with this approach where users use multiple quick clicks on links.

I get a "Illegal attempt to associate a collection with two open sessions" error.

I believe that this is where one session has not been closed yet by the filter and another is opened.

Any ideas?

Alex said...

Is there a way to use this solution with Spring-ws?

Anonymous said...

Thanks a lot, I felt identified with your situation in several ways.
Jose Luis.

Bahadır Konu said...

You say: to open another unit of work in the view is wrong in the sense of loosely coupled layers (by coupling the presentation layer with you DB layer, which is bad.)
But we are not changing the coupling there. We are just opening a new persistence context (unit of work) to render the view. This is not relevant to the concept of "seperation of concerns" or loosely coupled layers.

The only problem of that approach is, you are creating a new unit of work for rendering which should normally be part of the previous unit of work.

Again, I see nothing related to dependencies between layers. And "seperation of concerns" is not also relevant I think.

Anonymous said...

The later versions of Spring seem to have fixed this problem, also note that Spring allows you to explicitly set the flush mode:

setFlushMode
public void setFlushMode(int flushMode)Set the flush behavior to one of the constants in this class. Default is FLUSH_AUTO.

See Also:
setFlushModeName(java.lang.String), FLUSH_AUTO
From the latest Spring documentation.

-Ak