2017-08-07 118 views
0

我使用JPA庫在我SpringBoot應用程序相同的行我有我的數據庫定期設置某些元素「準備好」 我也有一個用戶可以調用和端點的後臺任務可以修改同一個表的一行。JPA兩種事務更新

有沒有辦法來避免一個又一個相互抵消寫?就拿這個場景

Table: 
Key 
id  name  is_ready 

0)有初始數據的關鍵(1個NO_NAME假)

1)在後臺任務踢和即將通過設置

is_ready to true 
Key key = repo.findKeyByIsReady(false) 
key.setIsReady(true) 
repo.save(key) <--- does NOT yet execute this 

修改表的關鍵2)用戶調用api端點將密鑰名稱更改爲「new_name」並完成

3)現在後臺服務執行repo.save(key),最後的數據是

1 no_name true 

代替

1 new_name true 

基本上後臺任務已覆蓋鍵名用戶

是有辦法避免這種情況的設置?交易如何在這裏有所幫助?

回答

1

這是通過額外的鎖通常解決:

樂觀鎖

您檢測,該行被別人改變第二交易。然後,您要麼告訴用戶並要求手動修復或嘗試自動合併更改。

要實現它,你不得不額外列添加到表 - 版本。然後更新該行查詢時,會是這個樣子:

UPDATE ..., version=old_version+1 WHERE id=old_id and version=old_version 

如果WHERE接近沒有找到行(因爲別人遞增版本),更改的行數是0(JDBC得到這個來自數據庫的信息)和JPA會在這種情況下引發錯誤。

附加字段將必須被映射爲JPA @Version

悲觀鎖

每次更新的實體使用建築:

select ... for update 

當第二個交易等問題發言,DB的鎖請求,直到第一個事務完成。如果在特定時間內沒有發生這種情況,您會從JPA中獲得例外。有關更多信息,請參閱EntityManager#lock()方法。

0

有幾種方法來解決這個問題 - 而不是更新記錄的所有領域

  1. 更新只is_ready場

    • 在你的後臺任務,僅更新is_ready領域。

      • MySQL提供all standard database isolation levels
      • 您可以使用「SERIALIZABLE」 - 這樣的話,你會不會通過使用事務與數據庫更加嚴格的隔離級別覆蓋變化等領域
    • 鎖行隔離級別

    • 在此級別下,一旦啓動事務並讀取一行,在第一個事務提交(或回滾)之前,沒有其他事務可以更新同一行
    • 因此,在您的情況下,即使兩個進程都設法讀取相同舊狀態中的行,只有其中一個將能夠成功更新它。另一個進程將無法更新記錄(我沒有自己測試過,但它是如何在此隔離級別下工作的)
    • 您可以使用Spring的@Transactional註釋來標記隔離級別 - @Transactional(isolation=Isolation.SERIALIZABLE)
    • 請注意,使用這種嚴格的隔離級別時必須小心。涉及從多個表讀取的不正確用法可能導致死鎖