Sunday, May 20, 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.

7 comments:

Alaa Nassef said...

I would like to deeply thank Ahmed Fadel for his help to solve my problem with the filter. Without him, I wasn't going to be able to get my app running correctly, or to write this article.

Amir Nasr said...

I also faced this problem in my old & present company, but i fixed it by creating my own filter and using ThreadLocal Object to put & retrieve the session object.

Mohamed Nabil said...

Alaa

Nice article but I have a question what about implementing the Lazy loading as a GHOST

by this the object that is to be loaded in a partial state. It may only contain the object's identifier, but it loads its own data the first time one of its properties is accessed.

Alaa Nassef said...

Amir, seems like a good approach, to have a Session object per thread, and not per request. Nice idea.

Alaa Nassef said...

Mohamed, implementing lazy loaded objects as ghosts is a great idea, and will solve the problem, but the problem here is this. Hibernate does not mention ghost objects anywhere in its reference or in it's website, and the "Open Session In View" design pattern is the closest thing to ghost objects.

What happens here is that objects are lazy loaded, and they are retrieved only when they are accessed by the system (exactly like what happens with ghost objects). The catch is that the Hibernate session must be open, or you will have a couple of handsome exceptions thrown in your face. I think that the "Open Session In View" design pattern is hibernate's implementation of the ghost concept.

Alvin said...

I am failing to understand why you write your own OSIV interceptor/filter and set the flush mode explicitly to auto. From what I gather is that you are wanting to persist things directly from your view layer - which is in itself a bad idea.

You are really breaking the idea of separation of concerns and mixing view layer logic with persistence logic. This is precisely the reason the OSIV interceptor/filter provided by Spring and Hibernate is FlushMode.None - so people do not start mixing up their logic.

The proper way to go about it is to have a business-logic and dao layer which handle persisting your objects.

Alaa Nassef said...

Hello Alvin,

I totally agree that you shouldn't mix your your layer logic, and I agree that the view accessing the database is one of the worst thing you could do.

Why did I create my own filter? As I recall that when I wrote this article (about a year ago) I was using Spring's HibernateTemplate, where I didn't explicitly do the session handling and the transaction management. When I used the filter that came with Spring, I couldn't do any database transactions except for reading, since the filter opens the session with flushing disabled, and HibernateTemplate doesn't open another session, since there is one already open.

As you can see, at the end of my post, I gave this warning "...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...".

I'm sure that there must be a more elegant way of solving the problem I was facing, but this is how I did it more than a year ago.