open session and Hibernate事務處理機制 轉載

在沒有使用Spring提供的Open Session In View狀況下,因須要在service(or Dao)層裏把session關閉,因此lazy loading 爲true的話,要在應用層內把關係集合都初始化,如 company.getEmployees(),不然Hibernate拋session already closed Exception;    Open Session In View提供了一種簡便的方法,較好地解決了lazy loading問題.    
    它有兩種配置方式OpenSessionInViewInterceptor和OpenSessionInViewFilter(具體參看 SpringSide),功能相同,只是一個在web.xml配置,另外一個在application.xml配置而已。    
     Open Session In View在request把session綁定到當前thread期間一直保持hibernate session在open狀態,使session在request的整個期間均可以使用,如在View層裏PO也能夠lazy loading數據,如 ${ company.employees }。當View 層邏輯完成後,纔會經過Filter的doFilter方法或Interceptor的postHandle方法自動關閉session。
     OpenSessionInViewInterceptor配置
Xml代碼  
  1.     
  2. <beans>   
  3. <bean name="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">   
  4. <property name="sessionFactory">   
  5. <ref bean="sessionFactory"/>   
  6. </property>   
  7. </bean>   
  8. <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">   
  9. <property name="interceptors">   
  10. <list>   
  11. <ref bean="openSessionInViewInterceptor"/>   
  12. </list>   
  13. </property>   
  14. <property name="mappings">   
  15. ...   
  16. </property>   
  17. </bean> ... </beans>   

OpenSessionInViewFilter配置
Xml代碼  
  1.    
  2. <web-app>   
  3. ...   
  4. <filter>   
  5. <filter-name>hibernateFilter</filter-name>   
  6. <filter-class> org.springframework.orm.hibernate3.support.OpenSessionInViewFilter </filter-class>   
  7. <!-- singleSession默認爲true,若設爲false則等於沒用OpenSessionInView -->   
  8. <init-param>   
  9. <param-name>singleSession</param-name>   
  10. <param-value>true</param-value>   
  11. </init-param>   
  12. </filter> ... <filter-mapping>   
  13. <filter-name>hibernateFilter</filter-name>   
  14. <url-pattern>*.do</url-pattern>   
  15. </filter-mapping> ... </web-app>   

    不少人在使用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裏的幾個方法
Java代碼  
  1.     
  2.   
  3. protected void doFilterInternal(HttpServletRequest request,   
  4.         HttpServletResponse response,  
  5.         FilterChain filterChain) throws ServletException, IOException {   
  6.         SessionFactory sessionFactory = lookupSessionFactory();   
  7.         logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");   
  8.         Session session = getSession(sessionFactory);   
  9.         TransactionSynchronizationManager.bindResource(    
  10.                 sessionFactory, new SessionHolder(session));   
  11.         try {    
  12.             filterChain.doFilter(request, response);   
  13.             }   
  14.         finally {   
  15.             TransactionSynchronizationManager.unbindResource(sessionFactory);   
  16.         logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");   
  17.         closeSession(session, sessionFactory);   
  18.         }  
  19. }   
  20. protected Session getSession(SessionFactory sessionFactory)  
  21.                    throws DataAccessResourceFailureException {   
  22.         Session session = SessionFactoryUtils.getSession(sessionFactory, true);   
  23.         session.setFlushMode(FlushMode.NEVER);   
  24.         return session;  
  25. }  
  26. protected void closeSession(Session session,   
  27.         SessionFactory sessionFactory)throws CleanupFailureDataAccessException {   
  28.     SessionFactoryUtils.closeSessionIfNecessary(session, sessionFactory);  
  29. }  

     關於綁定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的狀況:
