Spring 為我們提供了一個叫做 OpenSessionInViewFilter 的過濾器,他是标準的 Servlet Filter 是以我們把它按照規範配置到 web.xml 中方可使用。使用中我們必須配合使用 Spring 的 HibernateDaoSupport 來進行開發,也就是說,我們的dao層的類都要繼承于 HibernateDaoSupport,從中由 Spring 來控制 Hibernate 的 Session 在請求來的時候開啟,走的時候關閉,保證了我們通路資料對象時的穩定性。
1. 在 web.xml 中加入對應過濾器配置檔案
<!-- Spring的OpenSessionInView實作 -->
<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>
<!--
指定org.springframework.orm.hibernate3.LocalSessionFactoryBean在spring配置檔案中的名稱,預設值為sessionFactory。 如果LocalSessionFactoryBean在spring中的名稱不是sessionFactory,該參數一定要指定,否則會出現找不到sessionFactory的例外。是以預設可以不寫
-->
<init-param>
<param-name>sessionFactoryBean</param-name>
<param-value>sessionFactory</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>openSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2. 在我們通路持久層資料是使用 Spring 為我們的 HibernateDaoSupport 的支援,并使用其中的對應方法操作我們的持久層資料
import org.springframework.orm.hibernate3.support.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裡的幾個方法:
protected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain)
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);
}
}
protected Session getSession(SessionFactory sessionFactory)
throws DataAccessResourceFailureException {
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
session.setFlushMode(FlushMode.NEVER);
return session;
}
protected void closeSession(Session session, SessionFactory sessionFactory)
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,使方法擁有寫權限。
public static void closeSessionIfNecessary(Session session, SessionFactory sessionFactory)
throws CleanupFailureDataAccessException {
if (session == null ||
TransactionSynchronizationManager.hasResource(sessionFactory)) {
return;
}
logger.debug("Closing Hibernate session");
try {
session.close();
}
catch (JDBCException ex) {
// SQLException underneath
throw new CleanupFailureDataAccessException("Could not close Hibernate session", ex.getSQLException());
}
catch (HibernateException ex) {
throw new CleanupFailureDataAccessException("Could not close Hibernate session", ex);
}
}
也即是,如果有不是readOnly的transaction就可以由Flush.NEVER轉為Flush.AUTO,擁有 insert,update,delete操作權限,如果沒有transaction,并且沒有另外人為地設flush model的話,則doFilter的整個過程都是Flush.NEVER。是以受transaction保護的方法有寫權限,沒受保護的則沒有。
解決:
采用spring的事務聲明,使方法受transaction控制
<bean id="baseTransaction"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
abstract="true">
<property name="transactionManager" ref="transactionManager"/>
<property name="proxyTargetClass" value="true"/>
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="add*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="remove*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="userService" parent="baseTransaction">
<property name="target">
<bean class="com.phopesoft.security.service.impl.UserServiceImpl"/>
</property>
</bean>
對于上例,則以save,add,update,remove開頭的方法擁有可寫的事務,如果目前有某個方法,如命名為importExcel(),則因沒有transaction而沒有寫權限,這時若方法内有insert,update,delete操作的話,則需要手動設定flush model為Flush.AUTO,如
1 session.setFlushMode(FlushMode.AUTO);
2 session.save(user);
3 session.flush();
或可以
在web.xml中過濾器openSession修改初始參數:
<!-- 在項目中使用Spring+Hibernate的時候,會開啟OpenSessionInViewFilter來阻止延遲加載的錯誤,但是在我們開啟OpenSessionInViewFilter這個過濾器的時候FlushMode就已經被預設設定為了MANUAL,如果FlushMode是MANUAL或NEVEL,在操作過程中 hibernate會将事務設定為readonly,是以在增加、删除或修改操作過程中-->
<init-param>
<param-name>flushMode</param-name>
<param-value>AUTO</param-value>
</init-param>
從上述代碼其實可以得到一些對我們的開發有幫助的結論:
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模式的一些替代方案,
1
可以使用OpenSessionInViewInterceptor來代替這個Filter,此時可以使用Spring的AOP配置,将這個Interceptor配置到你所需要的層次上去。
在application.xml配置
<!-- Spring的OpenSessionInView實作 -->
<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
使用最古老的Hibernate.initialize()方法進行初始化了。
感謝:
http://www.cnblogs.com/children/archive/2010/05/01/1725419.html
OpenSessionInViewFilter作用及配置:http://www.yybean.com/opensessioninviewfilter-role-and-configuration;
OpenSessionInView詳解:http://www.javaeye.com/topic/32001
http://schnauzer.iteye.com/blog/160024