亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

當更新同時運行時,樂觀鎖定不工作 Spring Data JPA

當更新同時運行時,樂觀鎖定不工作 Spring Data JPA

蝴蝶不菲 2022-07-20 16:10:03
我無法使用 Spring Data JPA 在 Spring Boot 2 項目上獲得樂觀鎖定。我有一個在不同線程中運行 2 個簡單更新的測試,但它們都成功(沒有樂觀鎖異常),并且其中一個更新被另一個覆蓋。(請看底部的編輯)這是我的實體:@Entity@Table(name = "User")public class User {    @Column(name = "UserID")  @Id  @GeneratedValue(strategy = GenerationType.IDENTITY)  private Integer id;  @Column(name = "FirstName")  @NotBlank()  private String fistName;  @Column(name = "LastName")  @NotBlank  private String lastName;  @Column(name = "Email")  @NotBlank  @Email  private String email;  @Version  @Column(name = "Version")  private long version;  // getters & setters}這是我的存儲庫:public interface UserRepository extends JpaRepository<User, Integer> {}這是我的服務:@Servicepublic class UserService {  @Transactional(propagation = Propagation.REQUIRES_NEW)  public User updateUser(User user)        throws UserNotFoundException {    final Optional<User> oldUserOpt =  userRepository.findById(user.getId());    User oldUser = oldUserOpt            .orElseThrow(UserNotFoundException::new);        logger.debug("udpateUser(): saving user. {}", user.toString());        oldUser.setFistName(user.getFistName());        oldUser.setLastName(user.getLastName());        oldUser.setEmail(user.getEmail());        return userRepository.save(oldUser);          }}最后這是我的測試:@SpringBootTest@AutoConfigureMockMvc@RunWith(SpringRunner.class)@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)public class UserControllerIntegrationTest {  @Test  public void testConcurrentUpdate() throws Exception {    String body1 = "{\"fistName\":\"John\",\"lastName\":\"Doe\",\"email\":\"[email protected]\"}";    String body2 = "{\"fistName\":\"John\",\"lastName\":\"Watkins\",\"email\":\"[email protected]\"}";}當測試運行時,數據庫中只有這條記錄(使用內存中的 h2):插入用戶(用戶 ID,名字,姓氏,電子郵件,版本)值(1,'John','Oliver','johno@gmail. com', 1);這些是日志。我注意到正在檢查并在 sql 中設置版本,所以工作正常。事務結束時執行update語句,但兩個事務都執行成功,無異常。順便說一句,我嘗試覆蓋存儲庫中的保存方法以添加 @Lock(LockModeType.OPTIMISTIC) 但沒有任何改變。
查看完整描述

2 回答

?
慕斯709654

TA貢獻1840條經驗 獲得超5個贊

樂觀鎖定確保在加載和保存實體之間沒有對實體進行任何其他更改。由于您的服務在保存實體之前立即加載它,因此另一個線程不太可能在這個短時間范圍內干擾,這就是為什么只有讓線程休眠時才會看到沖突的原因。


如果您想將樂觀鎖定提供的保護擴展到數據庫事務之外,您可以將先前加載的實體傳遞給客戶端并返回,并保存它而無需再次加載:


  public User updateUser(User user) {

      return userRepository.save(user);

  }

(這調用entityManager.merge(),它會自動檢查版本)


或者,如果您需要更精細地控制更新哪些字段,您可以傳遞這些字段和版本,并在保存時自行檢查版本:


  public User updateUser(UserDto user) {

      User savedUser = userRepository.findById(user.getId());

      if (savedUser.getVersion() != user.getVersion()) {

          throw new OptimisticLockingViolationException();

      }

      savedUser.setName(user.getName());

  }


查看完整回答
反對 回復 2022-07-20
?
汪汪一只貓

TA貢獻1898條經驗 獲得超8個贊

您可以ExecutorService用來管理多線程并CyclicBarrier同步線程執行(或至少縮短線程之間的執行時間間隔)。


我做了一個打電話給你的UserService班級的例子:


存儲庫


public interface UserRepository extends CrudRepository<User, Long> {


  @Lock(value = LockModeType.OPTIMISTIC)

  Optional<User> findById(Long id);

}

JUnit 測試用例


// Create a Callable that updates the user

  public Callable<Boolean> createCallable(User user, int tNumber, CyclicBarrier gate) throws OptimisticLockingFailureException {

    return () -> {

      // Create POJO to update, we add a number to string fields

      User newUser = new User(user.getId(),

              user.getFistName() + "[" + tNumber + "]",

              user.getLastName()  + "[" + tNumber + "]",

              user.getEmail());


      // Hold on until all threads have created POJO

      gate.await();


      // Once CyclicBarrier is open, we run update

      User updatedUser = userService.updateUser(newUser);


      return true;

    };

  }


  @Test(expected = ObjectOptimisticLockingFailureException.class)

  public void userServiceShouldThrowOptimisticLockException() throws Throwable {

    final int threads = 2; // We need 2 threads

    final CyclicBarrier gate = new CyclicBarrier(threads); // Barrier will open once 2 threads are awaiting

    ExecutorService executor = Executors.newFixedThreadPool(threads);


    // Create user for testing

    User user = new User("Alfonso", "Cuaron", "[email protected]");

    User savedUser = userRepository.save(user);


    // Create N threads that calls to service

    List<Callable<Boolean>> tasks = new ArrayList<>();

    for(int i = 0; i < threads; i++) {

      tasks.add(createCallable(savedUser, i, gate));

    }


    // Invoke N threads

    List<Future<Boolean>> result = executor.invokeAll(tasks);


    // Iterate over the execution results to browse exceptions

    for (Future<Boolean> r : result) {

      try {

        Boolean temp = r.get();

        System.out.println("returned " + temp);

      } catch (ExecutionException e) {

        // Re-throw the exception that ExecutorService catch

        throw e.getCause();

      }

    }

  }

我們使用Callable,因為它可以拋出Exceptions,我們可以從中恢復ExecutorService。


請注意,線程調用和保存語句之間的指令越多,它們不同步導致 OptimisticLockException 的可能性就越大。由于您將調用控制器,我建議增加線程數量以獲得更好的機會。


查看完整回答
反對 回復 2022-07-20
  • 2 回答
  • 0 關注
  • 147 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號