open session and Hibernate事務處理機制

在沒有使用Spring提供的Open Session In View情況下,因需要在service(or Dao)層裏把session關閉,所以lazy loading true的話,要在應用層內把關係集合都初始化,如 company.getEmployees(),否則Hibernatesession already closed Exception;    Open Session In View提供了一種簡便的方法,較好地解決了lazy loading問題.

    它有兩種配置方式OpenSessionInViewInterceptorOpenSessionInViewFilter(具體參看SpringSide),功能相同,只是一個在web.xml配置,另一個在application.xml配置而已。

    Open Session In Viewrequestsession綁定到當前thread期間一直保持hibernate sessionopen狀態,使sessionrequest的整個期間都可以使用,如在View層裏PO也可以lazy loading數據,如 ${ company.employees }。當View 層邏輯完成後,纔會通過FilterdoFilter方法或InterceptorpostHandle方法自動關閉session


OpenSessionInViewInterceptor配置
  1. <beans>
  2. <bean name="openSessionInViewInterceptor"
  3. class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
  4. <property name="sessionFactory">
  5. <ref bean="sessionFactory"/>
  6. </property>
  7. </bean>
  8. <bean id="urlMapping"
  9. class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
  10. <property name="interceptors">
  11. <list>
  12. <ref bean="openSessionInViewInterceptor"/>
  13. </list>
  14. </property>
  15. <property name="mappings">
  16. ...
  17. </property>
  18. </bean>
  19. ...
  20. </beans>
OpenSessionInViewFilter配置
  1. <web-app>
  2. ...
  3. <filter>
  4. <filter-name>hibernateFilter</filter-name>
  5. <filter-class>
  6. org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
  7. </filter-class>
  8. <!-- singleSession默認爲true,若設爲false則等於沒用OpenSessionInView -->
  9. <init-param>
  10. <param-name>singleSession</param-name>
  11. <param-value>true</param-value>
  12. </init-param>
  13. </filter>
  14. ...
  15. <filter-mapping>
  16. <filter-name>hibernateFilter</filter-name>
  17. <url-pattern>*.do</url-pattern>
  18. </filter-mapping>
  19. ...
  20. </web-app>

很多人在使用OpenSessionInView過程中提及一個錯誤:

  1. org.springframework.dao.InvalidDataAccessApiUsageException: Write operations
  2. are not allowed in read-only mode (FlushMode.NEVER) - turn your Session into
  3. FlushMode.AUTO or remove 'readOnly' marker from transaction definition

看看OpenSessionInViewFilter裏的幾個方法

  1. 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);
     
    }
    }





     
  2. protected Session getSession(SessionFactory sessionFactory)
    throws DataAccessResourceFailureException
    {
     Session session = SessionFactoryUtils.getSession(sessionFactory, true);
     session.setFlushMode(FlushMode.NEVER);
     return session;
    }

  3. protected
    void closeSession(Session session, SessionFactory sessionFactory)
    throws CleanupFailureDataAccessException
    {
     SessionFactoryUtils.closeSessionIfNecessary(session, sessionFactory);
    }

           關於綁定session的方式,通過看spring裏TransactionSynchronizationManager的實現,發現:它維護一個java.lang.ThreadLocal類型的resources,resources負責持有線程局部變量,這裏resources持有的是一個HashMap,通過TransactionSynchronizationManager.bindResource()方法在map裏綁定和線程相關的所有變量到他們的標識上,包括如上所述的綁定在sessionFactory上的線程局部session。sessionHolder只不過是存放可以hold一個session並可以和transtaction同步的容器。可以看到OpenSessionInViewFilter在getSession的時候,會把獲取回來的session的flush mode 設爲FlushMode.NEVER。然後把該sessionFactory綁定到TransactionSynchronizationManager,使request的整個過程都使用同一個session,在請求過後再接除該sessionFactory的綁定,最後closeSessionIfNecessary根據該session是否已和transaction綁定來決定是否關閉session。綁定以後,就可以防止每次不會新開一個Session呢?看看HibernateDaoSupport的情況:


        我們的DAO將使用這個template進行操作.

