Thursday 20 March 2008

What is OpenSessionInViewFilter and how to use it?

Recently, in one of our project we were using hibernate. We were having hibernate integrated with Velocity, Webwork and Spring. We wanted to show a webpage where the webwork action will make a hibernate query and get the fields from one table. example : We were having a database schema to store countries and cities. The cities were mapped to country. Now our webpage should show all the countries and all the cities within that country. Our hibernate objects were created in such a way that we could retrieve cities from country(One-To-Many). We have also made LAZY loading for cities from country. i.e a country should return cities only when asked. or in other words, the hibernate wont query the database for cities in particular country unless asked.
Since we were using LAZY loading, we faced a problem.
When user sends a request to view page, our webwork action method is called. In this action method we load all the countries, but since cities are lazily loaded, no cities are loaded in action method. In the same action method we open the sesison and query for country and then close the session. Now list of cities is returned to the velocity Parser, but the response is not yet rendered. The velocity page now tries to fetch the cities in each country. Since session is already closed the getter method for cities gives LAZY Initialization Exception. This is because our action method made a query for country and closed the session. When velocity asks for cities and hibernate tries to query for cities, it finds the session to be closed. Hence LAZY initialization error.

This problem can be solved if we open a session on client request and close it before sending a response back to client. This can be achieved with Spring's OpenSessionInViewFilter.

To use OpenSessionInViewFilter, edit your web.xml file and put this filter before webwork filter.

<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>org.springframework.orm.hibernate.support.OpenSess ionInViewFilter</filter-class>
</filter>

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


Issues: Now when you are using OpenSessionInViewFilter, by default the session's flush mode is set to NEVER. So when you try to save something in your action using hibenate and commit it, it wont be reflected in your database. To solve this you need to flush the session in your action class or extend OpenSessionInViewFilter and override closeSession(Session session, SessionFactory sessionFactory).
public MyOSIVFilter extends OpenSessionInViewFilter{
public void closeSession(Session session, SessionFactory sessionFactory){
session.flush();
super.closeSession(session,sessionFactory);
}
}


change your web.xml to
<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>package.MYOSIVFilter</filter-class>
</filter>

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


Now it is also possible that you are maintaining a single transaction for per request. In your action you edit the attributes of a object and update it using session.update(object). But it is not yet commited as some other processing is remaining. At the same time, some other request is invoking a action which tries to retrieve the object which you were updating. Since the object is not yet commited the other request will get the old object. To solve this you need to begin a transaction before you load object and commit the transaction after you update the object. So that as soon as the object is saved/updated it is commited. With this there can be many transaction in single user request but only one session.

Now there is another way to do the same thing using Spring TransactionProxyFactoryBean.
This is actually a proxy factory which takes the target object which needs to be proxied, the method to be proxied and the transactionManager.

below is the code snippet from applicationContext.xml

<bean id="repository" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="hibernateTransactionManager"/>
<property name="transactionAttributeSource" ref="transactionAttributeSource"/>
<property name="target" ref="daoImpl"/>
<property name="proxyInterfaces">
<list>
<value>package.DAO</value>
</list>
</property>
</bean>

<bean id="transactionAttributeSource"
class="org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource">
<property name="properties">
<value>
get*=PROPAGATION_REQUIRED,readOnly
find*=PROPAGATION_REQUIRED,readOnly
create*=PROPAGATION_REQUIRED,readOnly
delete*=PROPAGATION_REQUIRED
</value>
</property>
</bean>

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" >
.........................
.............
.....
......
</bean>

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

<bean id="daoImpl" class="package.daoImpl">
...................................
..................................
</bean>


Above code says the TransactionProxyFactoryBean that all the methods in daoImpl starting with get, find, create, delete should be proxied and for all this methods the transaction should be begin before method starts and should get commited after method finishes.

3 comments:

Thảo Nguyên Xanh said...

Simple but very useful post. Thank you.

Unknown said...

thanks, i'm from VietNam :)

sWaTi said...

Nice post....but can you please explain something more about proxy and its significance