鎖(locking)
業(yè)務(wù)邏輯的實現(xiàn)過程中,往往需要保證數(shù)據(jù)訪問的排他性。如在金融系統(tǒng)的日終結(jié)算 處理中,我們希望針對某個cut-off時間點的數(shù)據(jù)進行處理,而不希望在結(jié)算進行過程中 (可能是幾秒種,也可能是幾個小時),數(shù)據(jù)再發(fā)生變化。此時,我們就需要通過一些機制來保證這些數(shù)據(jù)在某個操作過程中不會被外界修改,這樣的機制,在這里,也就是所謂 的“鎖”,即給我們選定的目標(biāo)數(shù)據(jù)上鎖,使其無法被其他程序修改。 Hibernate支持兩種鎖機制:即通常所說的“悲觀鎖(Pessimistic Locking)” 和“樂觀鎖(Optimistic Locking)”。
一 :悲觀鎖(Pessimistic Locking)
悲觀鎖,正如其名,它指的是對數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù),以及來自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度,因此,在整個數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定 狀態(tài)。悲觀鎖的實現(xiàn),往往依靠數(shù)據(jù)庫提供的鎖機制(也只有數(shù)據(jù)庫層提供的鎖機制才能 真正保證數(shù)據(jù)訪問的排他性,否則,即使在本系統(tǒng)中實現(xiàn)了加鎖機制,也無法保證外部系 統(tǒng)不會修改數(shù)據(jù))。 一個典型的倚賴數(shù)據(jù)庫的悲觀鎖調(diào)用: select * from account where name=”Erica” for update 這條sql 語句鎖定了account 表中所有符合檢索條件(name=”Erica”)的記錄。 本次事務(wù)提交之前(事務(wù)提交時會釋放事務(wù)過程中的鎖),外界無法修改這些記錄。 Hibernate的悲觀鎖,也是基于數(shù)據(jù)庫的鎖機制實現(xiàn)。 下面的代碼實現(xiàn)了對查詢記錄的加鎖:

2

3

4

獲取數(shù)據(jù) query.setLockMode 對查詢語句中特定別名所對應(yīng)的記錄進行加鎖(我們?yōu)?TUser類指定了一個別名“user”),這里也就是對返回的所有user記錄進行加鎖。 觀察運行期Hibernate生成的SQL語句:?

