我有一个采用以下方法的Spring控制器:
@PostMapping("/tables")
@Transactional(isolation = Isolation.SERIALIZABLE)
public ResponseEntity<Table> addTable(@RequestBody Table table) {
if (table.getTableNumber() == 0) {
table.setTableNumber(tableRepository.count() + 1);
}
return new ResponseEntity<>(tableRepository.save(table), HttpStatus.CREATED);
}
我希望依次执行多个调用,以便在每次调用时,count
方法将返回正确的行数(此刻不发生,因为可以同时运行多个调用)。 / p>
我试图找出最好的方法,然后得出了这些结论(如果我错了,请纠正我):
@Transactional
并没有帮助,因为仍然可以同时运行多个事务@Transactional
将阻止复杂的方法在多次调用save
时仅持久保留一部分数据(不是这种情况)isolation = Isolation.SERIALIZABLE
仅确定新事务与已完成事务之间的关系(在这种情况下,新事务将以最新形式查看数据库)我读到我可以对@Lock
使用锁来独占表,但是我只看到它应用于单个方法(例如findById
),而在我看来,锁可以必须跨越多个操作(count
和save
)。有没有一种方法可以任意锁定表(使用批注或以编程方式)?锁定整个表似乎有些麻烦,但是至少在不同表上操作的方法仍然可以并发运行(仅通过在方法级别使用synchronized
才能实现,尽管可以通过锁定存储库来实现) ,但这绝对不是一件干净的工作)。
Spring并没有提供一种简单的方式来说“嗨,我希望在此存储库上运行的该方法只能一次从一个线程运行”,这听起来很奇怪。我想念什么吗?
答案 0 :(得分:1)
您对@Transactional
的看法是正确的,因为它涉及单个请求,而不涉及多个请求。
您在这里有一些可用的选择:
通过编写原子查询将这种“锁定”移动到数据库层。例如:update tbl set tableNumber = tableNumber + 1 where id = X
将确保多个并发请求将返回正确的值。在这种情况下,数据库将执行行级WRITE锁定以确保数据一致性。
使用锁。这就像在数据库的表中设置标志一样简单。在任何线程开始此操作流程之前,它将检查标志是否已设置/取消设置。如果未设置,它将设置标志并继续。如果未设置,则线程将等待并轮询该值,直到读取未设置的值为止。使用Redis之类的东西来锁定实现是一个更好的主意,因为会在锁定到期时进行回调。
使用锁更好,因为这样您就可以使线程可以在需要的时间内保持锁的状态(记住拥有max_timeout
)并在此期间触摸所需的实体数。