Hibernate 數據庫事務與隔離級別


數據庫事務:事務是指一組相互依賴的操作行為,如銀行交易、股票交易或網上購物。事務的成功取決於這些相互依賴的操作行為是否都能執行成功,只要有一個操作行為失敗,就意味着整個事務失敗。關於事務的一個經典例子就是:A到銀行辦理轉賬事務,把100元錢轉到B的賬號上,這個事務包含以下操作行為: 

(1)從A的賬戶上減去100元。 

(2)往B的賬戶上增加100元。

      顯然,以上兩個操作必須作為一個不可分割的工作單元。假如僅僅第一步操作執行成功,使得Tom的賬戶上扣除了100元,但是第二步操作執行失敗,Jack的賬戶上沒有增加100元,那么整個事務失敗。 數據庫事務是對現實生活中事務的模擬,它由一組在業務邏輯上相互依賴的SQL語句組成。 

 下面我們一起來看一下數據庫事務的生命周期:

                                             

 

這個數據庫事務的生命周期圖反應出數據庫事務的三個邊界:

1.事務的開始邊界。 

2.事務的正常結束邊界(COMMIT):提交事務,永久保存被事務更新后的數據庫狀態。 

3.事務的異常結束邊界(ROLLBACK):撤銷事務,使數據庫退回到執行事務前的初始狀態。 

 

其實每個數據庫連接都有個全局變量@@autocommit,表示當前的事務模式,它有兩個可選值: 0:表示手工提交模式。 1:默認值,表示自動提交模式 

在自動提交模式下,每個SQL語句都是一個獨立的事務。也就是說,每執行一條sql語句,數據庫都會自動提交這個事務,當我們用數據庫另一個客戶端去查詢的時候,我們可以看到這個新修改或插入的數據。在手工提交模式下,必須顯式指定事務開始邊界和結束邊界: 

–事務的開始邊界:begin 

–提交事務:commit 

–撤銷事務:rollback 

 

下面我們來看一下通過JDBC API是如何聲明事務邊界的:

Connection提供了以下用於控制事務的方法: 

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

2.commit():提交事務 

3.rollback():撤銷事務 

下面我們看一下具體的應用示例:

 

[java]   view plain copy print ?
  1. try {   
  2. con = java.sql.DriverManager.getConnection(dbUrl,dbUser,dbPwd);   
  3. //設置手工提交事務模式   
  4. con.setAutoCommit(false);   
  5. stmt = con.createStatement();   
  6. //數據庫更新操作1   
  7. stmt.executeUpdate("update ACCOUNTS set BALANCE=900 where ID=1 ");   
  8. //數據庫更新操作2   
  9. stmt.executeUpdate("update ACCOUNTS set BALANCE=1000 where ID=2 ");   
  10. con.commit(); //提交事務   
  11. }catch(Exception e) {   
  12. try{   
  13. con.rollback(); //操作不成功則撤銷事務   
  14. }catch(Exception ex){   
  15. //處理異常   
  16. ……   
  17. }   
  18. //處理異常   
  19. ……   
  20. }finally{…}   



 

 

       看到上邊的示例我們可以看出,其實hibernate事務邊界就是模仿者JDBC的事務邊界來的,其實在hibernate底層的事務管理就是利用的JDBC的事務管理。我們來看一下hibernate事務邊界:

1.聲明事務的開始邊界: 

Transaction tx=session.beginTransaction(); 

2.提交事務: tx.commit(); 

3.撤銷事務: tx.rollback(); 

 

       我們在學習JDBC數據庫事務管理的時候,重點也是難點的學習了jdbc多個事務並發問題。既然hibernate底層是用JDBC事務管理實現的,那么它也一定存在着多個事務並發的問題。下面我們就具體來看一下:hibernate多個事務並發的並發問題:

•第一類丟失更新:撤銷一個事務時,把其他事務已提交的更新數據覆蓋。 

•臟讀:一個事務讀到另一事務未提交的更新數據。 

•虛讀:一個事務讀到另一事務已提交的新插入的數據。 

•不可重復讀:一個事務讀到另一事務已提交的更新數據。 

