使用 Spring 重试的顽固 StaleObjectStateException 问题

Recalcitrant StaleObjectStateException problem using Spring Retry

我正在使用 Spring 重试来处理在出现意外小错误的情况下重试给定工作方法的问题。这是我简化的当前代码:

public class WorkerClass {
    @Autowired
    protected MyJpaRepository myJpaRepository;

    @Retryable(
        value = {Exception.class}, maxAttempts=3, backoff=@Backoff(5000))
    public void doSomething(RandomDTO dto) throws Exception {
        boolean success = false;
        try {
            // perform the task
            success = true;
        }
        catch (Exception e) {
            LOGGER.error("Something went wrong");
        }

        if (!success) {
            // create logEntity for failure using DTO
            MyEntity entity = myJpaRespository.save(logEntity);
            // update DTO using auto generated ID from entity
            throw new Exception("Give it another try!");
        }
        else {
            // create logEntity for success using DTO
            myJpaRespository.save(logEntity);
        }
    }

    @Recover
    public void recover(Exception ex, RandomDTO dto) {
        LOGGER.error("fatal error; all retries failed");
            // create logEntity for failure using DTO
        myJpaRespository.save(logEntity); // EXCEPTION OCCURS HERE
    }
}

我观察到 doSomething() 方法的初始尝试和所有后续尝试均已完成且没有错误。但是,当调用 recover() 方法时,尝试写入存储库时出现 JPA 异常。

堆栈跟踪包含:

optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

我对这个问题的理解是,出于某种原因,Hibernate 对 doSomething() 方法中创建的实体保持锁定。然后,我们的 recover() 方法被 Spring 重试框架击中,更新失败,因为其他东西锁定了该记录。但是,我不明白为什么会发生这种情况。有人可以阐明这一点吗?

乐观锁定是一种在 JPA 中通过使用 @Version 注释字段启用的策略。这是一个很好的策略,因为它消除了使用悲观锁定方法引起的所有物理锁定问题。

对于你的情况,我有以下观察:

1)问题是插入到LOG类型table时引起的。我认为在这种情况下,应用程序永远不应包含任何更新逻辑,因此 @Version 已过时且不必要(即使这是您应用程序中使用的一般策略)。

2) 在数据库中保存日志的一般原则是 运行 保存在一个全新的事务中。我们不希望我们的程序(以及父事务)因为某些日志记录问题而失败。这就是为什么我会提出以下建议:

@Recover
@Transactional(propagation = PropagationType.REQUIRES_NEW)
public void recover(Exception ex, RandomDTO dto) {
    LOGGER.error("fatal error; all retries failed");
        // create logEntity for failure using DTO
    myJpaRespository.save(logEntity); // EXCEPTION OCCURS HERE
}