public abstract class BaseHibernateObjectDao extends HibernateDaoSupport
implements BaseObjectDao {
     protected BaseEntityObject getByClassId(final long id) {
                BaseEntityObject obj =(BaseEntityObject) getHibernateTemplate().execute(new HibernateCallback() {
                        public Object doInHibernate(Session session) throws HibernateException {
                                    return session.get(getPersistentClass(),new Long(id));
                        }
                });
                return obj;
      }
     public void save(BaseEntityObject entity) {
                  getHibernateTemplate().saveOrUpdate(entity);
     }

    public void remove(BaseEntityObject entity) {
              try {
                     getHibernateTemplate().delete(entity);
              } catch (Exception e) {
                      throw new FlexEnterpriseDataAccessException(e);
             }
     }

      public void refresh(final BaseEntityObject entity) {
               getHibernateTemplate().execute(new HibernateCallback() {
                          public Object doInHibernate(Session session) throws HibernateException {
                                      session.refresh(entity);
                                      return null;
                          }
               });
      }

     public void replicate(final Object entity) {
                getHibernateTemplate().execute(new HibernateCallback() {
                          public Object doInHibernate(Session session)throws HibernateException {
                                      session.replicate(entity,ReplicationMode.OVERWRITE);
                                      return null;
               }
                });
      }
}

        而HibernateTemplate試圖每次在execute之前去獲得Session,執行完就力爭關閉Session


      而這個SessionFactoryUtils能否得到當前的session以及closeSessionIfNecessary是否真正關閉session,端取決於這個session是否用sessionHolder和這個sessionFactory在我們最開始提到的TransactionSynchronizationManager綁定。

     public static void closeSessionIfNecessary(Session session, SessionFactory sessionFactory)

  1. throws CleanupFailureDataAccessException {
  2. if (session == null ||
    TransactionSynchronizationManager.hasResource(sessionFactory)) {
  3. return;
  4. }
  5. logger.debug("Closing Hibernate session");
  6. try {
  7. session.close();
  8. }
  9. catch (JDBCException ex) {
  10. // SQLException underneath
  11. throw new CleanupFailureDataAccessException("Could not close Hibernate session", ex.getSQLException());
  12. }
  13. catch (HibernateException ex) {
  14. throw new CleanupFailureDataAccessException("Could not close Hibernate session", ex);
  15. }
  16. }

    在這個過程中,若HibernateTemplate 發現自當前session有不是readOnly的transaction,就會獲取到FlushMode.AUTO Session,使方法擁有寫權限。也即是,如果有不是readOnly的transaction就可以由Flush.NEVER轉爲Flush.AUTO,擁有insert,update,delete操作權限,如果沒有transaction,並且沒有另外人爲地設flush model的話,則doFilter的整個過程都是Flush.NEVER。所以受transaction保護的方法有寫權限,沒受保護的則沒有。

  1. 可能的解決方式有:
    1、將singleSession設爲false,這樣只要改web.xml,缺點是Hibernate Session的Instance可能會大增,使用的JDBC Connection量也會大增,如果Connection Pool的maxPoolSize設得太小,很容易就出問題。
    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的事務控制。這就是常使用的方法:

採用spring的事務聲明,使方法受transaction控制

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

  2.     <bean id="userService" parent="baseTransaction">
            <property name="target">
                <bean class="com.phopesoft.security.service.impl.UserServiceImpl"/>
            </property>
        </bean>
  1.  

對於上例,則以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();

     儘管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是個雙刃劍,放在公網上內容多流量大的網站請慎用。

 

另外:這樣會產生一點危險性,畢竟把數據庫訪問的環境放到了表現層。(:用VO)

 


 

 


 

 

        Hibernate是對JDBC的輕量級對象封裝,Hibernate本身是不具備Transaction處理功能的,Hibernate的Transaction實際上是底層的JDBC Transaction的封裝,或者是JTA Transaction的封裝,下面我們詳細的分析:

  Hibernate可以配置爲JDBCTransaction或者是JTATransaction,這取決於你在hibernate.properties中的配置:

#hibernate.transaction.factory_class
net.sf.hibernate.transaction.JTATransactionFactory
#hibernate.transaction.factory_class
net.sf.hibernate.transaction.JDBCTransactionFactory

  如果你什麼都不配置,默認情況下使用JDBCTransaction,如果你配置爲:

hibernate.transaction.factory_class
net.sf.hibernate.transaction.JTATransactionFactory

  將使用JTATransaction,不管你準備讓Hibernate使用JDBCTransaction,還是JTATransaction,我的忠告就是什麼都不配,將讓它保持默認狀態,如下:

#hibernate.transaction.factory_class
net.sf.hibernate.transaction.JTATransactionFactory
#hibernate.transaction.factory_class
net.sf.hibernate.transaction.JDBCTransactionFactory

  在下面的分析中我會給出原因。

  一、JDBC Transaction

  看看使用JDBC Transaction的時候我們的代碼例子:

Session session = sf.openSession();
Transaction tx = session.beginTransactioin();
...
session.flush();
tx.commit();
session.close();
  這是默認的情況,當你在代碼中使用Hibernate的Transaction的時候實際上就是JDBCTransaction。那麼JDBCTransaction究竟是什麼東西呢?來看看源代碼就清楚了:

  Hibernate2.0.3源代碼中的類

  net.sf.hibernate.transaction.JDBCTransaction:

public void begin() throws HibernateException {
...
if (toggleAutoCommit) session.connection().setAutoCommit(false);
...
}
  這是啓動Transaction的方法,看到 connection().setAutoCommit(false) 了嗎?是不是很熟悉?

  再來看