?這里Hibernate通過使用數(shù)據(jù)庫的for update子句實現(xiàn)了悲觀鎖機制。 Hibernate的加鎖模式有:
? LockMode.NONE : 無鎖機制。
? LockMode.WRITE :Hibernate在Insert和Update記錄的時候會自動 獲取。
? LockMode.READ : Hibernate在讀取記錄的時候會自動獲取。
以上這三種鎖機制一般由Hibernate內(nèi)部使用,如Hibernate為了保證Update 過程中對象不會被外界修改,會在save方法實現(xiàn)中自動為目標(biāo)對象加上WRITE鎖。
?
? LockMode.UPGRADE :利用數(shù)據(jù)庫的for update子句加鎖。
? LockMode. UPGRADE_NOWAIT :Oracle的特定實現(xiàn),利用Oracle的for update nowait子句實現(xiàn)加鎖。
上面這兩種鎖機制是我們在應(yīng)用層較為常用的,加鎖一般通過以下方法實現(xiàn):
Criteria.setLockMode
Query.setLockMode
Session.lock
注意,只有在查詢開始之前(也就是Hiberate 生成SQL 之前)設(shè)定加鎖,才會 真正通過數(shù)據(jù)庫的鎖機制進行加鎖處理,否則,數(shù)據(jù)已經(jīng)通過不包含for update 子句的Select SQL加載進來,所謂數(shù)據(jù)庫加鎖也就無從談起。
二 :樂觀鎖(Optimistic Locking)
相對悲觀鎖而言,樂觀鎖機制采取了更加寬松的加鎖機制。悲觀鎖大多數(shù)情況下依 靠數(shù)據(jù)庫的鎖機制實現(xiàn),以保證操作最大程度的獨占性。但隨之而來的就是數(shù)據(jù)庫 性能的大量開銷,特別是對長事務(wù)而言,這樣的開銷往往無法承受。 如一個金融系統(tǒng),當(dāng)某個操作員讀取用戶的數(shù)據(jù),并在讀出的用戶數(shù)據(jù)的基礎(chǔ)上進 行修改時(如更改用戶帳戶余額),如果采用悲觀鎖機制,也就意味著整個操作過 程中(從操作員讀出數(shù)據(jù)、開始修改直至提交修改結(jié)果的全過程,甚至還包括操作 員中途去煮咖啡的時間),數(shù)據(jù)庫記錄始終處于加鎖狀態(tài),可以想見,如果面對幾 百上千個并發(fā),這樣的情況將導(dǎo)致怎樣的后果。 樂觀鎖機制在一定程度上解決了這個問題。樂觀鎖 大多是基于數(shù)據(jù)版本 (Version)記錄機制實現(xiàn)。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個版本標(biāo)識,在基于 數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個“version”字段來 實現(xiàn)。 讀取出數(shù)據(jù)時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提 交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對應(yīng)記錄的當(dāng)前版本信息進行比對,如果提交的數(shù)據(jù) 版本號大于數(shù)據(jù)庫表當(dāng)前版本號,則予以更新,否則認(rèn)為是過期數(shù)據(jù)。 對于上面修改用戶帳戶信息的例子而言,假設(shè) :
數(shù)據(jù)庫中帳戶信息表中有一個 version字段,當(dāng)前值為1;而當(dāng)前帳戶余額字段(balance)為$100。
1 :操作員A 此時將其讀出(version=1),并從其帳戶余額中扣除$50 ($100-$50)。
2?:?在操作員A操作的過程中,操作員B也讀入此用戶信息(version=1),并 從其帳戶余額中扣除$20($100-$20)。
3: 操作員A完成了修改工作,將數(shù)據(jù)版本號加一(version=2),連同帳戶扣 除后余額(balance=$50),提交至數(shù)據(jù)庫更新,此時由于提交數(shù)據(jù)版本大 于數(shù)據(jù)庫記錄當(dāng)前版本,數(shù)據(jù)被更新,數(shù)據(jù)庫記錄version更新為2。
4: 操作員B完成了操作,也將版本號加一(version=2)試圖向數(shù)據(jù)庫提交數(shù) 據(jù)(balance=$80),但此時比對數(shù)據(jù)庫記錄版本時發(fā)現(xiàn),操作員B提交的 數(shù)據(jù)版本號為2,數(shù)據(jù)庫記錄當(dāng)前版本也為2,不滿足“提交版本必須大于記 錄當(dāng)前版本才能執(zhí)行更新“的樂觀鎖策略,因此,操作員B 的提交被駁回。 這樣,就避免了操作員B 用基于version=1 的舊數(shù)據(jù)修改的結(jié)果覆蓋操作 員A的操作結(jié)果的可能。
從上面的例子可以看出,樂觀鎖機制避免了長事務(wù)中的數(shù)據(jù)庫加鎖開銷(操作員A 和操作員B操作過程中,都沒有對數(shù)據(jù)庫數(shù)據(jù)加鎖),大大提升了大并發(fā)量下的系 統(tǒng)整體性能表現(xiàn)。 需要注意的是,樂觀鎖機制往往基于系統(tǒng)中的數(shù)據(jù)存儲邏輯,因此也具備一定的局 限性,如在上例中,由于樂觀鎖機制是在我們的系統(tǒng)中實現(xiàn),來自外部系統(tǒng)的用戶 余額更新操作不受我們系統(tǒng)的控制,因此可能會造成臟數(shù)據(jù)被更新到數(shù)據(jù)庫中。在 系統(tǒng)設(shè)計階段,我們應(yīng)該充分考慮到這些情況出現(xiàn)的可能性,并進行相應(yīng)調(diào)整(如 將樂觀鎖策略在數(shù)據(jù)庫存儲過程中實現(xiàn),對外只開放基于此存儲過程的數(shù)據(jù)更新途 徑,而不是將數(shù)據(jù)庫表直接對外公開)。 Hibernate 在其數(shù)據(jù)訪問引擎中內(nèi)置了樂觀鎖實現(xiàn)。如果不用考慮外部系統(tǒng)對數(shù) 據(jù)庫的更新操作,利用Hibernate提供的透明化樂觀鎖實現(xiàn),將大大提升我們的 生產(chǎn)力。 Hibernate中可以通過class描述符的optimistic-lock屬性結(jié)合version 描述符指定。
現(xiàn)在,我們?yōu)橹笆纠械腡User加上樂觀鎖機制。
?
1. 首先為TUser的class描述符添加optimistic-lock屬性:











optimistic-lock屬性有如下可選取值:
? none 無樂觀鎖
? version 通過版本機制實現(xiàn)樂觀鎖
? dirty 通過檢查發(fā)生變動過的屬性實現(xiàn)樂觀鎖
? all 通過檢查所有屬性實現(xiàn)樂觀鎖
其中通過version實現(xiàn)的樂觀鎖機制是Hibernate官方推薦的樂觀鎖實現(xiàn),同時也 是Hibernate中,目前唯一在數(shù)據(jù)對象脫離Session發(fā)生修改的情況下依然有效的鎖機 制。因此,一般情況下,我們都選擇version方式作為Hibernate樂觀鎖實現(xiàn)機制。
2. 添加一個Version屬性描述符
代碼內(nèi)容

?2

?3

?4

?5

?6

?7

?8

?9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

注意version 節(jié)點必須出現(xiàn)在ID 節(jié)點之后。 這里我們聲明了一個version屬性,用于存放用戶的版本信息,保存在TUser表的 version字段中。 此時如果我們嘗試編寫一段代碼,更新TUser表中記錄數(shù)據(jù),如:
代碼內(nèi)容

2

3

4

5

6

7

8

每次對TUser進行更新的時候,我們可以發(fā)現(xiàn),數(shù)據(jù)庫中的version都在遞增。 而如果我們嘗試在tx.commit 之前,啟動另外一個Session,對名為Erica 的用 戶進行操作,以模擬并發(fā)更新時的情形:
代碼內(nèi)容

?2

?3

?4

?5

?6

?7

?8

?9

10

11

12

13

14

15

16

執(zhí)行以上代碼,代碼將在tx.commit()處拋出StaleObjectStateException異 常,并指出版本檢查失敗,當(dāng)前事務(wù)正在試圖提交一個過期數(shù)據(jù)。通過捕捉這個異常,我 們就可以在樂觀鎖校驗失敗時進行相應(yīng)處理。