天天看點

OpenSessionInViewFilter 的配置及替代方案

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

繼續閱讀