猿问

当更新同时运行时,乐观锁定不工作 Spring Data JPA

我无法使用 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> {

}

这是我的服务:


@Service

public 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\":\"johno@gmail.com\"}";

    String body2 = "{\"fistName\":\"John\",\"lastName\":\"Watkins\",\"email\":\"johno@gmail.com\"}";


}

当测试运行时,数据库中只有这条记录(使用内存中的 h2):插入用户(用户 ID,名字,姓氏,电子邮件,版本)值(1,'John','Oliver','johno@gmail. com', 1);


这些是日志。我注意到正在检查并在 sql 中设置版本,所以工作正常。事务结束时执行update语句,但两个事务都执行成功,无异常。


顺便说一句,我尝试覆盖存储库中的保存方法以添加 @Lock(LockModeType.OPTIMISTIC) 但没有任何改变。


蝴蝶不菲
浏览 113回答 2
2回答

慕斯709654

乐观锁定确保在加载和保存实体之间没有对实体进行任何其他更改。由于您的服务在保存实体之前立即加载它,因此另一个线程不太可能在这个短时间范围内干扰,这就是为什么只有让线程休眠时才会看到冲突的原因。如果您想将乐观锁定提供的保护扩展到数据库事务之外,您可以将先前加载的实体传递给客户端并返回,并保存它而无需再次加载:&nbsp; public User updateUser(User user) {&nbsp; &nbsp; &nbsp; return userRepository.save(user);&nbsp; }(这调用entityManager.merge(),它会自动检查版本)或者,如果您需要更精细地控制更新哪些字段,您可以传递这些字段和版本,并在保存时自行检查版本:&nbsp; public User updateUser(UserDto user) {&nbsp; &nbsp; &nbsp; User savedUser = userRepository.findById(user.getId());&nbsp; &nbsp; &nbsp; if (savedUser.getVersion() != user.getVersion()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new OptimisticLockingViolationException();&nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; savedUser.setName(user.getName());&nbsp; }

汪汪一只猫

您可以ExecutorService用来管理多线程并CyclicBarrier同步线程执行(或至少缩短线程之间的执行时间间隔)。我做了一个打电话给你的UserService班级的例子:存储库public interface UserRepository extends CrudRepository<User, Long> {&nbsp; @Lock(value = LockModeType.OPTIMISTIC)&nbsp; Optional<User> findById(Long id);}JUnit 测试用例// Create a Callable that updates the user&nbsp; public Callable<Boolean> createCallable(User user, int tNumber, CyclicBarrier gate) throws OptimisticLockingFailureException {&nbsp; &nbsp; return () -> {&nbsp; &nbsp; &nbsp; // Create POJO to update, we add a number to string fields&nbsp; &nbsp; &nbsp; User newUser = new User(user.getId(),&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; user.getFistName() + "[" + tNumber + "]",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; user.getLastName()&nbsp; + "[" + tNumber + "]",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; user.getEmail());&nbsp; &nbsp; &nbsp; // Hold on until all threads have created POJO&nbsp; &nbsp; &nbsp; gate.await();&nbsp; &nbsp; &nbsp; // Once CyclicBarrier is open, we run update&nbsp; &nbsp; &nbsp; User updatedUser = userService.updateUser(newUser);&nbsp; &nbsp; &nbsp; return true;&nbsp; &nbsp; };&nbsp; }&nbsp; @Test(expected = ObjectOptimisticLockingFailureException.class)&nbsp; public void userServiceShouldThrowOptimisticLockException() throws Throwable {&nbsp; &nbsp; final int threads = 2; // We need 2 threads&nbsp; &nbsp; final CyclicBarrier gate = new CyclicBarrier(threads); // Barrier will open once 2 threads are awaiting&nbsp; &nbsp; ExecutorService executor = Executors.newFixedThreadPool(threads);&nbsp; &nbsp; // Create user for testing&nbsp; &nbsp; User user = new User("Alfonso", "Cuaron", "alfonso@ac.com");&nbsp; &nbsp; User savedUser = userRepository.save(user);&nbsp; &nbsp; // Create N threads that calls to service&nbsp; &nbsp; List<Callable<Boolean>> tasks = new ArrayList<>();&nbsp; &nbsp; for(int i = 0; i < threads; i++) {&nbsp; &nbsp; &nbsp; tasks.add(createCallable(savedUser, i, gate));&nbsp; &nbsp; }&nbsp; &nbsp; // Invoke N threads&nbsp; &nbsp; List<Future<Boolean>> result = executor.invokeAll(tasks);&nbsp; &nbsp; // Iterate over the execution results to browse exceptions&nbsp; &nbsp; for (Future<Boolean> r : result) {&nbsp; &nbsp; &nbsp; try {&nbsp; &nbsp; &nbsp; &nbsp; Boolean temp = r.get();&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("returned " + temp);&nbsp; &nbsp; &nbsp; } catch (ExecutionException e) {&nbsp; &nbsp; &nbsp; &nbsp; // Re-throw the exception that ExecutorService catch&nbsp; &nbsp; &nbsp; &nbsp; throw e.getCause();&nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; }我们使用Callable,因为它可以抛出Exceptions,我们可以从中恢复ExecutorService。请注意,线程调用和保存语句之间的指令越多,它们不同步导致 OptimisticLockException 的可能性就越大。由于您将调用控制器,我建议增加线程数量以获得更好的机会。
随时随地看视频慕课网APP

相关分类

Java
我要回答