【发布时间】:2021-01-02 04:41:01
【问题描述】:
对于这个基于spring-boot-starter-data-jpa 依赖和H2 内存数据库的实验项目,我定义了一个带有两个字段(id 和firstName)的User 实体,并通过扩展声明了一个UsersRepository CrudRepository 接口。
现在,考虑一个提供两个端点的简单控制器:/print-user 读取同一个用户两次,间隔打印出其名字,/update-user 用于在两次读取之间更改用户的名字。请注意,我特意设置了Isolation.READ_COMMITTED 级别,并期望在第一个事务的过程中,通过相同 id 检索两次的用户将具有不同的名称。但相反,第一笔交易两次打印出相同的值。为了更清楚,这是完整的操作序列:
- 最初,
jeremy的名字设置为Jeremy。 - 然后我调用
/print-user,它会打印出Jeremy并进入睡眠状态。 - 接下来,我从另一个会话中调用
/update-user,它会将jeremy的名字更改为Bob。 - 最后,当第一个事务在睡眠后被唤醒并重新读取
jeremy用户时,它再次打印出Jeremy作为他的名字,即使名字已经更改为Bob(如果我们打开数据库控制台,它现在确实存储为Bob,而不是Jeremy)。
似乎设置隔离级别在这里没有效果,我很好奇为什么会这样。
@RestController
@RequestMapping
public class UsersController {
private final UsersRepository usersRepository;
@Autowired
public UsersController(UsersRepository usersRepository) {
this.usersRepository = usersRepository;
}
@GetMapping("/print-user")
@ResponseStatus(HttpStatus.OK)
@Transactional (isolation = Isolation.READ_COMMITTED)
public void printName() throws InterruptedException {
User user1 = usersRepository.findById("jeremy");
System.out.println(user1.getFirstName());
// allow changing user's name from another
// session by calling /update-user endpoint
Thread.sleep(5000);
User user2 = usersRepository.findById("jeremy");
System.out.println(user2.getFirstName());
}
@GetMapping("/update-user")
@ResponseStatus(HttpStatus.OK)
@Transactional(isolation = Isolation.READ_COMMITTED)
public User changeName() {
User user = usersRepository.findById("jeremy");
user.setFirstName("Bob");
return user;
}
}
【问题讨论】:
-
不应该
findById(....)返回Optional<User>吗? -
这里为了简单而省略了。
-
您可能尚未提交更新中的更改。您可能需要在设置后添加此行
usersRepository.save(user)。 -
当一个方法是事务性的,那么在该事务中检索到的实体处于托管状态,这意味着对它们所做的所有更改都将在事务结束时自动填充到数据库中。因此,
save()调用是多余的。 -
findById 看起来不对,应该是 findByFirstName 吗?
标签: java spring spring-boot spring-data-jpa spring-transactions