public void commit() throws HibernateException {
...
try {
if ( session.getFlushMode()!=FlushMode.NEVER ) session.flush();
try {
session.connection().commit();
committed = true;
}
...
toggleAutoCommit();
}

  這是提交方法,看到connection().commit() 了嗎?下面就不用我多說了,這個類代碼非常簡單易懂,通過閱讀使我們明白Hibernate的Transaction都在幹了些什麼?我現在把用Hibernate寫的例子翻譯成JDBC,大家就一目瞭然了:

Connection conn = ...;               <--- session = sf.openSession();
conn.setAutoCommit(false);   <--- tx = session.beginTransactioin();
... <--- ...
conn.commit();                           <--- tx.commit(); (對應左邊的兩句)
conn.setAutoCommit(true);
conn.close();                              <--- session.close();

  看明白了吧,Hibernate的JDBCTransaction根本就是conn.commit而已,根本毫無神祕可言,只不過在Hibernate中,Session打開的時候,就會自動conn.setAutoCommit(false),不像一般的JDBC,默認都是true,所以你最後不寫commit也沒有關係,由於Hibernate已經把AutoCommit給關掉了,所以用Hibernate的時候,你在程序中不寫Transaction的話,數據庫根本就沒有反應。 

 二、JTATransaction

如果你在EJB中使用Hibernate,或者準備用JTA來管理跨Session的長事務,那麼就需要使用JTATransaction,先看一個例子:

javax.transaction.UserTransaction tx = new InitialContext().lookup("javax.transaction.UserTransaction");

Session s1 = sf.openSession();
...
s1.flush();
s1.close();
...

Session s2 = sf.openSession();
...
s2.flush();
s2.close();

tx.commit();


這是標準的使用JTA的代碼片斷,Transaction是跨Session的,它的生命週期比Session要長。如果你在EJB中使用Hibernate,那麼是最簡單不過的了,你什麼Transaction代碼統統都不要寫了,直接在EJB的部署描述符上配置某某方法是否使用事務就可以了。

現在我們來分析一下JTATransaction的源代碼, net.sf.hibernate.transaction.JTATransaction:

public void begin(InitialContext context, ...
...
ut = (UserTransaction) context.lookup(utName);
...


看清楚了嗎? 和我上面寫的代碼 tx = new Initial Context?().lookup("javax.transaction.UserTransaction"); 是不是完全一樣?

public void commit() ...
...
if (newTransaction) ut.commit();
...


JTATransaction的控制稍微複雜,不過仍然可以很清楚的看出來Hibernate是如何封裝JTA的Transaction代碼的。

但是你現在是否看到了什麼問題? 仔細想一下,Hibernate Transaction是從Session中獲得的,tx = session.beginTransaction(),最後要先提交tx,然後再session.close,這完全符合JDBC的Transaction的操作順序,但是這個順序是和JTA的Transactioin操作順序徹底矛盾的!!! JTA是先啓動Transaction,然後啓動Session,關閉Session,最後提交Transaction,因此當你使用JTA的Transaction的時候,那麼就千萬不要使用Hibernate的Transaction,而是應該像我上面的JTA的代碼片斷那樣使用才行。

總結:
1、在JDBC上使用Hibernate

必須寫上Hibernate Transaction代碼,否則數據庫沒有反應。此時Hibernate的Transaction就是Connection.commit而已

2、在JTA上使用Hibernate

寫JTA的Transaction代碼,不要寫Hibernate的Transaction代碼,否則程序會報錯

3、在EJB上使用Hibernate

什麼Transactioin代碼都不要寫,在EJB的部署描述符裏面配置

|---CMT(Container Managed Transaction)
|
|---BMT(Bean Managed Transaction)
|
|----JDBC Transaction
|
|----JTA Transaction

 


 

 


 

關於session:

1.  servlet的session機制基於cookies,關閉瀏覽器的cookies則session失效即不能用網站的登錄功能。

2.  Hibernate Session.

      1>. session 清理緩存時,按照以下順序執行SQL語句:

            session.save()的實體insert                                                                                                                                         

            實體的update

            對集合的delete

            集合元素的delete,update,insert

            集合的insert

            session.delete()的先後,執行實體的delete

       2>. 默認時,session在以下時間點清理緩存:

              net.sf.hibernate.Transaction.commit():先清理緩存,再向數據庫提交事務                                                          

              Session.find()或iterate()時,若緩存中持久化對象的屬性發生了變化,就會先清緩存,以保證查詢結果正確

 

       3>.  Session的commit()和flush()的區別:flush()只執行SQL語句,不提交事務;commit()先調用flush(),再提交事務

       4>.  Session.setFlushMode()用於設定清理緩存的時間點。

清理緩存的模式 Session的查詢方法 Session.commit() Session.flush()
FlushMode.AUTO 清理 清理 清理
FlushMode.COMMIT 不清理 清理 清理
FlushMode.NEVER 不清理 不清理 清理

           

發佈了25 篇原創文章 · 獲贊 2 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章