JPA 各種實體鎖模式的區(qū)別

原文地址

為了能夠同步訪問實體,JPA提供了2種鎖機制。這兩種機制都可以避免兩個事務中的其中一個,在不知情的情況下覆蓋另一個事務的數(shù)據(jù)。

通過實體鎖,我們通常希望避免在兩個并行事務中產(chǎn)生如下情形:

  1. Adam的事務讀取數(shù)據(jù) X
  2. Barbara的事務讀取數(shù)據(jù) X
  3. Adam的事務修改數(shù)據(jù) X,并將其修改為 XA
  4. Adam的事務寫入數(shù)據(jù) XA
  5. Barbara的事務修改數(shù)據(jù) X,并將其修改為 XB
  6. Barbara的事務寫入數(shù)據(jù) XB

結(jié)果是,Adam所做的修改完全被Barbara所覆蓋掉了,但是Barbara對此卻毫不知曉。像這樣的情況通常被稱為“臟讀”。顯然,我們希望的結(jié)果是Adam寫入 XA,而Barbara需要在寫入 XB之前檢查對 XA 的修改。

樂觀鎖的工作原理

樂觀鎖基于的假設(shè)是實際中沖突很少發(fā)生,即使發(fā)生,拋出一個錯誤也比想辦法避免它們更容易接受和簡單。在樂觀鎖中,允許一個事務正確完成,但另一個事務需要拋出異常并回滾,并且必須被重新執(zhí)行或者丟棄。

我們還以Adam和Barbara為例,下面是一個使用樂觀鎖可能發(fā)生的情形:

  1. Adam的事務讀取數(shù)據(jù) X
  2. Barbara的事務讀取數(shù)據(jù) X
  3. Adam的事務修改數(shù)據(jù) X,并將其修改為 XA
  4. Adam的事務寫入數(shù)據(jù) XA
  5. Barbara的事務修改數(shù)據(jù) X,并將其修改為 XB
  6. Barbara的事務試圖寫入數(shù)據(jù) XB,但是收到一個錯誤
  7. Barbara需要讀取數(shù)據(jù) XA(或者重新開始一個新的事務)
  8. Barbara的事務修改數(shù)據(jù) XA,并將其修改為 XAB
  9. Barbara的事務寫入數(shù)據(jù) XAB

如你所見,Barbara被強制要求檢查Adam的修改,并且她可以選擇繼續(xù)修改Adam的結(jié)果并保存(合并修改)。最后的數(shù)據(jù)將同時包括Adam和Barbara的修改。

樂觀鎖完全由JPA控制。它需要在DB表中額外存儲一個版本號列。它完全依靠于底層用來存儲關(guān)系型數(shù)據(jù)的DB引擎來工作。

悲觀鎖的工作原理

對于某些人來說,悲觀鎖更容易接受。當事務需要修改一個可能被其他事務同時修改的實體時,事務會發(fā)起一個命令將實體鎖住。所有的鎖會持續(xù)到事務結(jié)束后再自動釋放。

使用悲觀鎖的情形可能如下所示:

  1. Adam的事務讀取數(shù)據(jù) X
  2. Adam的事務鎖住 X
  3. Barbara的事務希望讀取數(shù)據(jù) X,但是因為 X 已經(jīng)被鎖住,只好等待
  4. Adam的事務修改數(shù)據(jù) X,并將其修改為 XA
  5. Adam的事務寫入數(shù)據(jù) XA
  6. Barbara的事務讀取數(shù)據(jù) XA
  7. Barbara的事務修改數(shù)據(jù) XA,并將其修改為 XAB
  8. Barbara的事務寫入數(shù)據(jù) XAB

如你所見,Barbara又一次被強制的寫入 XAB,同時也包含了Adam的修改。但是,這個方案與樂觀鎖完全不同——Barbara需要等待Adam的事務完成以后才能夠讀取數(shù)據(jù)。更甚的是,為了讓該場景正確工作,我們需要在兩個事務中都手動發(fā)起一個lock命令。(因為我們并不確定那個事務先運行,所以兩個事務都需要在修改數(shù)據(jù)前先進行鎖定)雖然樂觀鎖要為每個實體增加一個版本列,比悲觀鎖工作略多,但是之后我們不需要再在事務中發(fā)起鎖操作了。JPA會自動完成所有的檢查,我們只需要處理可能的異常即可。

