@Transactional 和@Retryable 的问题
Issue with @Transactional and @Retryable
如果我从 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);
}
这就是我现在所在的位置:
- 不使用有状态(即默认情况下有状态设置为
false
)- 重试成功,但在它结束时,我看到这个异常:org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only
和数据是 updated/saved多次重试后数据库回滚table
- stateful = true - 不再发生重试
我浏览了很多 SO 帖子和博客,但找不到解决我的问题的方法。这里有人可以帮我吗?
编辑: 更新了我的问题以添加 try-catch 块有了这个 spring-重试不会启动(我知道因为我添加了一个监听器@Retryable
记录 retryContext
。我没有看到打印的日志。如果出现 CannotAcquireLockException
,事务也会自动回滚
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
LOGGER.info("Retry Context - {}", context);
}
您正在事务内进行重试;这是错误的,会产生你所看到的结果;您需要交换它并在重试中执行事务。这就是为什么在不使用有状态时会出现回滚错误的原因。
如果使用状态重试,@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
如果我从 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);
}
这就是我现在所在的位置:
- 不使用有状态(即默认情况下有状态设置为
false
)- 重试成功,但在它结束时,我看到这个异常:org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only
和数据是 updated/saved多次重试后数据库回滚table - stateful = true - 不再发生重试
我浏览了很多 SO 帖子和博客,但找不到解决我的问题的方法。这里有人可以帮我吗?
编辑: 更新了我的问题以添加 try-catch 块有了这个 spring-重试不会启动(我知道因为我添加了一个监听器@Retryable
记录 retryContext
。我没有看到打印的日志。如果出现 CannotAcquireLockException
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
LOGGER.info("Retry Context - {}", context);
}
您正在事务内进行重试;这是错误的,会产生你所看到的结果;您需要交换它并在重试中执行事务。这就是为什么在不使用有状态时会出现回滚错误的原因。
如果使用状态重试,@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