•第二類丟失更新:這是不可重復讀中的特例,一個事務覆蓋另一事務已提交的更新數據。 

下面我們就臟讀來舉一個示例:

                                            

 

 

取款事務在T5時刻把存款余額改為900元,支票轉賬事務在T6時刻查詢賬戶的存款余額為900元,取款事務在T7時刻被撤銷,支票轉賬事務在T8時刻把存款余額改為1000元。 由於支票轉賬事務查詢到了取款事務未提交的更新數據,並且在這個查詢結果的基礎上進行更新操作,如果取款事務最后被撤銷,會導致銀行客戶損失100元。 

事務隔離級別

關於事務隔離級別,我們來看一下下面的兩個圖解:

 

                                  

                                    

 

 

由上圖可以看出:隔離級別越高,越能保證數據的完整性和一致性,但是對並發性能的影響也越大。 對於多數應用程序,可以優先考慮把數據庫系統的隔離級別設為Read Committed,它能夠避免臟讀,而且具有較好的並發性能。盡管它會導致不可重復讀、虛讀和第二類丟失更新這些並發問題,在可能出現這類問題的個別場合,可以由應用程序采用悲觀鎖或樂觀鎖來控制。 

下面我們就具體來看一下hibernate怎么來配置隔離級別:在Hibernate的配置文件中可以顯式的設置隔離級別。每一種隔離級別都對應一個整數: 

1:Read Uncommitted 

2:Read Committed 

4:Repeatable Read 

8:Serializable 

例如,以下代碼把hibernate.cfg.xml文件中的隔離級別設為Read Committed: 

hibernate.connection.isolation=2 

或者SpringHibernate.xml文件事務配置的節點isolation="READ_COMMITTED" 即可

對於從數據庫連接池中獲得的每個連接,Hibernate都會把它改為使用Read Committed隔離級別。

 

Spring在TransactionDefinition接口中定義了五個不同的事務隔離級別:隔離級別是指若干個並發的事務之間的隔離程度。TransactionDefinition 接口中定義了五個表示隔離級別的常量:

 

  • TransactionDefinition.ISOLATION_DEFAULT:這是默認值,表示使用底層數據庫的默認隔離級別。對大部分數據庫而言,通常這值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的數據。該級別不能防止臟讀和不可重復讀,因此很少使用該隔離級別。
  • TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級別表示一個事務只能讀取另一個事務已經提交的數據。該級別可以防止臟讀,這也是大多數情況下的推薦值。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級別表示一個事務在整個過程中可以多次重復執行某個查詢,並且每次返回的記錄都相同。即使在多次查詢之間有新增的數據滿足該查詢,這些新增的記錄也會被忽略。該級別可以防止臟讀和不可重復讀。
  • TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止臟讀、不可重復讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。

 

在TransactionDefinition接口中定義了七個事務傳播行為

 

所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為。在TransactionDefinition定義中包括了如下幾個表示傳播行為的常量:

 

  • TransactionDefinition.PROPAGATION_REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:創建一個新的事務,如果當前存在事務,則把當前事務掛起。
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事務方式運行,如果當前存在事務,則拋出異常。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。
  • TransactionDefinition.PROPAGATION_NESTED:如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。

 

這里需要指出的是,前面的六種事務傳播行為是 Spring 從 EJB 中引入的,他們共享相同的概念。而 PROPAGATION_NESTED是 Spring 所特有的。以 PROPAGATION_NESTED 啟動的事務內嵌於外部事務中(如果存在外部事務的話),此時,內嵌事務並不是一個獨立的事務,它依賴於外部事務的存在,只有通過外部的事務提交,才能引起內部事務的提交,嵌套的子事務不能單獨提交。如果熟悉 JDBC 中的保存點(SavePoint)的概念,那嵌套事務就很容易理解了,其實嵌套的子事務就是保存點的一個應用,一個事務中可以包括多個保存點,每一個嵌套子事務。另外,外部事務的回滾也會導致嵌套子事務的回滾。

 

事務的回滾規則:

 

