休眠事务是否异步提交更改,独立于程序流程?

问题描述

我有一个测试方法,在部署过程中有时会失败,有时不会。我从未见过它在我的本地失败。你可以在下面看到我的代码

我有以下从另一个服务异步调用的重试机制:

@Transactional
public boolean retry(Notificationorder order) {
    notificationService.send(order);
    return true;
}

public void resolveOnFailedAttempt(Long orderId) {  //automatically called if `retry` method fails
    notificationorderCommonTransactionsService.updateNotificationorderRetryCount(orderId);
}

通知服务是这样的:

@Service
@requiredArgsConstructor
public class NotificationServiceImpl implements NotificationService {

    private final NotificationorderCommonTransactionsService notificationorderCommonTransactionsService;

    @Override
    @Transactional
    public NotificationResponse send(Notificationorder order) {
        NotificationRequest request;
        try {
            request = prepareNotificationRequest(order);
        } catch (Exception e) {
            notificationorderCommonTransactionsService.saveNotificationorderErrorMessage(order.getId(),e.getMessage());
            throw e;
        }

        ...
 
        return response;
    }

        private void prepareNotificationRequest(Notificationorder order) {
            ...
            throw new Exception("ERROR");
        }
}


常见的交易服务是这样的:

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Notificationorder saveNotificationorderErrorMessage(Long orderId,String errorMessage) {
        Notificationorder order = notificationRepository.findOne(orderId);
        order.setErrorDescription(errorMessage);
        notificationRepository.save(order);
        return order;
    }

    public Notificationorder updateNotificationorderRetryCount(Long orderId) {
        Notificationorder order = notificationRepository.findOne(orderId);
        order.setRetryCount(order.getRetryCount() + 1);
        order.setorderStatus(NotificationorderStatus.IN_PROGRESS);
        notificationRepository.save(order);
        return order;
    }

这是我的集成测试:

    @Test
    public void test() {
 
        NotificationorderRequest invalidRequest = invalidRequest();

        ResponseEntity<NotificationorderResponse> responseEntity = send(invalidRequest);

        Notificationorder notificationorder = notificationorderRepository.findOne(1);

        softly.assertthat(notificationorder.getorderStatus().isEqualTo(NotificationorderStatus.IN_PROGRESS))
        softly.assertthat(notificationorder.getErrorDescription())
                 .isEqualTo("ERROR");  //This the line that fails.

        softly.assertthat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
    }

在测试方法中确认调用updateNotificationorderRetryCount,并且订单状态更新为IN_PROGRESS。但是,错误消息为空,我收到以下断言错误

    -- failure 1 --
    Expecting:
     <null>
    to be equal to:
     <"ERROR">
    but was not.

我希望在调用 saveNotificationorderErrorMessage 方法之前完成 updateNotificationorderRetryCount 事务并提交更改。但它似乎确实以这种方式工作。谁能帮我找出为什么我的代码会这样?

如何在本地重现此错误?我该怎么做才能解决它?

谢谢。

解决方法

尝试启用 SQL 日志记录和参数绑定日志记录并查看语句。我不知道您的所有代码,但也许您正在某处将消息设置为 null?也可能是,这些动作以某种方式交错,使得 updateNotificationOrderRetryCountsaveNotificationOrderErrorMessage 之前/同时以导致这种情况的方式被调用。如果两者都在提交之前运行,但 saveNotificationOrderErrorMessageupdateNotificationOrderRetryCount 之前提交,您可能会看到错误消息被 null 覆盖。

,

如果问题的代码片段是准确的,请注意您正在重新抛出 prepareNotificationRequest 方法中引发的异常这一事实,我假设是为了启用重试机制:

NotificationRequest request;
try {
    request = prepareNotificationRequest(order);
} catch (Exception e) {
    notificationOrderCommonTransactionsService.saveNotificationOrderErrorMessage(order.getId(),e.getMessage());
    throw e; // You are rethrowing the exception
}

对于您的评论,抛出的异常扩展了 RuntimeException

Spring documentation 所示:

在其默认配置中,Spring Framework 的事务基础结构代码仅在运行时、未检查异常的情况下才将事务标记为回滚。也就是说,当抛出的异常是 RuntimeException 的实例或子类时。 (默认情况下,错误实例也会导致回滚)。从事务方法抛出的已检查异常不会导致默认配置中的回滚。

可能 Spring 正在执行与 saveNotificationOrderErrorMessage 关联的初始事务的回滚。我意识到这个方法被注释为 @Transactional(propagation = Propagation.REQUIRES_NEW) 并且它 is initiating a new transaction,但也许问题可能与它有关。

当重试机制发生时,与方法updateNotificationOrderRetryCount的调用相关的另一个事务被执行,并且该事务被成功提交。这就是为什么在第二种方法中执行的更改正确提交的原因。

问题的解决方案将取决于您的重试机制是如何实现的,但是您可以,例如,引发原始异常,并且作为重试机制的第一步,在数据库中跟踪问题,或者引发已检查异常 - 默认情况下,Spring 不会为其执行回滚 - 并酌情处理。

更新

问题的另一个可能原因可能是 send 方法中的事务划分。

这个方法被注解为@Transactional。因此,Spring 将为其启动一个新事务。

发生错误,您在数据库中跟踪错误,在新事务中,但请注意初始事务仍然存在。

虽然在你的代码中没有描述,但在某种程度上,重试机制发生了,并更新了重试计数。如果此操作是在初始事务(或更高级别的事务)中执行的,由于事务边界、数据库隔离级别和相关内容,这个初始事务有可能从当前事务中获取实际过时但最新的交易边界的观点,NotificationOrder。而这个信息是最终被提交的信息,覆盖了错误信息。我希望你能明白。

一种简单的解决方案(可能适用于两种可能性)是将错误消息包含在 updateNotificationOrderRetryCount 方法本身中,从而将问题简化为单个事务:

/* If appropriate,mark it as Transactional */
@Transactional
public NotificationOrder updateNotificationOrderRetryCount(Long orderId,String errorMessage) {
  NotificationOrder order = notificationRepository.findOne(orderId);
  order.setRetryCount(order.getRetryCount() + 1);
  order.setOrderStatus(NotificationOrderStatus.IN_PROGRESS);
  order.setErrorDescription(errorMessage);
  // It is unnecessary,all the changes performed in the entity within the transaction will be committed
  // notificationRepository.save(order); 
  return order;
}