天天看點

詳解Hibernate Session & 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代碼

public class PersistenceFilter implements Filter {  

    protected static ThreadLocal hibernateHolder = new ThreadLocal();  
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {  
        hibernateHolder.set(getSession());  
        try {  
             //......  
             chain.doFilter(request, response);  
             //......  
        } finally {  
           Session sess = (Session)hibernateHolder.get();  
           if (sess != null) {  
               hibernateHolder.set(null);  
               try {  
                   sess.close();  
               } catch (HibernateException ex) {  
                   throw new ServletException(ex);  
               }  
           }  
       }  
     }  
   } 
           

通過在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的話,資料庫根本就沒有反應。