Java代碼  
  1.    
  2. public final void setSessionFactory(SessionFactory sessionFactory) {   
  3. this.hibernateTemplate = new HibernateTemplate(sessionFactory);   
  4. }    
  5. rotected final HibernateTemplate getHibernateTemplate() {  
  6. return hibernateTemplate;    
  7.            

     咱們的DAO將使用這個template進行操做.
Java代碼  
  1.      
  2. public abstract class BaseHibernateObjectDao   
  3.                 extends HibernateDaoSupportimplements BaseObjectDao {       
  4. protected BaseEntityObject getByClassId(final long id) {                  
  5. BaseEntityObject obj =(BaseEntityObject)getHibernateTemplate().execute(new HibernateCallback() {                          
  6. public Object doInHibernate(Session session)   
  7.          throws HibernateException{                                      
  8.  return session.get(getPersistentClass(),new Long(id));                  
  9.        }                  
  10.     }  
  11. );                  
  12. return obj;        
  13. }       
  14. public void save(BaseEntityObject entity) {                    
  15.        getHibernateTemplate().saveOrUpdate(entity);       
  16. }       
  17. public void remove(BaseEntityObject entity) {                
  18. try {                       
  19.        getHibernateTemplate().delete(entity);                
  20. catch (Exception e) {                        
  21.        throw new FlexEnterpriseDataAccessException(e);               
  22.        }        
  23. }         
  24. public void refresh(final BaseEntityObject entity) {                 
  25.        getHibernateTemplate().execute(new HibernateCallback(){                            
  26.             public Object doInHibernate(Session session)   
  27.            throws HibernateException   {                                  
  28.                  session.refresh(entity);                                        
  29.                  return null;                            
  30.             }                 
  31.        }  
  32.     );        
  33. }        
  34. public void replicate(final Object entity) {                  
  35.        getHibernateTemplate().execute(new HibernateCallback(){                            
  36.              public Object doInHibernate(Session session)  
  37.                            throws HibernateException{                                        
  38.                   session.replicate(entity,ReplicationMode.OVERWRITE);                   
  39.                   eturn null;                 
  40.              }                  
  41.       });        
  42.    }  
  43. }             
   
   而HibernateTemplate試圖每次在execute以前去得到Session,執行完就力爭關閉Session
Java代碼  
  1.     
  2. public Object execute(HibernateCallback action) throws DataAccessException {    
  3.        Session session = (!this.allowCreate)SessionFactoryUtils.getSession(getSessionFactory(),  
  4.                          false);       
  5.        SessionFactoryUtils.getSession(getSessionFactory(),  
  6.                                       getEntityInterceptor(),   
  7.                                       getJdbcExceptionTranslator()));       
  8.        boolean existingTransaction = TransactionSynchronizationManager.hasResource(  
  9.                                        getSessionFactory());     
  10.        if (!existingTransaction && getFlushMode() == FLUSH_NEVER) {     
  11.             session.setFlushMode(FlushMode.NEVER);    
  12. }        
  13. try {            
  14.      Object result = action.doInHibernate(session);             
  15.      flushIfNecessary(session, existingTransaction);             
  16.      return result;      
  17. }      
  18. catch (HibernateException ex) {            
  19. throw convertHibernateAccessException(ex);       
  20. }       
  21. finally {       
  22.     SessionFactoryUtils.closeSessionIfNecessary(    
  23.     session, getSessionFactory());       
  24.     }   
  25. }         

   而這個SessionFactoryUtils可否獲得當前的session以及closeSessionIfNecessary是否真正關閉 session,取決於這個session是否用sessionHolder和這個sessionFactory在咱們最開始提到的 TransactionSynchronizationManager綁定。     
Java代碼  
  1.     
  2. public static void closeSessionIfNecessary(Session session,   
  3.                                            SessionFactory sessionFactory)   
  4.                 throws CleanupFailureDataAccessException {   
  5.    if (session == null || TransactionSynchronizationManager.hasResource(sessionFactory)) {   
  6.                return;   
  7. }   
  8.         logger.debug("Closing Hibernate session");   
  9. try {   
  10.         session.close();   
  11. catch (JDBCException ex) { // SQLException underneath  
  12.     throw new CleanupFailureDataAccessException("Could not close Hibernate session",   
  13.                                              ex.getSQLException());   
  14. catch (HibernateException ex) {   
  15.     throw new CleanupFailureDataAccessException("Could not close Hibernate session",   
  16.               ex);   
  17.        }   
  18. }       

     在這個過程當中,若HibernateTemplate 發現自當前session有不是readOnly的transaction,就會獲取到FlushMode.AUTO Session,使方法擁有 權 限。也便是,若是有不是readOnly的transaction就能夠由Flush.NEVER轉爲 Flush.AUTO,擁有insert,update,delete操做權限,若是沒有transaction,而且沒有另外人爲地設flush model的話,則doFilter的整個過程都是Flush.NEVER。因此受transaction保護的方法有寫權限,沒受保護的則沒有。
     可能的解決方式有:
一、將singleSession設爲false,這樣只要改web.xml,缺點是Hibernate Session的Instance可能會大增,使用的JDBC Connection量也會大增,若是Connection Pool的maxPoolSize設得過小,很容易就出問題。
二、在控制器中自行管理Session的FlushMode,麻煩的是每一個有Modify的Method都要多幾行程式。     
session.setFlushMode(FlushMode.AUTO);      
session.update(user);      
session.flush();
三、Extend OpenSessionInViewFilter,Override protected Session getSession(SessionFactory sessionFactory),將FlushMode直接改成Auto。
四、讓方法受Spring的事務控制。這就是常使用的方法: 採用spring的事務聲明,使方法受transaction控制 
Xml代碼  
  1.      
  2. <bean id="baseTransaction" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"             
  3. abstract="true">           
  4. <property name="transactionManager" ref="transactionManager"/>           
  5. <property name="proxyTargetClass" value="true"/>           
  6. <property name="transactionAttributes">               
  7. <props>                   
  8. <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>                   
  9. <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>                   
  10. <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>                   
  11. <prop key="save*">PROPAGATION_REQUIRED</prop>                   
  12. <prop key="add*">PROPAGATION_REQUIRED</prop>                   
  13. <prop key="update*">PROPAGATION_REQUIRED</prop>                   
  14. <prop key="remove*">PROPAGATION_REQUIRED</prop>               
  15. </props>           
  16. </property>       
  17. </bean>       
  18. <bean id="userService" parent="baseTransaction">           
  19. <property name="target">               
  20. <bean class="com.phopesoft.security.service.impl.UserServiceImpl"/>           
  21. </property>       
  22. </bean>   

    對於上例,則以save,add,update,remove開頭的方法擁有可寫的事務,若是當前有某個方法,如命名爲importExcel(),則因 沒有transaction而沒有寫權限,這時若方法內有insert,update,delete操做的話,則須要手動設置flush model爲Flush.AUTO,如 session.setFlushMode(FlushMode.AUTO); session.save(user); 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_classnet.sf.hibernate.transaction.JTATransactionFactory #hibernate.transaction.factory_classnet.sf.hibernate.transaction.JDBCTransactionFactory
  
     若是你什麼都不配置,默認狀況下使用JDBCTransaction,若是你配置爲:
引用
 
hibernate.transaction.factory_classnet.sf.hibernate.transaction.JTATransactionFactory

     將使用JTATransaction,無論你準備讓Hibernate使用JDBCTransaction,仍是JTATransaction,個人忠告就是 什麼都不配,將讓它保持默認狀態,以下:
引用
  
#hibernate.transaction.factory_classnet.sf.hibernate.transaction.JTATransactionFactory #hibernate.transaction.factory_classnet.sf.hibernate.transaction.JDBCTransactionFactory   

     在下面的分析中我會給出緣由。   
1、JDBC Transaction   
     看看使用JDBC Transaction的時候咱們的代碼例子:
Java代碼  
  1.     
  2. Session session = sf.openSession();   
  3. Transaction tx = session.beginTransactioin();   
  4. ... session.flush();   
  5. tx.commit();   
  6. session.close();     

     這是默認的狀況,當你在代碼中使用Hibernate的Transaction的時候實際上就是JDBCTransaction。那麼 JDBCTransaction到底是什麼東西呢?來看看源代碼就清楚了:   Hibernate2.0.3源代碼中的類net.sf.hibernate.transaction.JDBCTransaction:
Java代碼  
  1.     
  2. public void begin() throws HibernateException {   
  3. ...   
  4. if (toggleAutoCommit) session.connection().setAutoCommit(false);   
  5. ...   
  6. }    

     這是啓動Transaction的方法,看到 connection().setAutoCommit(false) 了嗎?是否是很熟悉?   
     再來看
Java代碼  
  1.      
  2. public void commit() throws HibernateException {   
  3. ...   
  4. try {   
  5.  if (   
  6.  session.getFlushMode()!=FlushMode.NEVER )   
  7. session.flush();   
  8. try {   
  9. session.connection().commit();   
  10. committed = true;   
  11. }   
  12. ...   
  13. toggleAutoCommit();   
  14. }     

     這是提交方法,看到connection().commit() 了嗎?下面就不用我多說了,這個類代碼很是簡單易懂,經過閱讀使咱們明白Hibernate的Transaction都在幹了些什麼?我如今把用 Hibernate寫的例子翻譯成JDBC,你們就一目瞭然了:
Java代碼  
  1.      
  2. Connection conn = ...;                 
  3. <--- session = sf.openSession();   
  4. conn.setAutoCommit(false);     
  5. <--- tx = session.beginTransactioin();   
  6. ...   
  7. <--- ... conn.commit();                             
  8. <--- tx.commit();   
  9. (對應左邊的兩句) conn.setAutoCommit(true);   
  10. conn.close();                                
  11. <--- session.close();     

   看明白了吧,Hibernate的JDBCTransaction根本就是conn.commit而已,根本毫無神祕可言,只不過在Hibernate 中,Session打開的時候,就會自動conn.setAutoCommit(false),不像通常的JDBC,默認都是true,因此你最後不寫 commit也沒有關係,因爲Hibernate已經把AutoCommit給關掉了,因此用Hibernate的時候,你在程序中不寫 Transaction的話,數據庫根本就沒有反應。 
2、JTATransaction
     若是你在EJB中使用Hibernate,或者準備用JTA來管理跨Session的長事務,那麼就須要使用JTATransaction,先看一個例子:
Java代碼  
  1.     
  2. javax.transaction.UserTransaction tx = new InitialContext().lookup("javax.transaction.UserTransaction");   
  3. Session s1 = sf.openSession();   
  4. ...   
  5. s1.flush();   
  6. s1.close();   
  7. ...   
  8. Session s2 = sf.openSession();   
  9. ...  
  10. s2.flush();   
  11. s2.close();  
  12. tx.commit();   

    這是標準的使用JTA的代碼片段,Transaction是跨Session的,它的生命週期比Session要長。若是你在EJB中使用 Hibernate,那麼是最簡單不過的了,你什麼Transaction代碼通通都不要寫了,直接在EJB的部署描述符上配置某某方法是否使用事務就可 以了。 如今咱們來分析一下JTATransaction的源代碼,
net.sf.hibernate.transaction.JTATransaction:
Java代碼  
  1.      
  2. public void begin(InitialContext context,   
  3. ...   
  4. ...   
  5. ut = (UserTransaction) context.lookup(utName);   
  6. ...   

    看清楚了嗎? 和我上面寫的代碼 「tx = new Initial Context?().lookup("javax.transaction.UserTransaction"); 」是否是徹底同樣?
Java代碼  
  1.     
  2. public void commit()   
  3. ...   
  4. ...   
  5. if (newTransaction)   
  6. ut.commit();  
  7.  ...   


    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的代碼片段那樣使用才行。
    總結:
一、在JDBC上使用Hibernate 必須寫上Hibernate Transaction代碼,不然數據庫沒有反應。此時Hibernate的Transaction就是Connection.commit而已;
二、在JTA上使用Hibernate 寫JTA的Transaction代碼,不要寫Hibernate的Transaction代碼,不然程序會報錯;
三、在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 不清理不清理清javascript

--------------------------------------------------------------------------------------------java

 <!-- Hibernate Open Session In View filter-->
 <filter>
  <filter-name>hibernateOpenSessionInViewFilter</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>
       <init-param> 
          <param-name>singleSession</param-name> 
           <param-value>true</param-value> 
      </init-param> 
 </filter>
 <filter-mapping>
  <filter-name>hibernateOpenSessionInViewFilter</filter-name>
  <url-pattern>*.do</url-pattern>
  <dispatcher>REQUEST</dispatcher>
 <dispatcher>FORWARD</dispatcher>
 </filter-mapping>web

相關文章
相關標籤/搜索