Spring @事务和并发

Spring @Transactional and concurrency

我有一个 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 方法都会 return 正确的行数(目前它不会发生,因为多次调用可以运行并发)。

我试图找出最好的方法,我得出了这些结论(如果我错了请纠正我):

  1. 使用 @Transactional 没有帮助,因为多个事务仍然可以 运行 同时
  2. 无论如何,@Transactional 将阻止一个复杂的方法在 save 被多次调用时只保留一部分数据(这不是这种情况)
  3. isolation = Isolation.SERIALIZABLE 仅确定新事务与已完成事务的关系(在这种情况下,新事务将以最新形式查看数据库)

我读到我可以使用带 @Lock 的锁来独占访问 table,但我只看到它应用于单个方法(如 findById),而在我的例子中,锁必须跨越多个操作(countsave)。有没有办法任意锁定 table (使用注释或以编程方式)?锁定整个 table 可能看起来有点过激,但至少在不同 table 上运行的方法仍然可以同时 运行 (这不能仅通过在 synchronized 处使用来实现方法级别,虽然可以通过锁定存储库来实现,但这绝对不是一件干净的工作)。

Spring 没有提供一种简单的表达方式 "ehi, I want this method operating on this repository to run only from a thread at once",这听起来很奇怪。我错过了什么吗?

您关于 @Transactional 的看法是正确的,因为它涉及单个请求而不是多个请求。

您可以在此处选择几个选项:

  1. 通过编写原子查询将此 "locking" 移动到数据库层。例如:update tbl set tableNumber = tableNumber + 1 where id = X 将确保多个并发请求将 return 正确的值。在这种情况下,DB正在执行行级WRITE锁定以确保数据一致性。

  2. 使用锁。这就像在数据库中的 table 中设置一个标志一样简单。在任何线程开始此操作流程之前,它会检查标志是否为 set/unset。如果未设置,它会设置标志并继续。如果未设置,线程将等待并轮询该值,直到它读取到未设置的值。使用像 Redis 这样的东西是锁定实现的更好主意,因为在锁定到期时会回调。

使用锁更好,因为这样你就可以让一个线程在需要的时候一直保持锁(记住要有一个 max_timeout),并在这段时间内接触尽可能多的实体。