春季@交易和并发

时间:2020-05-24 13:13:28

标签: java spring jpa concurrency transactions

我有一个采用以下方法的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>

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

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

我读到我可以对@Lock使用锁来独占表,但是我只看到它应用于单个方法(例如findById),而在我看来,锁可以必须跨越多个操作(countsave)。有没有一种方法可以任意锁定表(使用批注或以编程方式)?锁定整个表似乎有些麻烦,但是至少在不同表上操作的方法仍然可以并发运行(仅通过在方法级别使用synchronized才能实现,尽管可以通过锁定存储库来实现) ,但这绝对不是一件干净的工作)。

Spring并没有提供一种简单的方式来说“嗨,我希望在此存储库上运行的该方法只能一次从一个线程运行”,这听起来很奇怪。我想念什么吗?

1 个答案:

答案 0 :(得分:1)

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

您在这里有一些可用的选择:

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

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

使用锁更好,因为这样您就可以使线程可以在需要的时间内保持锁的状态(记住拥有max_timeout)并在此期间触摸所需的实体数。