Spring中如何处理并发访问的事务

Ore*_*est 6 java spring spring-transactions spring-data

我有一种方法的服务:

    @Service
    public class DefaultTestService implements TestService {
        private static final Logger LOGGER = Logger.getLogger(DefaultTestService.class);
        @Autowired
        private TestRepository testRepository;

        @Transactional(readOnly = false, isolation = Isolation.SERIALIZABLE)
        @Override
        public void incrementAndGet(Long testModelId) {
            LOGGER.debug("Transaction is active: " + TransactionSynchronizationManager.isActualTransactionActive());
            final TestModel tm = testRepository.findOne(testModelId);
            if (tm != null) {
                LOGGER.debug("Updated " + testModelId + " from value: " + tm.getValue());
                tm.setValue(tm.getValue() + 1);
                testRepository.save(tm);
            } else {
                LOGGER.debug("Saved with id: " + testModelId);
                final TestModel ntm = new TestModel();
                ntm.setId(testModelId);
                testRepository.save(ntm);
            }
       }
    }
Run Code Online (Sandbox Code Playgroud)

我正在运行带有 2 个带有参数的并行调用配置的加特林testModelId = 1L。由于这些调用,我收到错误:

org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "test_model_pkey"
Run Code Online (Sandbox Code Playgroud)

我从日志中可以看到的是,两个调用同时进入了这个方法,并且每个打印的日志

"Saved with id: 1"
"Saved with id: 1"
Run Code Online (Sandbox Code Playgroud)

我假设在此方法上添加事务注释会阻止线路上的一个调用,testRepository.findOne(testModelId)直到其他调用完成其执行,但正如我从日志中看到的,它以不同的方式工作。

所以我的问题是,当出现并发访问时,事务如何工作?我该如何处理并发访问的情况?

ben*_*n75 4

事务意味着在事务边界内执行的对持久对象的所有修改将:

  • 在事务结束时提交(即所有修改都保存在数据库中)
  • 在事务结束时回滚(即没有任何修改保留在数据库中)仅此而已。

在这种情况下交易如何进行?

2 个线程之一到达事务末尾并成功提交。另一个线程到达事务末尾,但由于违反约束而未能提交,因此第二个事务以“回滚”状态终止。

为什么findOne第二笔交易没有被阻止?

原因很简单,尽管事务级别为 SERIALIZABLE,但没有要锁定的行。findOne在两个事务中都没有返回结果,并且没有任何内容被锁定(当然,如果第一个事务在第二个事务执行之前提交findOne:这是另一个故事)。

如何在特定情况下处理并发事务(即插入新行时违反 PK 约束)?

最常见的策略是让数据库将 id 分配给新行 - 借助序列 -

(作为一个实验,您可以尝试将隔离级别设置为 READ_UNCOMMITED,以便第二个事务可以读取第一个事务中未提交的更改。我不确定您是否注意到任何差异,因为如果在findOne第二个事务中在第一个事务之前执行testRepository.save(ntm);,它将仍然没有返回结果)

一般情况下如何处理并发修改导致的事务回滚?

这实际上取决于您的用例。基本上你可以选择:

  • 捕获异常并“重试”操作。
  • 向调用者抛出异常(可能向用户显示一条温和的错误消息)。

请注意,如果事务在回滚状态下终止:事务期间修改的持久对象图不会恢复到其原始状态。

请注意,使用隔离级别 SERIALIZABLE 可能会导致巨大的性能问题,并且通常仅用于关键和偶尔的事务。