OpenSessionInViewFilter實踐

這次做一個項目,用上了OpenSessionInViewFilter這個過濾器,以前就知道使用它可以把session一直綁定在整個request請求之上,以前也有試過,但是之前的項目沒有使用到事務,所以一直會有問題,後來查了網上看到說這個使用過濾器一定要配置事務所以就放棄了。
這次做的項目,框架比較嚴謹,採用典型的三層結構,也在service層配置了spring註解式事務,接着就把OpenSessionInViewFilter用上去了,效果挺好的,顯然比關閉延遲加載的效率會高很多。配置很簡單:

<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>hibernateFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>

但是做着做着就發現了一個問題,我們的框架模仿了struts2的paramsPrepareParams攔截器棧的執行方式,會在提交保存表單的請求時,從請求中取出ID值,然後根據這個ID查找數據庫中的對象,接着再用請求中的參數覆蓋這個對象屬性,再把對象傳到Service層中進行處理。當然在保存這個對象之前還必須進行一些邏輯校驗,在通過後才保存到數據庫。問題就出來,我們發現不管是否校驗通過,這個對象都被修改了。代碼大概是這樣的:

Foo foo = ...;
if(check(foo)){
dao.saveOrUpdate(foo);
return 0;
}else{
log.error(...);
return 1;
}

排查的時候我們一想就猜到問題可能出在OpenSessionInViewFilter之上,由於從最早的去數據庫中查詢這個對象開始,session就一直沒有關閉,即使到了service層,這個對象仍然是出於託管狀態,那不管是否顯示調用了saveOrUpdate方法,由於hibernate會進行髒檢查,所以修改都會發生。後面我們想了幾個方案:
[list]
[*]最土的一個,取出這個對象後,把這個對象給clone一份,然後操作這個clone的對象,還要注意修改的時候不能使用update方法而要改用merge方法,否則會報錯,異常信息爲"org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session",這個方法倒是讓我學到了一種不用clone方法clone對象的方式~~
[*]關閉hibernate的髒檢查,可以通過session.setReadOnly(data, true),但是要確實修改的時候必須調用session.setReadOnly(data, false),還要在這行調用之後做一次修改對象的方法(調用對象的某個屬性的set方法)。
[*]在最早的數據庫取出需要的持久化對象後,就手工脫管,這可以通過session.evit(data)。
[*]使用OpenSessionInViewFilter之前做個判斷,讓修改和保存的方法都不經過這個過濾器,通過附加參數來實現。
[/list]
最後我們選擇了最後一種,因爲對代碼的改動量最少。過了一陣子,輕鬆下來後又想到這個問題,想看看這個過濾器到底是怎麼實現的,於是乎,打開docjar,查了OpenSessionInViewFilter的源碼,首先看到註釋說這個過濾器可以用在沒有事務的環境中:It is suitable for service layer transactions via org.springframework.orm.hibernate3.HibernateTransactionManager or org.springframework.transaction.jta.JtaTransactionManager as well as for non-transactional execution (if configured appropriately)。這裏要說明一點,網上有的文章說OpenSessionInViewFilter不能用於非事務環境中,還貼出了部分源碼,但是我比較了兩份源碼,發現不同,可能是版本的問題。
接着看下去,發現我配置的方式默認爲single session mode,這時Hibernate FlushMode默認爲NEVER(MANUAL),但是在事務之中會把它暫時性的修改爲AUTO,我就在想會不會是由於這個原因造成的,查了資料,無關。倒是後來讓我搞明白了該如何在非事務的環境中使用OpenSessionInViewFilter,應該這樣配置:

<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
</filter-class>
<init-param>
<param-name>flushMode</param-name>
<param-value>AUTO</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>hibernateFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>

有一天突然靈感閃現,想起來Spring的事務回滾原則,默認在拋出運行時異常的時候,事務就會被回滾。馬上試了一下,創建個自定義異常類,在邏輯判斷錯誤的地方,拋出這個異常,現在代碼看起來就像這樣:

Foo foo = ...;
if(check(foo)){
dao.saveOrUpdate(foo);
}else{
throw new CustomeException(...);
}

果然可以了!這同時讓我想到以前有個朋友問我,他看到書上網上很多源碼在檢查錯誤的時候都拋出一個異常,搞不懂這樣比返回一個錯誤代碼好在什麼地方?我那時也說不出個所以然來,只覺得如果有方法本身有返回值的時候就不好辦了。現在我又找到一個原因了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章