悲觀鎖使用底層數(shù)據(jù)庫提供的鎖機制來鎖住表中已有的記錄。JPA需要知道如何觸發(fā)這些鎖,并且尚不能完全支持某些數(shù)據(jù)庫。

即使是JPA規(guī)范中也說到,不需要提供PESSIMISTIC_READ(因為許多DB只支持WRITE鎖):

允許JPA實現(xiàn)用LockModeType.PESSIMISTIC_WRITE來代替LockModeType.PESSIMISTIC_READ,但是反之不可。

JPA中可用的鎖類型

首先,我想說,對于實體中有添加了@Version注解的列,JPA會自動對該實體使用樂觀鎖。你不需要使用任何鎖命令。但是,你可以在任何時候發(fā)起一個以下類型的鎖:

  1. LockModeType.Optimistic
    1. 這就是默認的鎖類型。也是如ObjectDB所說通常被大家所忽略的鎖類型。在我的印象中,只有在需要動態(tài)獲取并傳遞鎖類型時,才會用到它,即使我們很清楚最后的鎖是OPTIMISTIC的。雖然這個例子不太恰當,但是一個好的API設(shè)計,即使是默認值也應該為其提供一個可選項。
    2. 示例:Java
LockModeType lockMode = resolveLockMode();
A a = em.find(A.class, 1, lockMode);
  1. LockModeType.OPTIMISTIC_FORCE_INCREMENT

    1. 這個選項很少被用到。但是如果你希望用另一個實體來鎖住對當前實體的引用,就需要使用它。換句話說,即使當前實體沒有被修改,但是其他實體可能因為當前實體被修改,你就可以用它來鎖住對當前實體的引用。
    2. 示例:
      1. 假設(shè)我們有兩個實體“書(Book)”和“書架(Shelf)”。我們可以將書添加到書架中,但是書不持有對其書架的引用。我們需要對所有移動書到其他書架的動作加鎖,以避免一本書被放在2個書架上。為了鎖住這個動作,光鎖住當前的書架實體是不夠的,因為書可能還沒有放到某個書架上。鎖住所有書架也不合理,因為他們在不同的事務中可能都是不同的。唯一合理的是鎖住書實體本身,即使在我們這個例子中它并沒有發(fā)生變化(因為它并不持有其書架的引用)。
  2. LockModeType.PESSIMISTIC_READ

    1. 這個模式類似于LockModeType.PESSIMISTIC_WRITE,但是有一點不同:如果沒有事務對實體加寫鎖,那么就不能阻塞對該實體的讀取。它還允許其他事務使用LockModeType.PESSIMISTIC_READ來加鎖。WRITE鎖和READ鎖之間的區(qū)別,已經(jīng)被這兩篇文章(here (ObjectDB)here (OpenJPA))很詳細的說明了。但是,不僅因為規(guī)范中允許,而且許多實現(xiàn)也沒有分開處理,所以該鎖模式經(jīng)常被等價于LockModeType.PESSIMISTIC_WRITE
  3. LockModeType.PESSIMISTIC_WRITE

    1. 這是LockModeType.PESSIMISTIC_READ的增強版。當WRITE鎖發(fā)生時,JPA在數(shù)據(jù)庫的幫助下,會阻止其他事務讀取實體,而不像READ鎖那樣只禁止寫入。
  4. LockModeType.PESSIMISTIC_FORCE_INCREMENT

    1. 這是另一個很少使用的鎖模式。但是,它可以用來結(jié)合PESSIMISTICOPTIMISTIC時使用。在以下場景中,單獨使用PESSIMISTIC_WRITE是無效的:

      1. 事務A使用樂觀鎖并讀取實體E
      2. 事務B請求實體E上的WRITE鎖
      3. 事務B提交并釋放E上的鎖
      4. 事務A更新E并提交
    2. 在步驟4中,如果事務B沒有增加版本列的值,那么就無法阻止事務A覆蓋B的修改。即使事務B使用的是悲觀鎖,鎖模式LockModeType.PESSIMISTIC_FORCE_INCREMENT也會強制事務B更新版本號,并讓事務A失敗并拋出OptimisticLockException。

為了發(fā)起一個指定類型的鎖,JPA提供了以下方法:

你可以使用JPA中這兩種鎖機制中的任意一種。如果需要,也可以選擇悲觀鎖類型PESSIMISTIC_FORCE_INCREMENT,把二者混起來用。

歡迎打賞(微信請點擊“閱讀原文”),也請關(guān)注微信公眾賬號“重度恐高癥”,精彩技術(shù)文章就在這里。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容