JPA 樂觀鎖教程

JPA 樂觀鎖

在企業級應用中,管理對數據庫資源的併發訪問至關重要。這意味着我們應該能夠以一種有效且最重要防錯方式處理多個事務。
更重要的是,我們需要確保數據在併發讀取和更新時的一致性。爲此,我們可以使用Java Persistence API提供的樂觀鎖機制。其實現同一時間對同一數據進行多次更新不會相互干擾。

理解樂觀鎖

樂觀鎖是不是數據庫提供的機制。這裏通過在實體中增加一個帶有@Version註解的屬性實現樂觀鎖。在實際使用過程中每個事務讀取該屬性的值,事務更新數據之前,再次檢查該屬性的值。如果該值此時已經改變則拋出OptimisticLockException 異常,否則事務提交更新並版本屬性加一。

悲觀鎖 VS 樂觀鎖

與樂觀鎖相對應,JPA也提供了另一種處理併發訪問機制——悲觀鎖。下面簡要說明兩種直接差異以及各種優勢。

如前面示例所示,樂觀鎖是基於檢測實體中版本屬性變化機制。如果併發更新發生,拋出OptimisticLockException 異常,然後我們可以重新嘗試跟新數據。

可以想象這種機制比較適合應用中讀操作遠多於更新或刪除操作。而且在實體必須分離一段時間且不能持有鎖的情形下很有用。

相反,悲觀鎖機制是在數據庫級鎖定實體。每個事務可以獲得數據鎖,只要其持有鎖,其他事務不能對該數據進行讀操作,刪除或任何更新操作。我們可以推斷使用悲觀鎖定可能導致死鎖。但是確保了比樂觀鎖定更好的數據完整性。

版本屬性

版本屬性是帶有 @Version註解的屬性。對啓用樂觀鎖這是必須,請看示例:

@Entity
public class Student {

    @Id
    private Long id;

    private String name;

    private String lastName;

    @Version
    private Integer version;

    // getters and setters

}

申明版本屬性時需遵守幾個規則:

  • 每個實體必須只能有一個版本屬性
  • 當一個實體映射到多個表時,版本屬性必須在主表中
  • 版本屬性的類型必須是這幾種類型之一:int, Integer, long, Long, short, Short, java.sql.Timestamp

我們知道我們可以通過實體獲取版本屬性的值,但爲確保數據一致性,我們不能更新或增加它,僅持久化提供者可以。

值得注意的是,持久性提供者能夠支持沒有版本屬性的實體的樂觀鎖。但在使用樂觀鎖定,最好總是包含版本屬性。如果我們試圖鎖定一個不包含該屬性的實體,而持久性提供程序又不支持它,則會導致一個PersitenceException。

鎖模式

JPA提供我們兩種不同的樂觀鎖模式(以及兩個別名):

  • OPTIMISTIC – 所有包含版本屬性的實體獲得樂果讀鎖
  • OPTIMISTIC_FORCE_INCREMENT – 與OPTIMISTIC一樣獲得鎖並自動增加版本屬性的值
  • READ – OPTIMISTIC 的同義詞
  • WRITE – OPTIMISTIC_FORCE_INCREMENT的同義詞
    上述所有類型在LockModeType 中有完整定義.

OPTIMISTIC (READ)

我們知道OPTIMISTIC以及Read鎖,兩者是同義詞。但JPA規範建議我們在新應用中使用OPTIMISTIC。每當我們請求OPTIMISTIC模式時,持久性提供程序將阻止數據進行髒讀和不可重複讀。
簡單地說,它確保應該在下面情況下任何事務不能提交:
* 已經更新或刪除但未提交
* 已經同時成功地更新或刪除

OPTIMISTIC_INCREMENT (WRITE)

與前面一樣,OPTIMISTIC_INCREMENT和WRITE也是同義詞,但是前者更可取。
OPTIMISTIC_INCREMENT必須滿足與,OPTIMISTIC模式相同的條件。此外,它增加了version屬性的值。然而,它並沒有明確規定是否應該立即完成,或者是否應該推遲到提交或刷新。

需要注意的是當請求OPTIMISTIC模式時,持久性提供者是否提供OPTIMISTIC_INCREMENT功能。

使用樂觀鎖

對擁有版本屬性實體,缺省情況下樂觀鎖是有效的。但還有其他幾種方式顯示指定:

Find

通過給EntityManager的find方法傳遞適當的LockModeType作爲參數,可以獲取樂觀鎖:

entityManager.find(Student.class, studentId, LockModeType.OPTIMISTIC);

Query

另一種方法是通過Query對象的SetLockMode方法:

Query query = entityManager.createQuery("from Student where id = :id");
query.setParameter("id", studentId);
query.setLockMode(LockModeType.OPTIMISTIC_INCREMENT);
query.getResultList()

顯示指定鎖

通過調用EntityManager的lock方法顯示指定鎖:

Student student = entityManager.find(Student.class, id);
entityManager.lock(student, LockModeType.OPTIMISTIC);

Refresh

與之前一樣也可以調用refresh方法實現:

Student student = entityManager.find(Student.class, id);
entityManager.refresh(student, LockModeType.READ);

NamedQuery

最後一個使用@NamedQuery註解的lockMode屬性:

@NamedQuery(name="optimisticLock",
  query="SELECT s FROM Student s WHERE s.id LIKE :id",
  lockMode = WRITE)

OptimisticLockException

當JAP實現(持久化提供者)發現在實體上樂觀鎖衝突時,拋出OptimisticLockException異常。我們指定因爲異常導致活動事務總是回滾。

如何對OptimisticLockException做出反應是有必要的。爲了方便此異常包含對衝突實體的引用。然而,JAP實現(持久性提供者)並不是在每種情況下都必須提供它。無法保證對象是否可用。
因此,建議使用一種更好的方法來處理該異常。我們應該通過最好在新的事務中重新加載或刷新來重新檢索實體。然後嘗試再次更新。

總結

在本問中我們學習可以幫助處理併發事務的樂觀鎖工具,使用實體中包含的版本屬性來控制對它們的併發修改。因此,它確保任何更新或刪除不會被無意地覆蓋或丟失。與悲觀鎖定相反,它不會在數據庫級別上鎖定實體,因此不會引起DB級死鎖。

對擁有版本屬性的實體默認啓用了樂觀鎖。但也可以通過使用各種鎖模式類型顯式地指定樂觀鎖的其他幾種方法。
我們應該記住當實體上有衝突更新時,會出現OptimisticLockException異常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章