gpt4 book ai didi

java - 当更新同时运行时,乐观锁定不起作用 Spring Data JPA

转载 作者:行者123 更新时间:2023-12-03 22:30:56 25 4
gpt4 key购买 nike

我无法获得乐观锁定来使用 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\"}";

Runnable runnable1 = () -> {
try {
mvc.perform(put("/v1/users/1")
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("UTF-8")
.content(body1));
} catch (Exception e) {
System.out.println("exception in put " + e);
}
};

Runnable runnable2 = () -> {
try {
mvc.perform(put("/v1/users/1")
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("UTF-8")
.content(body2));
} catch (Exception e) {
System.out.println("exception in put " + e);
}
};

Thread t1 = new Thread(runnable1);
Thread t2 = new Thread(runnable2);

t1.start();
t2.start();
t1.join();
t2.join();

System.out.println("user after updates: " + userRepository.findById(1).get().toString());
}
}
当测试运行时,数据库中只有这条记录(使用内存中的 h2):
插入用户(用户 ID、名字、姓氏、电子邮件、版本)
值 (1, 'John', 'Oliver', 'johno@gmail.com', 1);
这些是日志。我注意到正在检查版本并在 sql 中设置,所以工作正常。事务结束时执行update语句,但两个事务都执行成功,也不异常(exception)。
顺便说一下,我尝试覆盖存储库中的 save 方法来添加 @Lock(LockModeType.OPTIMISTIC) 但没有任何改变。
[       Thread-4] c.u.i.service.UserService     : updateUser(): saving user. User{id=1, fistName='John', lastName='Doe', email='johno@gmail.com', version=1}
[ Thread-5] c.u.i.service.UserService : updateUser(): saving user. User{id=1, fistName='John', lastName='Watkins', email='johno@gmail.com', version=1}
[ Thread-5] o.s.t.i.TransactionInterceptor : Getting transaction for [com.company.app.service.UserService.updateUser]
[ Thread-4] o.s.t.i.TransactionInterceptor : Getting transaction for [com.company.app.service.UserService.updateUser]
[ Thread-4] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findById]
[ Thread-5] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findById]
[ Thread-4] org.hibernate.SQL : select user0_.UserID as Use1_3_0_, user0_.Email as Email2_3_0_, user0_.FirstName as FirstNam4_3_0_, user0_.LastName as LastName5_3_0_, user0_.Version as Version9_3_0_ from User user0_ where user0_.UserID=1
[ Thread-5] org.hibernate.SQL : select user0_.UserID as Use1_3_0_, user0_.Email as Email2_3_0_, user0_.FirstName as FirstNam4_3_0_, user0_.LastName as LastName5_3_0_, user0_.Version as Version9_3_0_ from User user0_ where user0_.UserID=1
[ Thread-5] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findById]
[ Thread-4] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findById]
[ Thread-5] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
[ Thread-4] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
[ Thread-4] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
[ Thread-5] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
[ Thread-4] o.s.t.i.TransactionInterceptor : Completing transaction for [com.company.app.service.UserService.updateUser]
[ Thread-5] o.s.t.i.TransactionInterceptor : Completing transaction for [com.company.app.service.UserService.updateUser]
[ Thread-5] org.hibernate.SQL : update User set Email=johno@gmail.com, FirstName=John, LastName=Watkins, Version=2 where UserID=1 and Version=1
[ Thread-4] org.hibernate.SQL : update User set Email=johno@gmail.com, FirstName=John, LastName=Doe, Version=2 where UserID=1 and Version=1
user after updates: User{id=1, fistName='John', lastName='Watkins', email='johno@gmail.com', version=2}
编辑:
我认为问题是因为插入是在完全相同的时间完成的。
我在调用 save() 之前在服务中添加了一些此代码:
double random = Math.random();
long wait = (long) (random * 500);
logger.debug("waiting {} ms", wait);
try {
Thread.sleep(wait);
} catch (InterruptedException e) {
e.printStackTrace();
}
使用这段代码,我总是得到 Optimistic Lock 异常,因为插入不是同时执行的。如果没有这个丑陋的解决方法,我永远不会得到异常(exception)。有没有办法解决这个问题? (除了这个解决方法)。或者我不应该担心这种情况发生在生产中?

最佳答案

乐观锁定确保在加载和保存实体之间没有对实体进行其他更改。由于您的服务在保存实体之前立即加载实体,因此另一个线程不太可能在这个短时间范围内进行干扰,这就是为什么只有在您让线程 hibernate 时才会看到冲突的原因。
如果您想将乐观锁提供的保护扩展到数据库事务之外,您可以将先前加载的实体传递给客户端并返回,并保存它而无需再次加载它:

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

关于java - 当更新同时运行时,乐观锁定不起作用 Spring Data JPA,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54986288/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com