(十)Hibernate之事務管理

事務的定義

事務就是指作爲單個邏輯工作單元執行的一組數據操作,這些操作要麼必須全部成功,要麼必須全部失敗,以保證數據的一致性和完整性。

事務具有ACID屬性

 

 原子性(Atomic):事務由一個或多個行爲綁在一起組成,好像是一個單獨的工作單元。原子性確保在事務中的所有操作要麼都發生,要麼都不發生。

 一致性(Consistent):一旦一個事務結束了(不管成功與否),系統所處的狀態和它的業務規則是一致的。即數據應當不會被破壞。

 隔離性(Isolated):事務應該允許多個用戶操作同一個數據,一個用戶的操作不會和其他用戶的操作相混淆。

 持久性(Durable):一旦事務完成,事務的結果應該持久化。

 


 

 事務的ACID特性是由關係數據庫管理系統(RDBMS)來實現的。

1)數據庫管理系統採用日誌來保證事務的原子性、一致性和持久性。日誌記錄了事務對數據庫所做的更新,如果某個事務在執行過程中發生錯誤,就可以根據日誌,撤銷事務對數據庫已做的更新,使數據庫退回到執行事務前的初始狀態。

2)數據庫管理系統採用鎖機制來實現事務的隔離性。當多個事務同時更新數據庫相同的數據時,只允許持有鎖的事務能更新該數據,其他事務必須等待,直到前一個事務釋放了鎖,其他事務纔有機會更新該數據。

 


數據庫事務聲明

數據庫系統的客戶程序只要向數據庫系統聲明瞭一個事務,數據庫系統就會自動保證事務的ACID特性。在JDBC API中,java.sql.Connection類代表一個數據庫連接。它提供了以下方法控制事務:

1.   setAutoCommit(Boolean autoCommit):設置是否自動提交事務。

2.    commit():提交事務。

3.     rollback():撤銷事務 

JDBC API聲明事務的示例代碼如下:

Java代碼  收藏代碼
  1. <span style="font-size: large;">Connection = null;  
  2. PreparedStatement pstmt = null;  
  3. try{  
  4. con = DriverManager.getConnection(dbUrl, username, password);  
  5. //設置手工提交事務模式  
  6. con.setAutoCommit(false);  
  7. pstmt = ……;  
  8. pstmt.executeUpdate();  
  9. //提交事務  
  10. con.commit();  
  11. }catch(Exception e){  
  12. //事務回滾  
  13. con.rollback();  
  14. …..  
  15. } finally{  
  16.     …….  
  17. }</span>  
 

Hibernate 是JDBC 的輕量級封裝,本身並不具備事務管理能力。

在事務管理層, Hibernate將其委託給底層的JDBC或者JTA,只是將底層的JDBCTransaction或者JTATransaction進行封裝一下,在外邊套上Transaction和Session的外殼,以實現事務管理和調度功能。

1)Hibernate中使用JDBC事務
如果在Hibernate中使用JDBC事務,可以在hibernate.cfg.xml中指定Hibernate事務爲JDBCTransaction。如果不進行配置,Hibernate會默認使用JDBC事務。代碼如下


Java代碼  收藏代碼
  1. <span style="font-size: large;"><span style="font-size: large;"><?xml version="1.0" encoding="utf-8" ?>  
  2. <!DOCTYPE hibernate-configuration PUBLIC  
  3.     "-//Hibernate/Hibernate Configuration DTD 3.0//EN"  
  4.     "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">  
  5. <hibernate-configuration>  
  6. <session-factory>  
  7.       <property name="hibernate.transaction.factory_class">      
  8.       org.hibernate.transaction.JDBCTransactionFactory  
  9.       </property>      
  10. </session-factory>  
  11. </hibernate-configuration></span></span>  

 基於JDBC的事務管理將事務管理委託給JDBC 進行處理無疑是最簡單的實現方式,Hibernate 對於JDBC事務的封裝也極爲簡單。 


我們來看下面這段代碼:

Java代碼
Java代碼  收藏代碼
  1. <span style="font-size: large;"><span style="font-size: large;">Transaction tx=null;  
  2. try{  
  3. session = sessionFactory.openSession();      
  4. Transaction tx = session.beginTransaction();    //開啓事務  
  5. //執行持久化操作  
  6. ……      
  7. tx.commit();   //操作正常,提交事務  
  8. }catch(RuntimeException e){  
  9.  if(tx!=null){  
  10.  tx.rollback();//操作過程中有一場,回滾事務  
  11.  throw e;//處理異常  
  12. }  
  13. }finally{  
  14.  session.close()  
  15. } </span></span>  
  從JDBC層面而言,上面的代碼實際上對應着: 
Java代碼
Java代碼  收藏代碼
  1. <span style="font-size: large;"><span style="font-size: large;">Connection dbconn = getConnection();      
  2. dbconn.setAutoCommit(false);      
  3. ……      
  4. dbconn.commit();    </span></span>  
  就是這麼簡單,Hibernate並沒有做更多的事情(實際上也沒法做更多的事情),只是將這樣的JDBC代碼進行了封裝而已。 
