本文是Hibernate和JPA中基于版本的樂觀并發控制的簡介。 這個概念已經很老了,上面已經寫了很多東西,但是無論如何我都看到了它被重新發明,誤解和濫用。 我在編寫它只是為了傳播知識,并希望引起人們對并發控制和鎖定的興趣。
用例
假設我們有一個供多個用戶使用的系統,其中每個實體可以由多個用戶修改。 我們希望避免兩個人加載一些信息,根據他們看到的內容做出一些決定并同時更新狀態的情況。 我們不希望丟失在第一個交易中首先單擊“保存”的用戶通過覆蓋它們所做的更改。
它也可能在服務器環境中發生–多個事務可以修改共享實體,我們希望避免出現以下情況:
- 交易1載入資料
- 事務2更新該數據并提交
- 使用在步驟1中加載的狀態(不再是當前狀態),事務1執行一些計算并更新狀態
在某些方面,它與不可重復的讀取具有可比性。
解決方案:版本控制
因此,Hibernate和JPA實現了基于版本的并發控制的概念。 運作方式如下。
您可以使用@Version
或<version>
(數字或時間戳)標記一個簡單的屬性。 這將是數據庫中的特殊列。 我們的映射如下所示:
@Entity
@Table(name = 'orders')
public class Order {@Idprivate long id;@Versionprivate int version;private String description;private String status;// ... mutators
}
當這樣的實體持續存在時,version屬性將設置為起始值。
每當更新時,Hibernate都會執行如下查詢:
update orders
set description=?, status=?, version=?
where id=? and version=?
請注意,在最后一行, WHERE
子句現在包括version
。 此值始終設置為“舊”值,因此只有在具有預期版本的情況下,它才會更新行。
假設有兩個用戶在版本1中加載訂單,并花一些時間在GUI中查看訂單。
安妮決定批準該訂單并執行該操作。 數據庫中的狀態已更新,一切正常。 傳遞給update語句的版本如下:
update orders
set description=?, status=?, version=2
where id=? and version=1
如您所見,在持久化更新持久層時,版本計數器將增加到2。
在她的GUI中,Betty仍然具有舊版本(編號1)。 當她決定對訂單執行更新時,該語句如下所示:
update orders
set description=?, status=?, version=2
where id=? and version=1
此時,在Anne的更新之后,數據庫中該行的版本為2。因此,第二次更新影響0行(沒有與WHERE
子句匹配的行)。 Hibernate會檢測到它,并檢測到一個org.hibernate.StaleObjectStateException
(包裝在javax.persistence.OptimisticLockException
)。
結果,第二個用戶除非刷新視圖,否則無法執行任何更新。 為了獲得適當的用戶體驗,我們需要進行一些干凈的異常處理,但是我將省略。
組態
這里幾乎沒有要自定義的內容。 @Version
屬性可以是數字或時間戳。 數字是人為的,但通常在內存和數據庫中占用較少的字節。 時間戳較大,但始終會更新為“當前時間戳”,因此您可以實際使用它來確定實體的更新時間。
為什么?
那為什么要使用它呢?
- 它提供了一種方便且自動化的方式來維持上述情況下的一致性。 這意味著每個動作只能執行一次,并且可以確保用戶或服務器進程在制定業務決策時看到最新狀態。
- 設置只需很少的工作。
- 由于其樂觀的性質,因此速度很快。 在任何地方都沒有鎖定,只有一個字段添加到同一查詢中。
- 在某種程度上,即使在已提交讀事務隔離級別的情況下,它也可以確保可重復讀。 它將以一個異常結束,但是至少不可能創建不一致的狀態。
- 它適用于非常長的對話,包括跨越多個事務的對話。
- 在ACID數據庫上的所有可能情況和競爭條件下,它都是完全一致的。 更新必須是順序更新,更新涉及行鎖定,而“第二”更新將始終影響0行并失敗。
演示版
為了演示這一點,我創建了一個非常簡單的Web應用程序。 它將Spring和Hibernate連接在一起(在JPA API后面),但是它也可以在其他設置中工作:Pure Hibernate(無JPA),具有不同實現的JPA,非webapp,非Spring等。
該應用程序保留一個具有與上述類似的架構的Order
,并以Web表單顯示該Order
,您可以在其中更新描述和狀態。 要嘗試并發控制,請在兩個選項卡中打開頁面,進行不同的修改并保存。 不使用@Version
嘗試相同的@Version
。
它使用嵌入式數據庫,因此需要最少的設置(僅Web容器),并且只需重新啟動即可從新數據庫開始。
這非常簡單-在@Transactional
@Controller
訪問EntityManager
并直接使用JPA映射的實體支持表單。 對于不太瑣碎的項目而言,這可能不是最好的處理方法,但是至少它將所有代碼集中在一個地方并且非常容易掌握。
可以在我的GitHub存儲庫中找到Eclipse項目的完整源代碼。
參考: 在我們的JCG合作伙伴 Konrad Garus的Squirrel博客上,JPA / Hibernate中基于版本的樂觀并發控制 。
翻譯自: https://www.javacodegeeks.com/2012/11/jpahibernate-version-based-optimistic-concurrency-control.html