通常情況下,如果在事務中拋出了未檢查異常(繼承自 RuntimeException 的異常),則默認將回滾事務。如果沒有拋出任何異常,或者拋出了已檢查異常,則仍然提交事務。這通常也是大多數開發者希望的處理方式,也是 EJB 中的默認處理方式。但是,我們可以根據需要人為控制事務在拋出某些未檢查異常時任然提交事務,或者在拋出某些已檢查異常時回滾事務。

 

    • 如果事務是只讀的,那么我們可以指定只讀屬性,使用“readOnly”指定。否則我們不需要設置該屬性。
    • 超時屬性的取值必須以“TIMEOUT_”開頭,后面跟一個int類型的值,表示超時時間,單位是秒。
    • 不影響提交的異常是指,即使事務中拋出了這些類型的異常,事務任然正常提交。必須在每一個異常的名字前面加上“+”。異常的名字可以是類名的一部分。比如“+RuntimeException”、“+tion”等等。
    • 導致回滾的異常是指,當事務中拋出這些類型的異常時,事務將回滾。必須在每一個異常的名字前面加上“-”。異常的名字可以是類名的全部或者部分,比如“-RuntimeException”、“-tion”等等。

      ----------------------------------------------------------------------------------------

      一、丟失或覆蓋更新

      當兩個或多個事務選擇同一行,然后基於最初選定的值更新該行時,會發生丟失更新問題。每個事務都不知道其它事務的存在。最后的更新將重寫由其它事務所做的更新,這將導致數據丟失。
      舉例如下:

      事務A和事務B同時修改某行的值,
      事務A將數值改為1並提交 事務B將數值改為2並提交。
      這時數據的值為2,事務A所做的更新將會丟失。 解決辦法:對行加鎖,只允許並發一個更新事務。 -----------------------------------------------------------------------------------------

      二、臟讀:未確認的相關性

      當第二個事務選擇其它事務正在更新的行時,會發生未確認的相關性問題。第二個事務正在讀取的數據還沒有確認並且可能由更新此行的事務所更改。
      舉例如下: Mary的原工資為1000, 財務人員將Mary的工資改為了8000(但未提交事務)  Mary讀取自己的工資,發現自己的工資變為了8000,歡天喜地! 而財務發現操作有誤,回滾了事務,Mary的工資又變為了1000
      像這樣,Mary記取的工資數8000是一個臟數據。
      解決辦法:如果在第一個事務提交前,任何其他事務不可讀取其修改過的值,則可以避免該問題。 -------------------------------------------------------------------------------------------

      三、非重復讀:不一致的分析

      當第二個事務多次訪問同一行而且每次讀取不同的數據時,會發生不一致的分析問題。不一致的分析與未確認的相關性類似,因為其它事務也是正在更改第二個事務正在讀取的數據。然而,在不一致的分析中,第二個事務讀取的數據是由已進行了更改的事務提交的。而且,不一致的分析涉及多次(兩次或更多)讀取同一行,而且每次信息都由其它事務更改;因而該行被非重復讀取。
      在一個事務中前后兩次讀取的結果並不致,導致了不可重復讀。
      舉例如下: 在事務1中,Mary 讀取了自己的工資為1000,操作並沒有完成 在事務2中,這時財務人員修改了Mary的工資為2000,並提交了事務. 在事務1中,Mary 再次讀取自己的工資時,工資變為了2000
      解決辦法:如果只有在修改事務完全提交之后才可以讀取數據,則可以避免該問題。

      --------------------------------------------------------------------------------------------

      四、幻想讀

      當對某行執行插入或刪除操作,而該行屬於某個事務正在讀取的行的范圍時,會發生幻像讀問題。事務第一次讀的行范圍顯示出其中一行已不復存在於第二次讀或后續讀中,因為該行已被其它事務刪除。同樣,由於其它事務的插入操作,事務的第二次或后續讀顯示有一行已不存在於原始讀中。

      舉例如下:

      目前工資為1000的員工有10人。 事務A讀取所有工資為1000的員工。 這時事務B向employee表插入了一條員工記錄,工資也為1000
      解決辦法:如果在操作事務完成數據處理之前,任何其他事務都不可以添加新數據,則可避免該問題

    •  

      Spring官方參考文檔:

      http://static.springsource.org/spring/docs/

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM