使用 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
}
我正在使用 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
}