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管理是跨類調用的。