詳解Hibernate Session & Transaction

詳解Hibernate Session & Transaction  

2011-04-06 19:32:27|  分類: JavaEE |  標籤:session  線程  hibernate  實例  transaction  |舉報|字號 訂閱

HIbernate中的Session
  Session是JAVA應用程序和Hibernate進行交互時使用的主要接口,它也是持久化操作核心API, 
  注意這裏的Session的含義,它與傳統意思上web層的HttpSession並沒有關係,Hibernate Session之與Hibernate,相當於JDBC Connection相對與JDBC。 
  Session對象是有生命週期的,它以Transaction對象的事務開始和結束邊界 
  Session作爲貫穿Hibernate的持久化管理器核心,提供了衆多的持久化的方法,如save(), update ,delete ,find(Hibernate 3中已經取消了此方法)等,通過這些方法我們可以透明的完成對象的增刪改查(CRUD-- create read update delete),這裏所謂的透明是指,Session在讀取,創建和刪除影射的實體對象的實例時,這一系列的操作將被轉換爲對數據庫表中數據的增加,修改,查詢和刪除操作。

       SessionFactory負責創建Session,SessionFactory是線程安全的,多個併發線程可以同時訪問一個SessionFactory 並從中獲取Session實例。而Session並非線程安全,也就是說,如果多個線程同時使用一個Session實例進行數據存取,則將會導致Session 數據存取邏輯混亂.因此創建的Session實例必須在本地存取空上運行,使之總與當前的線程相關。


Session有以下的特點
  1,不是線程安全的,應該避免多個線程共享同一個Session實例 
  2,Session實例是輕量級的,所謂輕量級:是指他的創建和刪除不需要消耗太多資源 
  3,Session對象內部有一個緩存,被稱爲Hibernate第一緩存,他存放被當前工作單元中加載的對象,每個Session實例都有自己的緩存。

Hibernate Session緩存被稱爲Hibernate的第一級緩存。SessionFactory的外置緩存稱爲Hibernate的二級緩存。這兩個緩存都位於持久層,它們存放的都是數據庫數據的拷貝。SessionFactory的內置緩存 存放元數據和預定義SQL, SessionFactory的內置緩存是隻讀緩存。

Hibernate Session緩存的三大作用:
1,減少數據庫的訪問頻率,提高訪問性能。
2,保證緩存中的對象與數據庫同步,位於緩存中的對象稱爲持久化對象。
3,當持久化對象之間存在關聯時,Session 保證不出現對象圖的死鎖。
Session 如何判斷持久化對象的狀態的改變呢?
Session 加載對象後會爲對象值類型的屬性複製一份快照。當Session 清理緩存時,比較當前對象和它的快照就可以知道那些屬性發生了變化。

Session 什麼時候清理緩存?
1,commit() 方法被調用時
2,查詢時會清理緩存,保證查詢結果能反映對象的最新狀態。
3,顯示的調用session 的 flush方法。
session 清理緩存的特例:
當對象使用 native 生成器 時 會立刻清理緩存向數據庫中插入記錄。


org.hibernate Interface Session
  public interface Session extends Serializable : 是一個Java application 和Hibernate之間主要的運行時接口,這是執行持久化服務的中心API 
  主要方法: 
  public Transaction beginTransaction() throws HibernateException【1】:返回和當前Session對象相互聯繫的Transaction對象(表示在數據庫中重新開始一個事務) 
  public Transaction getTransaction(): 返回和當前session聯繫的Transaction對象 
  public Connection connection close() throws HibernateExcepton:結束當前的Session對象 
  public void clear() :清空Session,清除所有保存在當前Session緩存中的實體對象,終止所有正在執行的方法(eg: save() , update() ,delete() .....) 
  public Serializable save(Object object)throws HibernateException 對當前參數指定的對象進行持久化(系統會首先賦予參數對象一個標識符OID),他相當於insert語句 後面在詳細介紹 
  public Connection connection() throws HibernateException 得到當前Session 中包含的Connection對象。 
  public boolean contains(Object object):判斷參數給出的對象(持久化類)是否在當前Session的緩存中 
  public void evict(Object object) throws HibernateException :將參數給出的Object從當前Session對象類中刪除,使這個對象從持久態變成遊離態,這種狀態的改變不會引起對數據庫的同步,後面詳細介紹 
  public Object load(Class theclass ,Serializable id) throws HibernateException 返回第一個參數指定類對應的表中,第二個參數指定的行(第二個參數就是要取得對象的OID,他對應表中主鍵列的值) 
  public void update(Object object) throws HibernateException :更新一個對象到數據庫中,後面在詳細介紹 
  public void delete (Object object)throws HibernateException:從數據庫中刪除和參數指定的對象對應的記錄 
  public Object get(Class class,Serializable id) throws HibernateException:和load()方法一樣區別在於,如果數據庫表中沒有對應的記錄,get()方法返回null,load()方法將報異常
Transaction
  Transanction接口是Hibernate的數據庫事務接口,用於管理事務,他對底層的事務作出了封裝,用戶可以使用Transanction對象定義自己的對數據庫的原子操作,底層事務包括:JDBC API ,JTA(Java Transaction API)。。。。。 
  一個Transaction對象的事務可能會包括多個對數據庫進行的操作 
  org.hibernate Interface Transaction 
  public interface Transaction
