【问题标题】:Issue with @Transactional and @Retryable@Transactional 和 @Retryable 的问题
【发布时间】:2021-04-21 14:10:40
【问题描述】:

如果我从 spring-retry 库中添加 @Retryable,我将无法在事务中执行数据库操作。这是我的代码结构的样子:

    public class ExpireAndSaveTrades {
    @Transactional(rollbackFor = MyException.class)
    public void expireAndSaveTrades(List<Trade> trades) {
        try {
            // these two MUST be executed in one transaction
            trades.forEach(trade -> dao.expireTrades(trade));
            dao.saveTrades(trades);
        } catch (Exception e) {
            throw new MyException(e.getMessage(), e);
        }
    }
}

public class Dao {
    @Retryable(value = CannotAcquireLockException.class,
            maxAttempts = 3,
            stateful = true,
            backoff = @Backoff(delay = 300, multiplier = 3))
    public void expireTrades(Trade trade) {
    try {
          tradeRepository.expire(trade.getId(), trade.getNewStopDate());
    } catch (CannotAcquireLockException e) {
          expireTrade(trade);
        }

    }

    @Retryable(value = CannotAcquireLockException.class,
            maxAttempts = 3,
            stateful = true,
            backoff = @Backoff(delay = 300, multiplier = 3))
    public void saveTrades(List<Trades> trades) {
    try {
          tradeRepository.saveAll(trades)
    } catch (CannotAcquireLockException e) {
              saveTrades(trades);
            }
    }
}

public interface TradeRepository extends JpaRepository<Trade, Integer> {
    @Modifying
    @Query(value = "update trade set stop_date=:new_stop_date where id=:id", nativeQuery = true)
    void expire(@Param("id") int id, @Param("new_stop_date") String newStopDate);
}

所以这就是我现在的位置:

  1. 不使用有状态(即有状态默认设置为false) - 重试成功,但在它结束时,我看到这个异常:org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only 并且多次重试后更新/保存的数据是在数据库表中回滚
  2. stateful = true - 不再发生重试

我浏览了许多 SO 帖子和博客,但找不到解决问题的方法。有人可以帮帮我吗?

编辑: 更新了我的问题以添加 try-catch 块这样,spring-retry 不会启动(我知道,因为我添加了一个侦听器到 @Retryable 以记录 retryContext .我没有看到日志被打印出来。如果有一个CannotAcquireLockException,事务也会默默地回滚

@Override
    public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
        LOGGER.info("Retry Context - {}", context);
    }

【问题讨论】:

  • 请用以下内容更新问题: 1. ExpireAndSaveTrades 类与它有何关联。 2.Daoclass'方法里面到底发生了什么(如果有其他类的调用,也请贴出来)
  • @amseager - 更新了问题,我正在使用 spring-data 进行数据库操作
  • 这能回答你的问题吗? Spring @Retryable with stateful Hibernate Object
  • 谢谢@crizzis - 我确实看过这篇文章..但我无法让它工作。让我再试试看
  • 使用有状态重试,只维护状态;您必须调用该方法,直到成功或重试用尽;看我的回答。

标签: java spring transactions spring-transactions spring-retry


【解决方案1】:

您正在事务中进行重试;这是错误的,会产生您所看到的结果;您需要交换它并在重试中执行事务。这就是为什么在不使用有状态时会出现回滚错误的原因。

如果使用有状态重试,@Retryable 所做的只是保持状态;重试对象的调用者必须继续调用,直到成功或重试耗尽。

编辑

这里是一个使用有状态重试的例子

@Component
class ServiceCaller {

    @Autowired
    Service service;

    public void call() {
        try {
            this.service.process();
        }
        catch (IllegalStateException e) {
            System.out.println("retrying...");
            call();
        }
        catch (RuntimeException e) {
            throw e;
        }
    }

}

@Component
class Service {

    @Autowired
    Retryer retryable;

    @Transactional
    public void process() {
        retryable.invoke();
    }

}

@Component
class Retryer {

    @Retryable(maxAttempts = 3, stateful = true)
    public void invoke() {
        System.out.println("Invoked");
        throw new IllegalStateException("failed");
    }

    @Recover
    public void recover(IllegalStateException e) {
        System.out.println("Retries exhausted");
        throw new RuntimeException(e);
    }

}
Invoked
retrying...
Invoked
retrying...
Invoked
retrying...
Retries exhausted
...
Caused by: java.lang.RuntimeException: java.lang.IllegalStateException: failed
    at com.example.demo.Retryer.recover(So67197577Application.java:84) ~[classes/:na]
...
Caused by: java.lang.IllegalStateException: failed

而且,没有@Recover 方法...

Invoked
retrying...
Invoked
retrying...
Invoked
retrying...
...
Caused by: org.springframework.retry.ExhaustedRetryException: Retry exhausted after last attempt with no recovery path; nested exception is java.lang.IllegalStateException: failed

【讨论】:

  • 不幸的是我不能交换它,为什么这样做是错误的?
  • 我没有完全阅读这个问题;我的解释是为什么你得到回滚异常。通过有状态重试,您的代码必须不断调用;没有自动重试调用;在您的情况下,expireAndSaveTrades 的调用者需要继续调用,直到成功或重试用尽。我编辑了答案。
  • 我加了一个例子。
  • 非常感谢。因此,在此示例中,您使用的是 @Retryable,但您没有指定此时要重试哪个异常。因此,一旦出现异常,控件将返回父类的 catch 块。简而言之,这也可以通过在父类中使用 @Retryable 来实现,即在 ServiceCaller 类中,这样您就不必编写 try/catch 块。
  • 我在这里试图实现的是,如果在 Retryer.invoke 方法中有多个数据库操作(比如正在发生 6 个数据库调用),并且假设最后一个数据库操作失败,而不是重试所有这些,我只是想重试特定的数据库调用以节省时间
猜你喜欢
  • 2018-02-14
  • 2021-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-07
  • 2019-03-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多