這裏要注意的是,在sessionFactory.openSession()中,hibernate會初始化數據庫連接,與此同時,將其AutoCommit 設爲關閉狀態(false)。而其後,在Session.beginTransaction 方法中,Hibernate 會再次確認Connection 的AutoCommit 屬性被設爲關閉狀態( 爲了防止用戶代碼對session 的Connection.AutoCommit屬性進行修改)。
這也就是說,我們一開始從SessionFactory獲得的session,其自動提交屬性就已經被關閉(AutoCommit=false),下面的代碼將不會對數據庫產生任何效果:
Java代碼
Java代碼  收藏代碼
  1. <span style="font-size: large;"><span style="font-size: large;">session = sessionFactory.openSession();      
  2. session.save(user);      
  3. session.close();     
  4. session = sessionFactory.openSession();  
  5. session.save(user);  
  6. session.close(); </span></span>  
  這實際上相當於 JDBC Connection的AutoCommit屬性被設爲false,執行了若干JDBC操作之後,沒有調用commit操作即將Connection關閉。如果要使代碼真正作用到數據庫,我們必須顯式的調用Transaction指令: 
Java代碼
Java代碼  收藏代碼
  1. <span style="font-size: large;"><span style="font-size: large;">session = sessionFactory.openSession();      
  2. Transaction tx = session.beginTransaction();      
  3. session.save(user);      
  4. tx.commit();      
  5. session.close();    </span></span>  
2)  Hibernate中使用JTA事務
具體配置爲
Java代碼  收藏代碼
  1. <span style="font-size: large;"><span style="font-size: large;"><?xml version="1.0" encoding="utf-8" ?>  
  2. <!DOCTYPE hibernate-configuration PUBLIC  
  3.     "-//Hibernate/Hibernate Configuration DTD 3.0//EN"  
  4.     "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">  
  5. <hibernate-configuration>  
  6. <session-factory>  
  7.       <property name="hibernate.transaction.factory_class">      
  8.       org.hibernate.transaction.JTATransactionFactory  
  9.       </property>      
  10. </session-factory>  
  11. </hibernate-configuration></span></span>  
 JTA 提供了跨Session 的事務管理能力。這一點是與JDBC Transaction 最大的差異。 
JDBC事務由Connnection管理,也就是說,事務管理實際上是在JDBC Connection中實現。事務週期限於Connection的生命週期之類。同樣,對於基於JDBC Transaction的Hibernate 事務管理機制而言,事務管理在Session 所依託的JDBC Connection中實現,事務週期限於Session的生命週期。 
JTA 事務管理則由 JTA 容器實現,JTA 容器對當前加入事務的衆多Connection 進 行調度,實現其事務性要求。JTA的事務週期可橫跨多個JDBC Connection生命週期。 同樣對於基於JTA事務的Hibernate而言,JTA事務橫跨可橫跨多個Session。 
JTA 事務是由JTA Container 維護,而參與事務的Connection無需對事務管理進行干涉。這也就是說,如果採用JTA Transaction,我們不應該再調用HibernateTransaction功能。 
上面基於JDBC Transaction的正確代碼,這裏就會產生問題:

Java代碼  收藏代碼
  1. <span style="font-size: large;"><span style="font-size: large;">public class ClassA{      
  2. public void saveUser(User user){      
  3. session = sessionFactory.openSession();      
  4. Transaction tx = session.beginTransaction();      
  5. session.save(user);      
  6. tx.commit();      
  7. session.close();      
  8. }      
  9. }      
  10. public class ClassB{      
  11. public void saveOrder(Order order){      
  12. session = sessionFactory.openSession();      
  13. Transaction tx = session.beginTransaction();      
  14. session.save(order);      
  15. tx.commit();      
  16. session.close();      
  17. }      
  18. }      
  19. public class ClassC{      
  20. public void save(){      
  21. ……      
  22. UserTransaction tx = new InitialContext().lookup(“……”);      
  23. ClassA.save(user);      
  24. ClassB.save(order);      
  25. tx.commit();      
  26. ……      
  27. }      
  28. }     
  29. public class ClassA{  
  30. public void saveUser(User user){  
  31. session = sessionFactory.openSession();  
  32. Transaction tx = session.beginTransaction();  
  33. session.save(user);  
  34. tx.commit();  
  35. session.close();  
  36. }  
  37. }  
  38. public class ClassB{  
  39. public void saveOrder(Order order){  
  40. session = sessionFactory.openSession();  
  41. Transaction tx = session.beginTransaction();  
  42. session.save(order);  
  43. tx.commit();  
  44. session.close();  
  45. }  
  46. }  
  47. public class ClassC{  
  48. public void save(){  
  49. try{  
  50. UserTransaction tx = (UserTransaction)new InitialContext().lookup(“java:comp/UserTransaction”);  
  51. tx.begin();//開啓JTA事務  
  52. ClassA.save(user);  
  53. ClassB.save(order);  
  54. tx.commit();//操作正常,提交JTA事務  
  55. }catch(RuntimeException e){  
  56. tx.rollback();//操作異常,回滾JTA事務  
  57. throw e;//異常處理  
  58. }finally{  
  59. }  
  60. }  
  61. }</span></span>  
    這裏有兩個類ClassA和ClassB,分別提供了兩個方法:saveUsersaveOrder,用於保存用戶信息和訂單信息。在ClassC中,我們接連調用了ClassA.saveUser方法和ClassB.saveOrder 方法,同時引入了JTA 中的UserTransaction 以實現ClassC.save方法中的事務性。問題出現了,ClassA 和ClassB 中分別都調用了Hibernate 的Transaction 功能。在Hibernate 的JTA 封裝中,Session.beginTransaction 同樣也執行了InitialContext.lookup方法獲取UserTransaction實例,Transaction.commit方法同樣也調用了UserTransaction.commit方法。實際上,這就形成了兩個嵌套式的JTA Transaction:ClassC 申明瞭一個事務,而在ClassC 事務週期內,ClassA 和ClassB也企圖申明自己的事務,這將導致運行期錯誤。因此,如果決定採用JTA Transaction,應避免再重複調用Hibernate 的 