常用方法:
  public void commit() throws HibernateException 刷新當前的Session以及結束事務的工作,這個方法將迫使數據庫對當前的事務進行提交 
  public void rollback() throws HibernateException :強迫回滾當前事務 
  public boolean isActive() throws HibernateException: 這個事務是否存活 
  ----------------------------------------------------------------------------------------


  Session:當中包含一個Connection對象 
  Connection c =session.getConnection(); 
  Session的緩存用於臨時保存持久化的對象,等到一定時候,再將緩存中的對象保存到數據庫中。 
  應用程序事務:如果一個Session中包含有多個Transaction(數據庫事務),這些Transaction的集合稱爲應用程序事務 
  標準使用形式: 
  Configuration config=new Configuration().configure("hibernate.cfg.xml"); 
  SessionFactory sessionfactory=config.buildSessionFactory(); 
  Session session=sessionfactory.openSession(); 
  Transaction tx=session.beginTransaction(); 
  try 
  { 
  session.save(); 
  tx.commit(); 
  } 
  catch(Exception e) 
  { 
  if(tx!=null) tx.rollback(); 
  } 
  finally 
  { 
  session.close (); 
  }

 

保證session的線程安全

ThreadLocal,在很多種Session 管理方案中都用到了它.ThreadLocal 是Java中一種較爲特殊的線程綁定機制,通過ThreadLocal存取的數據,總是與當前線程相關,也就是說,JVM 爲每個運行的線程,綁定了私有的本地實例存取空間,從而爲多線程環境常出現的併發訪問問題提供了一種隔離機制,ThreadLocal並不是線程本地化的實現,而是線程局部變量。

也就是說每個使用該變量的線程都必須爲該變量提供一個副本,每個線程改變該變量的值僅僅是改變該副本的值,而不會影響其他線程的該變量的值,ThreadLocal是隔離多個線程的數據共享,不存在多個線程之間共享資源,因此不再需要對線程同步。

請看一下代碼:
public class HibernateUtil {  
 
public static final SessionFactory sessionFactory;  
public static final ThreadLocal session = new ThreadLocal();  
static{  
try{  
Configuration configuration=new Configuration().configure();   
sessionFactory = configuration.buildSessionFactory();  
}catch (Throwable ex){  
System.err.println("Initial SessionFactory creation failed." + ex);  
throw new ExceptionInInitializerError(ex);  
}  
}  
public static Session currentSession() throws HibernateException{  
Session s = (Session) session.get();  
if (s == null)  
{  
s = sessionFactory.openSession();  
session.set(s);  
}  
return s;  
}  
 
public static void closeSession() throws HibernateException {  
Session s = (Session) session.get();  
if (s != null)  
s.close();  
session.set(null);  
}  
}

     不安全解釋,例如:Servlet 運行是多線程的,而應用服務器並不會爲每個線程都創建一個Servlet實例,也就是說,TestServlet在應用服務器中只有一個實例(在Tomcat中是這樣,其他的應用服務器可能有不同的實現),而這個實例會被許多個線程併發調用,doGet 方法也將被不同的線程反覆調用,可想而知,每次調用doGet 方法,這個唯一的TestServlet 實例的session 變量都會被重置

      在代碼中,只要藉助上面這個工具類獲取Session 實例,我們就可以實現線程範圍內的Session 共享,從而避免了在線程中頻繁的創建和銷燬Session 實例。不過注意在線程結束時關閉Session。同時值得一提的是,新版本的Hibernate在處理Session的時候已經內置了延遲加載機制,只有在真正發生數據庫操作的時候,纔會從數據庫連接池獲取數據庫連接,我們不必過於擔心Session的共享會導致整個線程生命週期內數據庫連接被持續佔用。

對於Web程序
而言,我們可以藉助Servlet2.3規範中新引入的Filter機制,輕鬆實現線程生命週期內的Session管理(關於Filter的具體描述,請參考Servlet2.3規範)。Filter的生命週期貫穿了其所覆蓋的Servlet(JSP也可以看作是一種特殊的Servlet)
及其底層對象。Filter在Servlet被調用之前執行,在Servlet調用結束之後結束。因此,在Filter 中管理Session 對於Web 程序而言就顯得水到渠成。下面是一個通過Filter 進行Session管理的典型案例:

Java代碼

   1. public class PersistenceFilter implements Filter  
   2. {  
   3. protected static ThreadLocal hibernateHolder = new ThreadLocal();  
   4. public void doFilter(ServletRequest request, ServletResponse  
   5. response, FilterChain chain)  
   6. throws IOException, ServletException  
   7. {  
   8. hibernateHolder.set(getSession());  
   9. try  
  10. {  
  11. ......  
  12. chain.doFilter(request, response);  
  13. ......  
  14. }  
  15. finally  
  16. {  
  17. Session sess = (Session)hibernateHolder.get();  
  18. if (sess != null)  
  19. {  
  20. hibernateHolder.set(null);  
  21. try  
  22. {  
  23. sess.close();  
  24. }  
  25. catch (HibernateException ex) {  
  26. throw new ServletException(ex);  
  27. }  
  28. }  
  29. }  
  30. }  
  31. ......  
  32. Hibernate Developer's Guide Version 1.0  
  33. September 2, 2004 So many open source projects. Why not Open your Documents?  
  34. } 

