如何在Spring

时间:2016-01-16 22:16:44

标签: java spring spring-data spring-transactions

我用一种测试方法创建了服务:

    @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);
            }
       }
    }

我正在运行Gatling,使用testModelId = 1L对此方法进行两次并发访问(一次2次调用)。 由于这些电话,我收到了错误:

org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "test_model_pkey"

我从日志中可以看到,两个并发调用一次进入此方法并且每个打印日志

"Saved with id: 1"
"Saved with id: 1"

我假设在此方法上添加事务注释会阻止其中一个调用testRepository.findOne(testModelId),直到其他调用结束,但正如我从日志中看到的那样,它不能像那样工作。

所以我的问题是在出现并发访问时这种情况下交易是如何工作的?如何通过并发访问来处理这种情况?

2 个答案:

答案 0 :(得分:1)

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

  • 在交易结束时提交(即所有修改都保留在数据库中)
  • 在事务结束时回滚(即修改的保留在数据库中) 这就是全部。

在这种情况下交易如何运作?

两个线程中的一个到达事务的末尾并成功提交。另一个线程到达事务的末尾并且由于约束违规而无法提交,因此第二个事务终止于" rollback"状态。

为什么在第二次交易中findOne没有被屏蔽?

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

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

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

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

如何处理因一般的并发修改而导致的事务回滚?

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

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

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

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

答案 1 :(得分:0)

我遇到了类似的并行调用问题,我使用 ReentrantLock 解决了它。

这是使用您的代码的一个示例:

@Service
public class DefaultTestService {

  private final ReentrantLock lock = new ReentrantLock();

  @Autowired
  private TestRepository testRepository;

  @Transactional(readOnly = false, isolation = Isolation.SERIALIZABLE)
  public void incrementAndGet(Long testModelId) {
    lock.lock();
    try {
      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);
      }
    } finally {
      lock.unlock();
    }
  }
}