Spring 爲我們提供了一個叫做 OpenSessionInViewFilter 的過濾器,他是標準的 Servlet Filter 所以我們把它按照規範配置到 web.xml 中方可使用。使用中我們必須配合使用 Spring 的 HibernateDaoSupport 來進行開發,也就是說,我們的dao層的類都要繼承於 HibernateDaoSupport,從中由 Spring 來控制 Hibernate 的 Session 在請求來的時候開啓,走的時候關閉,保證了我們訪問數據對象時的穩定性。
1. 在 web.xml 中加入對應過濾器配置文件
<filter>
<filter-name>openSessionInViewFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>openSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2. 在我們訪問持久層數據是使用 Spring 爲我們的 HibernateDaoSupport 的支持,並使用其中的對應方法操作我們的持久層數據
public class XxxDAO extends HibernateDaoSupport {
public void save(Xxx transientInstance) {
try {
getHibernateTemplate().save(transientInstance);
} catch (RuntimeException re) {
throw re;
}
}
}
OpenSessionInViewFilter的主要功能是用來把一個Hibernate Session和一次完整的請求過程對應的線程相綁定。Open Session In View在request把session綁定到當前thread期間一直保持hibernate session在open狀態,使session在request的整個期間都可以使用,如在View層裏PO也可以lazy loading數據,如 ${ company.employees }。當View 層邏輯完成後,纔會通過Filter的doFilter方法或Interceptor的postHandle方法自動關閉session。
很多人在使用OpenSessionInView過程中提及一個錯誤:
org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.NEVER) – turn your Session into FlushMode.AUTO or remove ‘readOnly’ marker
from transaction definition
看看OpenSessionInViewFilter裏的幾個方法:
throws ServletException, IOException {
SessionFactory sessionFactory = lookupSessionFactory();
logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");
Session session = getSession(sessionFactory);
TransactionSynchronizationManager.bindResource(
sessionFactory, new SessionHolder(session));
try {
filterChain.doFilter(request, response);
}
finally {
TransactionSynchronizationManager.unbindResource(sessionFactory);
logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
closeSession(session, sessionFactory);
}
}
throws DataAccessResourceFailureException {
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
session.setFlushMode(FlushMode.NEVER);
return session;
}
throws CleanupFailureDataAccessException {
SessionFactoryUtils.closeSessionIfNecessary(session, sessionFactory);
}
可以看到OpenSessionInViewFilter在getSession的時候,會把獲取回來的session的flush mode 設爲FlushMode.NEVER。然後把該sessionFactory綁定到 TransactionSynchronizationManager,使request的整個過程都使用同一個session,在請求過後再解除該 sessionFactory的綁定,最後closeSessionIfNecessary根據該 session是否已和transaction綁定來決定是否關閉session。在這個過程中,若HibernateTemplate 發現自當前session有不是readOnly的transaction,就會獲取到FlushMode.AUTO Session,使方法擁有寫權限。也即是,如果有不是readOnly的transaction就可以由Flush.NEVER轉爲Flush.AUTO,擁有 insert,update,delete操作權限,如果沒有transaction,並且沒有另外人爲地設flush model的話,則doFilter的整個過程都是Flush.NEVER。所以受transaction保護的方法有寫權限,沒受保護的則沒有。
從上述代碼其實可以得到一些對我們的開發有幫助的結論:
1)如果使用了OpenSessionInView模式,那麼Spring會幫助你管理Session的開和關,從而你在你的DAO中通過HibernateDaoSupport拿到的getSession()方法,都是綁定到當前線程的線程安全的Session,即拿即用,最後會由Filter統一關閉。
2)由於拿到的Hibernate的Session被設置了session.setFlushMode(FlushMode.NEVER); 所以,除非你直接調用session.flush(),否則Hibernate session無論何時也不會flush任何的狀態變化到數據庫。因此,數據庫事務的配置非常重要。(我們知道,在調用org.hibernate.Transaction.commit()的時候會觸發session.flush())我曾經見過很多人在使用OpenSessionInView模式時,都因爲沒有正確配置事務,導致了底層會拋出有關FlushMode.NEVER的異常。
總結:
OpenSessionInView這個模式使用比較簡單,也成爲了大家在Web開發中經常使用的方法,不過它有時候會帶來一些意想不到的問題,這也是在開發中需要注意的。
1. 在Struts+Spring+Hibernate環境中,由於配置的問題導致的模式失效這個問題以前論壇曾經討論過,可以參考一下下面這個帖子:http://www.javaeye.com/topic/15057
2. OpenSessionInView的效率問題
這個問題也有人在論壇提出過,Robbin曾經做過具體的測試,可以具體參考一下下面這個帖子: http://www.javaeye.com/topic/17501
3. 由於使用了OpenSessionInView模式後造成了內存和數據庫連接問題
這個問題是我在生產環境中碰到的一個問題。由於使用了OpenSessionInView模式,Session的生命週期變得非常長。雖然解決了Lazy Load的問題,但是帶來的問題就是Hibernate的一級緩存,也就是Session級別的緩存的生命週期會變得非常長,那麼如果你在你的Service層做大批量的數據操作時,其實這些數據會在緩存中保留一份,這是非常耗費內存的。還有一個數據庫連接的問題,存在的原因在於由於數據庫的Connection是和Session綁在一起的,所以,Connection也會得不到及時的釋放。因而當系統出現業務非常繁忙,而計算量又非常大的時候,往往數據連接池的連接數會不夠。這個問題我至今非常頭痛,因爲有很多客戶對數據連接池的數量會有限制,不會給你無限制的增加下去。
4. 使用了OpenSessionInView模式以後取數據的事務問題
在使用了OpenSessionInView以後,其實事務的生命週期比Session的生命週期來得短,就以爲着,其實有相當一部分的查詢是不被納入到事務的範圍內的,此時是否會讀到髒數據?這個問題我至今不敢確認,有經驗的朋友請指教一下。
最後提一下OpenSessionInView模式的一些替代方案,可以使用OpenSessionInViewInterceptor來代替這個Filter,此時可以使用Spring的AOP配置,將這個Interceptor配置到你所需要的層次上去。另外就是隻能使用最古老的Hibernate.initialize()方法進行初始化了。
轉載自:
OpenSessionInViewFilter作用及配置:http://www.yybean.com/opensessioninviewfilter-role-and-configuration;
OpenSessionInView詳解:http://www.javaeye.com/topic/32001