通過在doFilter中獲取和關閉Session,並在週期內運行的所有對象(Filter鏈中其餘的Filter,及其覆蓋的Servlet 和其他對象)對此Session 實例進行重用,保證了一個Http Request處理過程中只佔用一個Session,提高了整體性能表現。在實際設計中,Session的重用做到線程級別一般已經足夠,企圖通過HttpSession實現用戶級的Session重用反而可能導致其他的問題。凡事不能過火,Session重用也一樣。 

 

Hibernate中的Transaction

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使用JDBCTransaction,還是JTATransaction,我的忠告就是什麼都不配,將讓它保持默認狀態,如下: 
在下面的分析中我會給出原因。

一、JDBC Transaction

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

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

Hibernate2.0.3源代碼中的類
Java代碼  
net.sf.hibernate.transaction.JDBCTransaction:   
 public void begin(); throws HibernateException {   
    log.debug("begin");;       
    try {   
        toggleAutoCommit = session.connection();.getAutoCommit();;   
        if (toggleAutoCommit); session.connection();.setAutoCommit(false);;   
    }   
    catch (SQLException e); {   
        log.error("Begin failed", e);;   
        throw new TransactionException("Begin failed with SQL exception: ", e);;   
    }      
    begun = true;   

這是啓動Transaction的方法,看到 connection().setAutoCommit(false) 了嗎?是不是很熟悉? 
public void commit(); throws HibernateException {      
    if (!begun); throw new TransactionException("Transaction not successfully started");;      
    log.debug("commit");;      
    try {   
        if ( session.getFlushMode();!=FlushMode.NEVER ); session.flush();;   
        try {   
            session.connection();.commit();;   
            committed = true;   
        }   
        catch (SQLException e); {   
            log.error("Commit failed", e);;   
            throw new TransactionException("Commit failed with SQL exception: ", e);;   
        }   
    }   
    finally {   
        session.afterTransactionCompletion();;   
    }   
    toggleAutoCommit();;   

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

Java代碼  
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,先看一個例子:

Java代碼  
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:

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

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

Java代碼  
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的部署描述符裏面配置 
Java代碼  
|---CMT(Container Managed Transaction);   
|   
|---BMT(Bean Managed Transaction);   
        |   
        |----JDBC Transaction   
        |   
        |----JTA Transaction  

你說“Hibernate的JDBCTransaction根本就是conn.commit而已,根本毫無神祕可言,只不過在Hibernate中,Session打開的時候,就會自動conn.setAutoCommit(false),不像一般的JDBC,默認都是true,所以你最後不寫commit也沒有關係,由於Hibernate已經把AutoCommit給關掉了,所以用Hibernate的時候,你在程序中不寫Transaction的話,數據庫根本就沒有反應” 
但sf.opengSession()時,並沒有setAutoCommit(false),我想問的是,如果不編寫任何事務代碼,如:
Java代碼  
Session s = sf.openSession();;   
......   
s.close();;  
數據庫會不會有反應(此時應該是默認AutoCommit爲true)。

另外,我想問一下: 
1. s.flush()是不是必須的 
2. s.close()是不是一定要關閉 
比如你上面提到的: 
Java代碼  
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();;  
s1不關閉,使用s2進行操作的代碼中使用s1可不可以(我覺得這樣更加節約資源,不需要反覆的連接、關閉)

引用
但sf.opengSession()時,並沒有setAutoCommit(false),我想問的是,如果不編寫任何事務代碼,如:
Session s = sf.openSession(); 
...... 
s.close(); 
數據庫會不會有反應(此時應該是默認AutoCommit爲true)。
不會有反應。在sf.openSession() 創建Session實例的時候,就已經調用了conn.setAutoCommit(false)了。 
引用
另外,我想問一下: 
1. s.flush()是不是必須的 
2. s.close()是不是一定要關閉 
s.flush不是必須的,s.close()會調用一次s.flush()

s.close()正常情況下應該關閉,除非你是用ThreadLocal管理Session。 
引用
s1不關閉,使用s2進行操作的代碼中使用s1可不可以(我覺得這樣更加節約資源,不需要反覆的連接、關閉)
在這個例子中看不出來JTA的作用。 
假設 
Java代碼  
Class A  {   
  find(); {   
    Session s1 = sf.openSession();;    
    ...    
    s1.flush();;    
    s1.close();;    
  }   


Java代碼  
Class B  {   
  find(); {   
    Session s2 = sf.openSession();;    
    ...    
    s2.flush();;    
    s2.close();;    
  }   


Java代碼  
Main {   
  
  tx = ...;   
  A.find();;   
  B.find();;   
  tx.commit();;   
}  
看明白了嗎?JTA的Transaction管理是跨類調用的。

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