程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Hibernate的事務和並發(一)

Hibernate的事務和並發(一)

編輯:關於JAVA

Hibernate的事務和並發控制很容易掌握。Hibernate直接使用JDBC連接和JTA資源,不添加任何附加鎖定 行為。我們強烈推薦你花點時間了解JDBC編程,ANSI SQL查詢語言和你使用 的數據庫系統的事務隔離規范。Hibernate只添加自動版本管理,而不會鎖 定內存中的對象,也不會改變數據庫事務的隔離級別。基本上,使用 Hibernate就好像直接使用JDBC(或者JTA/CMT)來訪問你的數據庫資源。

除了自動版本管理,針對行級悲觀鎖定,Hibernate也提供了輔助的API,它使用了 SELECT FOR UPDATE的SQL語法。本章後面會討論這個API。

我們從Configuration層、SessionFactory層, 和 Session層開始討論Hibernate的並行控制、數據庫事務和應用 程序的長事務。

12.1.Session和事務范圍(transaction scopes)

一個SessionFactory對象的創建代價很昂貴,它是線程安全的對象,它被設計成可以 為所有的應用程序線程所共享。它只創建一次,通常是在應用程序啟動的時候,由一個 Configuraion的實例來創建。

一個Session的對象是輕型的,非線程安全的,對於單個業務進程,單個的 工作單元而言,它只被使用一次,然後就丟棄。只有在需要的時候,Session 才會獲取一個JDBC的Connection(或一個Datasource) 對象。所以你可以放心的打開和關閉Session,甚至當你並不確定一個特定的請 求是否需要數據訪問時,你也可以這樣做。(一旦你實現下面提到的使用了請求攔截的模式,這就 變得很重要了。

此外我們還要考慮數據庫事務。數據庫事務應該盡可能的短,降低數據庫鎖定造成的資源爭用。 數據庫長事務會導致你的應用程序無法擴展到高的並發負載。

一個操作單元(Unit of work)的范圍是多大?單個的Hibernate Session能跨越多個 數據庫事務嗎?還是一個Session的作用范圍對應一個數據庫事務的范圍?應該何時打開 Session,何時關閉Session?,你又如何劃分數據庫事務的邊界呢?

12.1.1.操作單元(Unit of work)

首先,別再用session-per-operation這種反模式了,也就是說,在單個線程中, 不要因為一次簡單的數據庫調用,就打開和關閉一次Session!數據庫事務也是如此。 應用程序中的數據庫調用是按照計劃好的次序,分組為原子的操作單元。(注意,這也意味著,應用程 序中,在單個的SQL語句發送之後,自動事務提交(auto-commit)模式失效了。這種模式專門為SQL控制台操作設計的。 Hibernate禁止立即自動事務提交模式,或者期望應用服務器禁止立即自動事務提交模式。)

在多用戶的client/server應用程序中,最常用的模式是 每個請求一個會話(session-per-request)。 在這種模式下,來自客戶端的請求被發送到服務器端(即Hibernate持久化層運行的地方),一 個新的Hibernate Session被打開,並且執行這個操作單元中所有的數據庫操作。 一旦操作完成(同時發送到客戶端的響應也准備就緒),session被同步,然後關閉。你也可以使用單 個數據庫事務來處理客戶端請求,在你打開Session之後啟動事務,在你關閉 Session之前提交事務。會話和請求之間的關系是一對一的關系,這種模式對 於大多數應用程序來說是很棒的。

真正的挑戰在於如何去實現這種模式:不僅Session和事務必須被正確的開始和結束, 而且他們也必須能被數據訪問操作訪問。用攔截器來實現操作單元的劃分,該攔截器在客戶端請求達到服 務器端的時候開始,在服務器端發送響應(即,ServletFilter)之前結束。我們推薦 使用一個ThreadLocal 變量,把 Session綁定到處理客戶端請求的線 程上去。這種方式可以讓運行在該線程上的所有程序代碼輕松的訪問Session(就像訪問一 個靜態變量那樣)。你也可以在一個ThreadLocal 變量中保持事務上下文環境,不過這依賴 於你所選擇的數據庫事務劃分機制。這種實現模式被稱之為 ThreadLocal Session和 Open Session in View。你可以很容易的擴展本文前面章節展示的 HibernateUtil 輔助類來實現這種模式。當然,你必須找到一種實現攔截器的方法,並 且可以把攔截器集成到你的應用環境中。請參考Hibernate網站上面的提示和例子。

12.1.2.應用程序事務(Application transactions)

session-per-request模式不僅僅是一個可以用來設計操作單元的有用概念。很多業務處理流程都需 要一系列完整的和用戶之間的交互,即用戶對數據庫的交叉訪問。在基於web的應用和企業 應用中,跨用戶交互的數據庫事務是無法接受的。考慮下面的例子:

在界面的第一屏,打開對話框,用戶所看到的數據是被一個特定的 Session 和數據 庫事務載入(load)的。用戶可以隨意修改對話框中的數據對象。

5分鐘後,用戶點擊“保存”,期望所做出的修改被持久化;同時他也期望自己是唯一修改這個信息的人,不會出現 修改沖突。

從用戶的角度來看,我們把這個操作單元稱為應用程序長事務(application transaction)。 在你的應用程序中,可以有很多種方法來實現它。

頭一個幼稚的做法是,在用戶思考的過程中,保持Session和數據庫事務是打開的, 保持數據庫鎖定,以阻止並發修改,從而保證數據庫事務隔離級別和原子操作。這種方式當然是一個反模式, 因為數據庫鎖定的維持會導致應用程序無法擴展並發用戶的數目。

很明顯,我們必須使用多個數據庫事務來實現一個應用程序事務。在這個例子中,維護業務處理流程的 事務隔離變成了應用程序層的部分責任。單個應用程序事務通常跨越多個數據庫事務。如果僅僅只有一 個數據庫事務(最後的那個事務)保存更新過的數據,而所有其他事務只是單純的讀取數據(例如在一 個跨越多個請求/響應周期的向導風格的對話框中),那麼應用程序事務將保證其原子性。這種方式比聽 起來還要容易實現,特別是當你使用了Hibernate的下述特性的時候:

自動版本化 - Hibernate能夠自動進行樂觀並發控制 ,如果在用戶思考 的過程中發生並發修改沖突,Hibernate能夠自動檢測到。

脫管對象(Detached Objects)- 如果你決定采用前面已經討論過的 session-per-request模式,所有載入的實例在用戶思考的過程 中都處於與Session脫離的狀態。Hibernate允許你把與Session脫離的對象重新關聯到Session 上,並且對修改進行持久化,這種模式被稱為 session-per-request-with-detached-objects。自動版本化被用來隔離並發修改。

長生命周期的Session (Long Session)- Hibernate 的Session 可以在數據庫事務提交之後和底層的JDBC連接斷開,當一個新的客戶端請求到來的時候,它又重新連接上底層的 JDBC連接。這種模式被稱之為session-per-application-transaction,這種情況可 能會造成不必要的Session和JDBC連接的重新關聯。自動版本化被用來隔離並發修改。

session-per-request-with-detached-objects 和 session-per-application-transaction 各有優缺點,我們在本章後面樂觀並發 控制那部分再進行討論。

12.1.3.關注對象標識(Considering object identity)

應用程序可能在兩個不同的Session中並發訪問同一持久化狀態,但是, 一個持久化類的實例無法在兩個 Session中共享。因此有兩種不同的標識語義:

數據庫標識

foo.getId().equals( bar.getId() )

JVM 標識

foo==bar

對於那些關聯到 特定Session (也就是在單個Session的范圍內)上的對象來說,這 兩種標識的語義是等價的,與數據庫標識對應的JVM標識是由Hibernate來保 證的。不過,當應用程序在兩個不同的session中並發訪問具有同一持久化標 識的業務對象實例的時候,這個業務對象的兩個實例事實上是不相同的(從 JVM識別來看)。這種沖突可以通過在同步和提交的時候使用自動版本化和樂 觀鎖定方法來解決。

這種方式把關於並發的頭疼問題留給了Hibernate和數據庫;由於在單個線程內,操作單元中的對象識別不 需要代價昂貴的鎖定或其他意義上的同步,因此它同時可以提供最好的可伸縮性。只要在單個線程只持有一個 Session,應用程序就不需要同步任何業務對象。在Session 的范圍內,應用程序可以放心的使用==進行對象比較。

不過,應用程序在Session的外面使用==進行對象比較可能會 導致無法預期的結果。在一些無法預料的場合,例如,如果你把兩個脫管對象實例放進同一個 Set的時候,就可能發生。這兩個對象實例可能有同一個數據庫標識(也就是說, 他們代表了表的同一行數據),從JVM標識的定義上來說,對脫管的對象而言,Hibernate無法保證他們 的的JVM標識一致。開發人員必須覆蓋持久化類的equals()方法和 hashCode() 方法,從而實現自定義的對象相等語義。警告:不要使用數據庫標識 來實現對象相等,應該使用業務鍵值,由唯一的,通常不變的屬性組成。當一個瞬時對象被持久化的時 候,它的數據庫標識會發生改變。如果一個瞬時對象(通常也包括脫管對象實例)被放入一 個Set,改變它的hashcode會導致與這個Set的關系中斷。雖 然業務鍵值的屬性不象數據庫主鍵那樣穩定不變,但是你只需要保證在同一個Set 中的對象屬性的穩定性就足夠了。請到Hibernate網站去尋求這個問題更多的詳細的討論。請注意,這不是一 個有關Hibernate的問題,而僅僅是一個關於Java對象標識和判等行為如何實現的問題。

12.1.4.常見問題

決不要使用反模式session-per-user-session或者 session-per-application(當然,這個規定幾乎沒有例外)。請注意, 下述一些問題可能也會出現在我們推薦的模式中,在你作出某個設計決定之前,請務必理解該模式的應用前提。

Session 是一個非線程安全的類。如果一個Session 實例允許共享的話,那些支持並發運行的東東,例如HTTP request,session beans,或者是 Swing workers,將會導致出現資源爭用(race condition)。如果在HttpSession中有 Hibernate 的Session的話(稍後討論),你應該考慮同步訪問你的Http session。 否則,只要用戶足夠快的點擊浏覽器的“刷新”,就會導致兩個並發運行線程使用同一個 Session。

一個由Hibernate拋出的異常意味著你必須立即回滾數據庫事務,並立即關閉Session (稍後會展開討論)。如果你的Session綁定到一個應用程序上,你必 須停止該應用程序。回滾數據庫事務並不會把你的業務對象退回到事務啟動時候的狀態。這 意味著數據庫狀態和業務對象狀態不同步。通常情況下,這不是什麼問題,因為異常是不可 恢復的,你必須在回滾之後重新開始執行。

Session 緩存了處於持久化狀態的每個對象(Hibernate會監視和檢查髒數據)。 這意味著,如果你讓Session打開很長一段時間,或是僅僅載入了過多的數據, Session占用的內存會一直增長,直到拋出OutOfMemoryException異常。這個 問題的一個解決方法是調用clear() 和evict()來管理 Session的緩存,但是如果你需要大批量數據操作的話,最好考慮 使用存儲過程。在第14章 批量處理(Batch processing)中有一些解決方案。在用戶會話期間一直保持 Session打開也意味著出現髒數據的可能性很高。

12.2.數據庫事務聲明

數據庫(或者系統)事務的聲明總是必須的。在數據庫事務之外,就無法和數據庫通訊(這可能會讓那些習慣於 自動提交事務模式的開發人員感到迷惑)。永遠使用清晰的事務聲明,即使只讀操作也是如此。進行 顯式的事務聲明並不總是需要的,這取決於你的事務隔離級別和數據庫的能力,但不管怎麼說,聲明事務總歸有益無害。

一個Hibernate應用程序可以運行在非托管環境中(也就是獨立運行的應用程序,簡單Web應用程序, 或者Swing圖形桌面應用程序),也可以運行在托管的J2EE環境中。在一個非托管環境中,Hibernate 通常自己負責管理數據庫連接池。應用程序開發人員必須手工設置事務聲明,換句話說,就是手工啟 動,提交,或者回滾數據庫事務。一個托管的環境通常提供了容器管理事務,例如事務裝配通過可聲 明的方式定義在EJB session beans的部署描述符中。可編程式事務聲明不再需要,即使是 Session 的同步也可以自動完成。

讓持久層具備可移植性是人們的理想。Hibernate提供了一套稱為Transaction的封裝API, 用來把你的部署環境中的本地事務管理系統轉換到Hibernate事務上。這個API是可選的,但是我們強烈 推薦你使用,除非你用CMT session bean。

通常情況下,結束 Session 包含了四個不同的階段:

同步session(flush,刷出到磁盤)

提交事務

關閉session

處理異常

session的同步(flush,刷出)前面已經討論過了,我們現在進一步考察在托管和非托管環境下的事務聲明和異常處理。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved