簡體   English   中英

Spring data - 啟用樂觀鎖定

[英]Spring data - enable optimistic locking

注意:我不需要關於樂觀鎖定的解釋。 這個問題是關於使用樂觀鎖定時似乎特定的 Spring Data 行為。


根據 jpa 規范,只要實體具有@Version注釋字段,就應該在實體上自動啟用樂觀鎖定。

如果我在使用存儲庫的彈簧數據測試項目中執行此操作,則鎖定似乎沒有被激活。 事實上,在進行不可重復讀取測試時不會拋出OptimisticLockException (請參閱 JPA 規范第 93 頁上的 P2)

但是,從 spring 文檔中我看到,如果我們使用@Lock(LockModeType.OPTIMISTIC)注釋單個方法,那么底層系統會正確拋出一個OptimisticLockException (然后由 spring 捕獲並以稍微不同的形式向上傳播)。

這是正常的還是我錯過了什么? 我們是否有義務注釋我們所有的方法(或創建一個獲取鎖的基本存儲庫實現)以使用 spring 數據啟用樂觀行為?

我在 spring boot 項目 1.4.5 的上下文中使用 spring 數據。

考試:

public class OptimisticLockExceptionTest {

    static class ReadWithSleepRunnable extends Thread {

        private OptimisticLockExceptionService service;

        private int id;

        UserRepository userRepository;

        public ReadWithSleepRunnable(OptimisticLockExceptionService service, int id, UserRepository userRepository) {
            this.service = service;
            this.id = id;
            this.userRepository = userRepository;
        }

        @Override
        public void run() {
            this.service.readWithSleep(this.userRepository, this.id);
        }

    }

    static class ModifyRunnable extends Thread {

        private OptimisticLockExceptionService service;

        private int id;

        UserRepository userRepository;

        public ModifyRunnable(OptimisticLockExceptionService service, int id, UserRepository userRepository) {
            this.service = service;
            this.id = id;
            this.userRepository = userRepository;
        }

        @Override
        public void run() {
            this.service.modifyUser(this.userRepository, this.id);
        }

    }

    @Inject
    private OptimisticLockExceptionService service;

    @Inject
    private UserRepository userRepository;

    private User u;

    @Test(expected = ObjectOptimisticLockingFailureException.class)
    public void thatOptimisticLockExceptionIsThrown() throws Exception {

        this.u = new User("email", "p");
        this.u = this.userRepository.save(this.u);

        try {
            Thread t1 = new ReadWithSleepRunnable(this.service, this.u.getId(), this.userRepository);
            t1.start();
            Thread.sleep(50);// To be sure the submitted thread starts
            assertTrue(t1.isAlive());
            Thread t2 = new ModifyRunnable(this.service, this.u.getId(), this.userRepository);
            t2.start();
            t2.join();
            assertTrue(t1.isAlive());
            t1.join();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

測試服務:

@Component
public class OptimisticLockExceptionService {

    @Transactional
    public User readWithSleep(UserRepository userRepo, int id) {

        System.err.println("started read");
        User op = userRepo.findOne(id);
        Thread.currentThread();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.err.println("read end");
        return op;

    }

    @Transactional
    public User modifyUser(UserRepository userRepo, int id) {

        System.err.println("started modify");
        User op = userRepo.findOne(id);

        op.setPassword("p2");

        System.err.println("modify end");
        return userRepo.save(op);

    }
}

存儲庫:

@Repository
public interface UserRepository extends CrudRepository<User, Integer> {
}

Spring Data JPA的樂觀鎖由所使用的JPA實現實現。

您指的是JPA規范第93頁的P2 本節開始於:

如果事務T1調用版本對象上的lock(entity, LockModeType.OPTIMISTIC) ,則實體管理器必須確保不會發生以下兩種現象:

但是您的測試不會創建這種情況。 方法lock永遠不會被調用。 因此,不會發生任何相關的鎖定。 特別是僅加載實體並不會對其調用lock

當人們修改一個對象時,事情發生了變化(第93頁的第二篇,但該規范的最后一段):

如果以其他方式更新或刪除了版本控制的對象,則即使未對EntityManager.lock進行任何顯式調用,實現也必須確保滿足LockModeType.OPTIMISTIC_FORCE_INCREMENT的要求。

注意:您正在使用相同的存儲庫生成兩個線程,從而使它們使用相同的EntityManager 我懷疑EntityManager是否支持此功能,而且我不確定您是否真的以這種方式進行了兩次交易,但這又是一個問題。

回答你的問題,不。 但是,如果您想使用您的實體並在 save() 之后需要新版本,那么可以,或者使用 flush()。 原因是樂觀鎖定是在事務的提交(或刷新)時確定的。

樂觀鎖背后的原因是為了防止從先前狀態更新表。 例如:

  1. 您獲得ID為1的用戶
  2. 另一個用戶更新ID為1的用戶並將其提交到新狀態
  3. 您更新用戶1(在步驟1中加載),然后嘗試將其提交到數據庫。

在這種情況下,在第3步中,您將覆蓋其他人在第2步中所做的更改,而您需要引發異常。

我相信spring是通過@version屬性實現的,該屬性對應於數據庫中的version列。 結果是這樣的:

update users set password="p2" where id=1 and version=1; 

我認為spring實際上使用字符串作為版本,但是我不確定。 也可能是時間戳記,但這是一般想法。

您不會得到異常,因為只有一個線程正在處理數據。 您在線程1中讀取它,而當前版本為1,然后在線程2中讀取它-版本仍為1。然后,當您嘗試保存它時,將休眠會話中的版本與數據庫中的版本進行比較,並且它們匹配-一切都井井有條,因此它將繼續正常運行。 嘗試將其設置為updateWithSleep(),則應獲取預期的異常。

您可以將樂觀鎖定策略設置為:

樂觀鎖定(可選-默認為版本):確定樂觀鎖定策略。

樂觀鎖定策略:版本:檢查版本/時間戳列,全部:檢查所有列,臟:檢查更改的列無:不使用樂觀鎖定

樂觀鎖定完全由Hibernate處理。

樂觀鎖概念

使用方案:在事務結束時很少進行並發更新時,檢查是否由任何其他事務更新

可以使用樂觀鎖定來處理並發更新。 樂觀鎖定通過檢查自讀取以來要更新的數據是否已被另一個事務更改來進行工作。 例如,您搜索了一條記錄,經過很長時間后,您將要修改該記錄,但與此同時,該記錄已被其他人更新。 實現樂觀鎖定的一種常用方法是在每個表中添加一個版本列,應用程序每次更改一行時都會增加該列。 每個UPDATE語句的WHERE子句都會檢查版本號自讀取以來是否未更改。 如果行已被另一個事務更新或刪除,則應用程序可以回滾該事務並重新開始。 樂觀鎖定之所以得名,是因為它假設並發更新很少,而應用程序會檢測並從中恢復,而不是阻止並發更新。 樂觀鎖模式僅在用戶嘗試保存更改時才檢測到更改,僅當重新開始對用戶沒有負擔時,它才能很好地起作用。 當實現用例時,由於不得不放棄幾分鍾的工作而使用戶非常惱火,一個更好的選擇是使用悲觀鎖。

@Version批注用作數據庫中的一列,應將其添加到每個實體以對該實體執行樂觀鎖定,例如

@Entity
public class User {
    @Version
    @Column(nullable = false)
    private Long version;
}

這樣可以確保不會使用錯誤的版本創建任何用戶。 這意味着您不能同時從多個來源更新用戶。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM