2 回答

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());
}

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 的可能性就越大。由于您將調用控制器,我建議增加線程數量以獲得更好的機會。
添加回答
舉報