Hibernate數(shù)據(jù)庫事務(wù)攻略
Hibernate數(shù)據(jù)庫事務(wù)有很多值得學(xué)習(xí)的地方,這里我們主要介紹Hibernate數(shù)據(jù)庫事務(wù)聲明,包括介紹 非托管環(huán)境、使用JTA、異常處理等方面。
Hibernate數(shù)據(jù)庫事務(wù)聲明
數(shù)據(jù)庫(或者系統(tǒng))事務(wù)的聲明總是必須的。在數(shù)據(jù)庫事務(wù)之外,就無法和數(shù)據(jù)庫通訊(這可能會(huì)讓那些習(xí)慣于 自動(dòng)提交事務(wù)模式的開發(fā)人員感到迷惑)。永遠(yuǎn)使用清晰的事務(wù)聲明,即使只讀操作也是如此。進(jìn)行顯式的事務(wù)聲明并不總是需要的,這取決于你的事務(wù)隔離級(jí)別和數(shù)據(jù)庫的能力,但不管怎么說,聲明事務(wù)總歸有益無害。當(dāng)然,一個(gè)單獨(dú)的數(shù)據(jù)庫事務(wù)總是比很多瑣碎的事務(wù)性能更好,即時(shí)對(duì)讀數(shù)據(jù)而言也是一樣。
一個(gè)Hibernate應(yīng)用程序可以運(yùn)行在非托管環(huán)境中(也就是獨(dú)立運(yùn)行的應(yīng)用程序,簡單Web應(yīng)用程序, 或者Swing圖形桌面應(yīng)用程序),也可以運(yùn)行在托管的J2EE環(huán)境中。在一個(gè)非托管環(huán)境中,Hibernate 通常自己負(fù)責(zé)管理數(shù)據(jù)庫連接池。應(yīng)用程序開發(fā)人員必須手工設(shè)置事務(wù)聲明,換句話說,就是手工啟 動(dòng),提交,或者回滾數(shù)據(jù)庫事務(wù)。一個(gè)托管的環(huán)境通常提供了容器管理事務(wù)(CMT),例如事務(wù)裝配通過可聲 明的方式定義在EJB session beans的部署描述符中??删幊淌绞聞?wù)聲明不再需要,即使是 Session 的同步也可以自動(dòng)完成。
讓持久層具備可移植性是人們的理想,這種移植發(fā)生在非托管的本地資源環(huán)境,與依賴JTA但是使用BMT而非CMT的系統(tǒng)之間。在兩種情況下你都可以使用編程式的事務(wù)管理。Hibernate提供了一套稱為Transaction的封裝API, 用來把你的部署環(huán)境中的本地事務(wù)管理系統(tǒng)轉(zhuǎn)換到Hibernate事務(wù)上。這個(gè)API是可選的,但是我們強(qiáng)烈 推薦你使用,除非你用CMT session bean。
通常情況下,結(jié)束 Session 包含了四個(gè)不同的階段:
◆同步session(flush,刷出到磁盤)
◆提交事務(wù)
◆關(guān)閉session
◆處理異常
session的同步(flush,刷出)前面已經(jīng)討論過了,我們現(xiàn)在進(jìn)一步考察在托管和非托管環(huán)境下的事務(wù)聲明和異常處理。
1.非托管環(huán)境
如果Hibernat持久層運(yùn)行在一個(gè)非托管環(huán)境中,數(shù)據(jù)庫連接通常由Hibernate的簡單(即非DataSource)連接池機(jī)制 來處理。session/transaction處理方式如下所示:
- //Non-managed environment idiom
- Session sess = factory.openSession();
- Transaction tx = null;
- try {
- tx = sess.beginTransaction();
- // do some work
- ...
- tx.commit();
- }
- catch (RuntimeException e) {
- if (tx != null) tx.rollback();
- throw e; // or display error message
- }
- finally {
- sess.close();
- }
你不需要顯式flush() Session - 對(duì)commit()的調(diào)用會(huì)自動(dòng)觸發(fā)session的同步(取決于session的第 10.10 節(jié) “Session刷出(flush)”)。調(diào)用 close() 標(biāo)志session的結(jié)束。close()方法重要的暗示是,session釋放了JDBC連接。這段Java代碼在非托管環(huán)境下和JTA環(huán)境下都可以運(yùn)行。
更加靈活的方案是Hibernate內(nèi)置的"current session"上下文管理,前文已經(jīng)講過:
- // Non-managed environment idiom with getCurrentSession()
- try {
- factory.getCurrentSession().beginTransaction();
- // do some work
- ...
- factory.getCurrentSession().getTransaction().commit();
- }
- catch (RuntimeException e) {
- factory.getCurrentSession().getTransaction().rollback();
- throw e; // or display error message
- }
你很可能從未在一個(gè)通常的應(yīng)用程序的業(yè)務(wù)代碼中見過這樣的代碼片斷:致命的(系統(tǒng))異常應(yīng)該總是 在應(yīng)用程序“頂層”被捕獲。換句話說,執(zhí)行Hibernate調(diào)用的代碼(在持久層)和處理 RuntimeException異常的代碼(通常只能清理和退出應(yīng)用程序)應(yīng)該在不同 的應(yīng)用程序邏輯層。Hibernate的當(dāng)前上下文管理可以極大地簡化這一設(shè)計(jì),你所有的一切就是SessionFactory。 異常處理將在本章稍后進(jìn)行討論。
請(qǐng)注意,你應(yīng)該選擇 org.hibernate.transaction.JDBCTransactionFactory (這是默認(rèn)選項(xiàng)),對(duì)第二個(gè)例子來說,hibernate.current_session_context_class應(yīng)該是"thread"
2. 使用JTA
如果你的持久層運(yùn)行在一個(gè)應(yīng)用服務(wù)器中(例如,在EJB session beans的后面),Hibernate獲取 的每個(gè)數(shù)據(jù)源連接將自動(dòng)成為全局JTA事務(wù)的一部分。 你可以安裝一個(gè)獨(dú)立的JTA實(shí)現(xiàn),使用它而不使用EJB。Hibernate提供了兩種策略進(jìn)行JTA集成。
如果你使用bean管理事務(wù)(BMT),可以通過使用Hibernate的 Transaction API來告訴 應(yīng)用服務(wù)器啟動(dòng)和結(jié)束BMT事務(wù)。因此,事務(wù)管理代碼和在非托管環(huán)境下是一樣的。
- // BMT idiom
- Session sess = factory.openSession();
- Transaction tx = null;
- try {
- tx = sess.beginTransaction();
- // do some work
- ...
- tx.commit();
- }
- catch (RuntimeException e) {
- if (tx != null) tx.rollback();
- throw e; // or display error message
- }
- finally {
- sess.close();
- }
如果你希望使用與事務(wù)綁定的Session,也就是使用getCurrentSession()來簡化上下文管理,你將不得不直接使用JTA UserTransactionAPI。
- // BMT idiom with getCurrentSession()
- try {
- UserTransaction tx = (UserTransaction)new InitialContext()
- .lookup("java:comp/UserTransaction");
- tx.begin();
- // Do some work on Session bound to transaction
- factory.getCurrentSession().load(...);
- factory.getCurrentSession().persist(...);
- tx.commit();
- }
- catch (RuntimeException e) {
- tx.rollback();
- throw e; // or display error message
- }
在CMT方式下,事務(wù)聲明是在session bean的部署描述符中,而不需要編程。 因此,代碼被簡化為:
- // CMT idiom
- Session sess = factory.getCurrentSession();
- // do some work
- ...
在CMT/EJB中甚至?xí)詣?dòng)rollback,因?yàn)榧偃粲形床东@的RuntimeException從session bean方法中拋出,這就會(huì)通知容器把全局事務(wù)回滾。這就意味著,在BMT或者CMT中,你根本就不需要使用Hibernate Transaction API ,你自動(dòng)得到了綁定到事務(wù)的“當(dāng)前”Session。
注意,當(dāng)你配置Hibernate的transaction factory的時(shí)候,在直接使用JTA的時(shí)候(BMT),你應(yīng)該選擇org.hibernate.transaction.JTATransactionFactory,在CMT session bean中選擇org.hibernate.transaction.CMTTransactionFactory。記得也要設(shè)置hibernate.transaction.manager_lookup_class。還有,確認(rèn)你的hibernate.current_session_context_class未設(shè)置(為了向下兼容),或者設(shè)置為"jta"。
getCurrentSession()在JTA環(huán)境中有一個(gè)弊端。對(duì)after_statement連接釋放方式有一個(gè)警告,這是被默認(rèn)使用的。因?yàn)镴TA規(guī)范的一個(gè)很愚蠢的限制,Hibernate不可能自動(dòng)清理任何未關(guān)閉的ScrollableResults 或者Iterator,它們是由scroll()或iterate()產(chǎn)生的。你must通過在finally塊中,顯式調(diào)用ScrollableResults.close()或者Hibernate.close(Iterator)方法來釋放底層數(shù)據(jù)庫游標(biāo)。(當(dāng)然,大部分程序完全可以很容易的避免在JTA或CMT代碼中出現(xiàn)scroll()或iterate()。)
3. 異常處理
如果 Session 拋出異常 (包括任何SQLException), 你應(yīng)該立即回滾數(shù)據(jù)庫事務(wù),調(diào)用 Session.close() ,丟棄該 Session實(shí)例。Session的某些方法可能會(huì)導(dǎo)致session 處于不一致的狀態(tài)。所有由Hibernate拋出的異常都視為不可以恢復(fù)的。確保在 finally 代碼塊中調(diào)用close()方法,以關(guān)閉掉 Session。
HibernateException是一個(gè)非檢查期異常(這不同于Hibernate老的版本), 它封裝了Hibernate持久層可能出現(xiàn)的大多數(shù)錯(cuò)誤。我們的觀點(diǎn)是,不應(yīng)該強(qiáng)迫應(yīng)用程序開發(fā)人員 在底層捕獲無法恢復(fù)的異常。在大多數(shù)軟件系統(tǒng)中,非檢查期異常和致命異常都是在相應(yīng)方法調(diào)用 的堆棧的頂層被處理的(也就是說,在軟件上面的邏輯層),并且提供一個(gè)錯(cuò)誤信息給應(yīng)用軟件的用戶 (或者采取其他某些相應(yīng)的操作)。請(qǐng)注意,Hibernate也有可能拋出其他并不屬于 HibernateException的非檢查期異常。這些異常同樣也是無法恢復(fù)的,應(yīng)該 采取某些相應(yīng)的操作去處理。
在和數(shù)據(jù)庫進(jìn)行交互時(shí),Hibernate把捕獲的SQLException封裝為Hibernate的 JDBCException。事實(shí)上,Hibernate嘗試把異常轉(zhuǎn)換為更有實(shí)際含義 的JDBCException異常的子類。底層的SQLException可以 通過JDBCException.getCause()來得到。Hibernate通過使用關(guān)聯(lián)到 SessionFactory上的SQLExceptionConverter來 把SQLException轉(zhuǎn)換為一個(gè)對(duì)應(yīng)的JDBCException 異常的子類。默認(rèn)情況下,SQLExceptionConverter可以通過配置dialect 選項(xiàng)指定;此外,也可以使用用戶自定義的實(shí)現(xiàn)類(參考javadocs SQLExceptionConverterFactory類來了解詳情)。標(biāo)準(zhǔn)的 JDBCException子類型是:
◆JDBCConnectionException - 指明底層的JDBC通訊出現(xiàn)錯(cuò)誤
◆SQLGrammarException - 指明發(fā)送的SQL語句的語法或者格式錯(cuò)誤
◆ConstraintViolationException - 指明某種類型的約束違例錯(cuò)誤
◆LockAcquisitionException - 指明了在執(zhí)行請(qǐng)求操作時(shí),獲取 所需的鎖級(jí)別時(shí)出現(xiàn)的錯(cuò)誤。
◆GenericJDBCException - 不屬于任何其他種類的原生異常
4. 事務(wù)超時(shí)
EJB這樣的托管環(huán)境有一項(xiàng)極為重要的特性,而它從未在非托管環(huán)境中提供過,那就是事務(wù)超時(shí)。在出現(xiàn)錯(cuò)誤的事務(wù)行為的時(shí)候,超時(shí)可以確保不會(huì)無限掛起資源、對(duì)用戶沒有交代。在托管(JTA)環(huán)境之外,Hibernate無法完全提供這一功能。但是,Hiberante至少可以控制數(shù)據(jù)訪問,確保數(shù)據(jù)庫級(jí)別的死鎖,和返回巨大結(jié)果集的查詢被限定在一個(gè)規(guī)定的時(shí)間內(nèi)。在托管環(huán)境中,Hibernate會(huì)把事務(wù)超時(shí)轉(zhuǎn)交給JTA。這一功能通過Hibernate Transaction對(duì)象進(jìn)行抽象。
- Session sess = factory.openSession();
- try {
- //set transaction timeout to 3 seconds
- sess.getTransaction().setTimeout(3);
- sess.getTransaction().begin();
- // do some work
- ...
- sess.getTransaction().commit()
- }
- catch (RuntimeException e) {
- sess.getTransaction().rollback();
- throw e; // or display error message
- }
- finally {
- sess.close();
- }
注意setTimeout()不應(yīng)該在CMT bean中調(diào)用,此時(shí)事務(wù)超時(shí)值應(yīng)該是被聲明式定義的。
【編輯推薦】
