Transaction功能,上面的代碼修改如下:
Java代碼  收藏代碼
  1. <span style="font-size: large;"><span style="white-space: normal; font-size: 12px;"><span style="font-size: large;"><span style="font-size: large;"> </span></span><span style="white-space: pre;"><span style="font-size: large;"><span style="font-size: large;">public class ClassA{</span></span></span></span></span>  
Java代碼  收藏代碼
  1. <span style="font-size: large;"><span style="font-size: large;">public void save(TUser user){      
  2. session = sessionFactory.openSession();      
  3. session.save(user);      
  4. session.close();      
  5. }      
  6. ……      
  7. }      
  8. public class ClassB{      
  9. public void save (Order order){      
  10. session = sessionFactory.openSession();      
  11. session.save(order);      
  12. session.close();      
  13. }      
  14. ……      
  15. }      
  16. public class ClassC{      
  17. public void save(){      
  18. ……      
  19. UserTransaction tx = new InitialContext().lookup(“……”);      
  20. classA.save(user);      
  21. classB.save(order);      
  22. tx.commit();      
  23. ……      
  24. }      
  25. }     
  26. public class ClassA{  
  27. public void save(TUser user){  
  28. session = sessionFactory.openSession();  
  29. session.save(user);  
  30. session.close();  
  31. }  
  32. ……  
  33. }  
  34. public class ClassB{  
  35. public void save (Order order){  
  36. session = sessionFactory.openSession();  
  37. session.save(order);  
  38. session.close();  
  39. }  
  40. ……  
  41. }  
  42. public class ClassC{  
  43. public void save(){  
  44. ……  
  45. UserTransaction tx = new InitialContext().lookup(“……”);  
  46. classA.save(user);  
  47. classB.save(order);  
  48. tx.commit();  
  49. ……  
  50. }  
  51. }  
  52. </span></span>  
 上面代碼中的ClassC.save方法,也可以改成這樣: 
Java代碼
Java代碼  收藏代碼
  1. <span style="font-size: large;"><span style="font-size: large;">public class ClassC{      
  2. public void save(){      
  3. ……      
  4. session = sessionFactory.openSession();      
  5. Transaction tx = session.beginTransaction();      
  6. classA.save(user);      
  7. classB.save(order);      
  8. tx.commit();      
  9. ……      
  10. }      
  11. }     
  12. </span></span>  
  實際上,這是利用Hibernate來完成啓動和提交UserTransaction的功能,但這樣的做法比原本直接通過InitialContext獲取UserTransaction 的做法消耗了更多的資源,得不償失。 
在EJB 中使用JTA Transaction 無疑最爲簡便,我們只需要將save 方法配置爲JTA事務支持即可,無需顯式申明任何事務,下面是一個Session Bean的save方法,它的事務屬性被申明爲“Required”,EJB容器將自動維護此方法執行過程中的事務:
Java代碼
Java代碼  收藏代碼
  1. <span style="font-size: large;"><span style="font-size: large;">/**    
  2. * @ejb.interface-method    
  3. * view-type="remote"    
  4. *    
  5. * @ejb.transaction type = "Required"    
  6. **/     
  7. public void save(){      
  8. //EJB環境中,通過部署配置即可實現事務申明,而無需顯式調用事務      
  9. classA.save(user);      
  10. classB.save(log);      
  11. }//方法結束時,如果沒有異常發生,則事務由EJB容器自動提交。</span></span> 
發佈了7 篇原創文章 · 獲贊 6 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章