轉自:http://blog.csdn.net/guoquanyou/article/details/6697916
Spring為我們解決最讓人頭痛的難題之一,Hibernate的Session的關閉與開啟問題。
當hibernate+spring配合使用的時候,如果設置了lazy=true,那么在讀取數據的時候,當讀取了父數據后,hibernate會自動關閉session,這樣,當要使用子數據的時候,系統會拋出lazyinit的錯誤。
Hibernate 允許對關聯對象、屬性進行延遲加載,但是必須保證延遲加載的操作限於同一個 Hibernate Session范圍之內進行。如果 Service 層返回一個啟用了延遲加載功能的領域對象給 Web 層,當 Web層訪問到那些需要延遲加載的數據時,由於加載領域對象的 Hibernate Session已經關閉,這些導致延遲加載數據的訪問異常。而Spring為我們提供的OpenSessionInViewFilter過濾器為我們很好的解決了這個問題。
OpenSessionInViewFilter主要是保持Session狀態知道request將全部頁面發送到客戶端,這樣就可以解決延遲加載帶來的問題。
如果應用中使用了OpenSessionInViewFilter或者OpenSessionInViewInterceptor,所有打開的session會被保存在一個線程變量里。在線程退出前通過OpenSessionInViewFilter或者OpenSessionInViewInterceptor斷開這些session。為什么這么做?這主要是為了實現Hibernate的延遲加載功能。基於一個請求一個hibernate session的原則。
它有兩種配置方式OpenSessionInViewInterceptor和OpenSessionInViewFilter(具體參看SpringSide),功能相同,只是一個在web.xml配置,另一個在application.xml配置而已。
spring中對OpenSessionInViewFilter的描述如下:
它是一個Servlet2.3過濾器,用來把一個Hibernate Session和一次完整的請求過程對應的線程相綁定。目的是為了實現"Open Session in View"的模式。
例如: 它允許在事務提交之后延遲加載顯示所需要的對象。
這個過濾器和 HibernateInterceptor 有點類似:它是通過線程實現的。無論是沒有事務的應用,還是有業務層事務的應用(通過HibernateTransactionManager 或
JtaTransactionManager的方式實現)它都適用。在后一種情況下,事務會自動采用由這個filter綁定的Session來進行相關的操作以及根據實際情況完成提交操作。
如果使用struts2,此監聽器應該在struts2的監聽器前面
<!--OpenSessionInViewFilter 解決延遲加載問題-->
<filter>
<filter-name>OpenSessionInViewFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
</filter-class>
<!-- singleSession默認為true,若設為false則等於沒用OpenSessionInView -->
<init-param>
<param-name>singleSession</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>OpenSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
對於OpenSessionInView的配置中,singleSession應該設置為true,表示一個request只能打開一個 session,如果設置為false的話,session可以被打開多個,這時在update、delete的時候會出現打開多個session的異常。但是當設置為true的時候,系統的性能會因為用戶的網絡狀況受到影響,當request在生成頁面完成后,session才會被釋放,所以如果用戶的網絡狀況比較差,那么連接池中的鏈接會遲遲不被回收,造成內存增加,系統性能受損。但是如果不用這種方法的話,lazy模式有體現不出它的優點,用?不用?兩難啊。。。。。。
盡管Open Session In View看起來還不錯,其實副作用不少。看回上面OpenSessionInViewFilter的doFilterInternal方法代碼,這個方法實際上是被父類的doFilter調用的,因此,我們可以大約了解的OpenSessionInViewFilter調用流程:request(請求)->open session並開始transaction->controller->View(Jsp)->結束transaction並close session.
一切看起來很正確,尤其是在本地開發測試的時候沒出現問題,但試想下如果流程中的某一步被阻塞的話,那在這期間connection就一直被占用而不釋放。最有可能被阻塞的就是在寫Jsp這步,一方面可能是頁面內容大,response.write的時間長,另一方面可能是網速慢,服務器與用戶間傳輸時間久。當大量這樣的情況出現時,就有連接池連接不足,造成頁面假死現象。
Open Session In View是個雙刃劍,放在公網上內容多流量大的網站請慎用。
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
由於OpenSessionInViewFilter把session綁在當前線程上,導致session的生命周期比事務要長,這期間所有事務性操作都在復用這同一個session,由此產生了一些“怪問題”:
例如出現如下錯誤:
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 在把session綁在當前線程上的時候,會把session的flush mode 設為FlushMode.NEVER,因此,如果某個方法沒有事務或者有只讀事務,則不能對session做insert,update,delete操作,除非事先把session的flush mode手動設為auto
方案:
1、將singleSession設為false,這樣只要改 web.xml,缺點是Hibernate Session的Instance可能會大增,使用的JDBC Connection量也會大增,如果Connection Pool的maxPoolSize設得太小,很容易就出問題。<!-- singleSession默認為true,若設為false則等於沒用OpenSessionInView -->
2、在控制器中自行管理Session的FlushMode,麻煩的是每個有Modify的Method都要多幾行程式
session.setFlushMode(FlushMode.AUTO);
session.update(user);
session.flush();
3、Extend OpenSessionInViewFilter,Override protected Session getSession(SessionFactory sessionFactory),將FlushMode直接改為Auto。
4、讓方法受Spring的事務控制(service和配置文件對應)
