【问题标题】:Recalcitrant StaleObjectStateException problem using Spring Retry使用 Spring Retry 的顽固 StaleObjectStateException 问题
【发布时间】:2019-08-15 09:49:31
【问题描述】:

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

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 异常。

堆栈跟踪包含:

乐观锁定失败;嵌套异常是 org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction(或未保存值映射不正确)

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

【问题讨论】:

  • logEntity 从何而来。它在哪里创建/从数据库中获取?你能添加它的实体类吗?
  • 添加代码与问题并不真正相关。实体是使用 DTO(也包含主键 ID 字段)生成的。因此,每当我们需要使用 DTO 编写实体时,我们都会从头开始创建实体对象,然后使用 JPA 存储库保存它。这种方法几乎有效,recover() 方法除外。
  • @Maciej 你知道有什么方法可以告诉 Hibernate/JPA 停止跟踪给定的实体吗?
  • 当您的实体上有@Version 字段时,会管理乐观锁定。这就是为什么我对那个实体的定义感兴趣..
  • 是的,有一个带有@Version注解的字段。为什么这在这里很重要?

标签: spring hibernate spring-retry


【解决方案1】:

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

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

1) 问题是插入到LOG类型表时引起的。我认为在这种情况下,应用程序不应该包含任何更新逻辑,因此 @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
}

【讨论】:

  • 虽然这不能完全解决我的问题,但您关于 @Version 是罪魁祸首的建议确实是正确的,并引导我找到了正确的解决方案。确切的问题是我试图保留一个实体,其 @Version 值与 Hibernate 缓存/数据库中已有的内容不同步。解决方法是始终确保版本反映最新修订。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-07-23
  • 1970-01-01
  • 2013-06-29
  • 1970-01-01
  • 2018-10-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多