解決Hibernate的Session的關閉與開啓問題

當在使用Hibernate做開發的時候出現org.hibernate.LazyInitializationException: could not initialize proxy - no Session

錯誤提示

病症:這是一個lazy使用後的Exception,使用遲時加載,在session(hibernate裏的session)關閉後使用該對象的未加載變量,也就是說session已經關閉,沒有保存到內存中,然後你使用了,導致該異常。
解決方法:

方法1:

<may-to-one>or<one-to-may>的lazy屬性默認爲:lazy = "proxy"
解決:<many-to-one>   & <set> 中設置 lazy="false"

如果還不行,根據自己需求,經過我的仔細排查放在set一端不行,那就放在<many-to-one>那端

 

總結:原因是hibernate的session已經關閉,集合沒有被初始化。在hibernate中:hibernate3 默認支持延遲加載(lazy="proxy"我們可以把proxy看作是true),hibernate2 默認立即加載 (lazy="false")。

      在hibernate3中,所有的實體設置文件(user.hbm.xml)中的lazy屬性都被默認設成了true,就是當這個類沒有被調用時,延時加載,導致了以上情況的發生,在配置文件中將lzay屬性設爲false就可以了。

 

但是這種方法很是消耗資源


方法2:用openSessionInView

 

Spring爲我們解決最讓人頭痛的難題之一,Hibernate的Session的關閉與開啓問題。
Hibernate 允許對關聯對象、屬性進行延遲加載,但是必須保證延遲加載的操作限於同一個 Hibernate Session 範圍之內進行。如果 Service 層返回一個啓用了延遲加載功能的領域對象給 Web 層,當 Web 層訪問到那些需要延遲加載的數據時,由於加載領域對象的 Hibernate Session 已經關閉,這些導致延遲加載數據的訪問異常。而Spring爲我們提供的OpenSessionInViewFilter過濾器爲我們很好的解決了這個問題。OpenSessionInViewFilter的主要功能是使每個請求過程綁定一個 Hibernate Session,即使最初的事務已經完成了,也可以在 Web 層進行延遲加載的操作。OpenSessionInViewFilter 過濾器將 Hibernate Session 綁定到請求線程中,它將自動被 Spring 的事務管理器探測到。所以 OpenSessionInViewFilter 適用於 Service 層使用HibernateTransactionManager 或 JtaTransactionManager 進行事務管理的環境,也可以用於非事務只讀的數據操作中。

所謂的OpenSessionInView模式,把session的週期交給servlet filter來管理,每當有request進來,就打開一個session,response結束之後再關閉它,這樣可以讓session存在於整個請求週期中

 

假設在你的應用中Hibernate是通過spring 來管理它的session.如果在你的應用中沒有使用OpenSessionInViewFilter或者 OpenSessionInViewInterceptor。session會在transaction結束後關閉。
如果你採用了spring的聲明式事務模式,它會對你的被代理對象的每一個方法進行事務包裝(AOP的方式)。

<bean id="txProxyTemplate" abstract="true"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager" ref="transactionManager"/>
        <property name="transactionAttributes">
            <props>
                <prop key="save*">PROPAGATION_REQUIRED</prop>
                <prop key="remove*">PROPAGATION_REQUIRED</prop>
                <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
            </props>
        </property>
    </bean>

    <bean id="manager" parent="txProxyTemplate">
        <property name="target">
            <bean class="org.appfuse.service.impl.BaseManager">
                <property name="dao" ref="dao"/>
            </bean>
        </property>
    </bean>
目標類org.appfuse.service.impl.BaseManager 的 save *方法的事務類型PROPAGATION_REQUIRED ,remove* 方法的事務類型PROPAGATION_REQUIRED,其他的方法的事務類型是PROPAGATION_REQUIRED,readOnly。
所以給你的感覺是調用這個名爲“manager”的bean的方法之後session就關掉了。
如果應用中使用了OpenSessionInViewFilter或者OpenSessionInViewInterceptor,所有打開的session會被保存在一個線程變量裏。在線程退出前通過
OpenSessionInViewFilter或者OpenSessionInViewInterceptor斷開這些session。 爲什麼這麼做?這主要是爲了實現Hibernate的延遲加載功能。基於一個請求一個hibernate session的原則。

spring中對OpenSessionInViewFilter的描述如下:
     它是一個Servlet2.3過濾器,用來把一個Hibernate Session和一次完整的請求過程對應的線程相綁定。目的是爲了實現"Open Session in View"的模式。
例如: 它允許在事務提交之後延遲加載顯示所需要的對象。

這個過濾器和 HibernateInterceptor 有點類似:它是通過線程實現的。無論是沒有事務的應用,還是有業務層事務的應用(通過HibernateTransactionManager 或JtaTransactionManager的方式實現)它都適用。在後一種情況下,事務會自動採用由這個filter綁定的Session來進行相關的操作以及根據實際情況完成提交操作。

    警告: 如果在你的應用中,一次請求的過程中使用了單一的一個HIbernate Session,在這種情況下,採用這個filter會產生一些以前沒遇到的問題。特別需要注意的是通過Hibernate Session重新組織持久化對象之間關係的相關操作需要在請求的最開始進行。以免與已經加載的相同對象發生衝突。

    或者,我們可以通過指定"singleSession"="false"的方式把這個過濾器調到延期關閉模式。這樣在一次請求的過程中不會使用一個單一的Session.每一次數據訪問或事務相關
操作都使用屬於它自己的session(有點像不使用Open Session in View).這些session都被註冊成延遲關閉模式,即使是在這一次的請求中它相關操作已經完成。

     "一次請求一個session" 對於一級緩存而言很有效,但是這樣可以帶來副作用。例如在saveOrUpdate的時候或事物回滾之後,雖然它和“no Open Session in View”同樣安全。
但是它卻允許延遲加載。

    它會在spring的web應用的上下文根中查找Session工廠。它也支持通過在web.xml中定義的“SessionFactoryBeanName”的init-param元素 指定的Session工廠對應的bean的名字來查找session工廠。默認的bean的名字是"sessionFactory".他通過每一次請求查找一次SessionFactory的方式來避免由初始化順序引起的問題(當使用ContextLoaderServlet來集成spring的時候 ,spring 的應用上下文是在這個filter 之後才被初始化的)。
默認的情況下,這個filter 不會同步Hibernate Session.這是因爲它認爲這項工作是通過業務層的事務來完成的。而且HibernateAccessors 的FlushMode爲FLUSH_EAGER.如果你
想讓這個filter在請求完成以後同步session.你需要覆蓋它的closeSession方法,在這個方法中在調用父類的關閉session操作之前同步session.此外你需要覆蓋它的getSession()
方法。返回一個session它的FlushMode 不是默認的FlushMode.NEVER。需要注意的是getSession()和closeSession()方法只有在single session的模式中才被調用。

在myfaces的wiki裏提供了OpenSessionInViewFilter的一個子類如下:
public class OpenSessionInViewFilter extends org.springframework.orm.hibernate3.support.OpenSessionInViewFilter {
      
        /**
         * we do a different flushmode than in the codebase
         * here
         */
        protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
                Session session = SessionFactoryUtils.getSession(sessionFactory, true);
                session.setFlushMode(FlushMode.COMMIT);
                return session;
        }
        /**
         * we do an explicit flush here just in case
         * we do not have an automated flush
         */
        protected void closeSession(Session session, SessionFactory factory) {
                session.flush();
                super.closeSession(session, factory);
        }
}

<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>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章