java—hibernate事务是否独立于程序流异步提交更改?

bjp0bcyl  于 2021-07-24  发布在  Java
关注(0)|答案(2)|浏览(319)

我有一个测试方法,它有时在部署期间失败,有时不成功。我从未见过它在我的本地计算机上失败。你可以在下面看到我的代码。
我有以下从另一个服务异步调用的重试机制:

@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");
        }
}

commmon事务服务是这样的:

@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 . 但是,错误消息为null,我得到以下Assert错误:

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

我想 saveNotificationOrderErrorMessage 要完成的事务和要提交的更改 updateNotificationOrderRetryCount 方法被调用。但看起来确实是这样。有人能帮我找出为什么我的代码会这样吗?
如何在本地计算机上重现此错误?我能做些什么来修复它?
谢谢。

xoefb8l8

xoefb8l81#

尝试启用sql日志记录和参数绑定日志记录,并查看语句。我不知道你所有的代码,但也许你正在某处设置消息为空?也有可能是,这些行动以某种方式交织在一起 updateNotificationOrderRetryCount 在前/时调用 saveNotificationOrderErrorMessage 在某种程度上导致了这一点。如果两个都在提交之前运行,但是 saveNotificationOrderErrorMessage 之前提交 updateNotificationOrderRetryCount ,您可以看到错误消息被null覆盖。

3npbholx

3npbholx2#

如果问题的代码片段是准确的,请注意您正在重新引用 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文档所示:
在其默认配置中,spring框架的事务基础结构代码仅在运行时未检查异常的情况下将事务标记为回滚。也就是说,当抛出的异常是runtimeexception的示例或子类时默认情况下,错误示例也会导致回滚)。从事务方法引发的已检查异常不会导致默认配置中的回滚。
可能spring正在执行初始事务的回滚,该事务与 saveNotificationOrderErrorMessage . 我意识到这个方法被注解为 @Transactional(propagation = Propagation.REQUIRES_NEW) 它正在发起一项新的交易,但问题可能与此有关。
当重试机制发生时,另一个与方法调用相关联的事务 updateNotificationOrderRetryCount 已执行,并且此事务已成功提交。这就是在第二个方法中执行的更改被正确提交的原因。
问题的解决方案将取决于您的重试机制是如何实现的,但是您可以,例如,引发原始异常,作为重试机制的第一步,在数据库中跟踪问题,或者引发选中的异常(默认情况下,spring不会对其执行回滚),并根据需要进行处理。
更新
问题的另一个可能原因可能是 send 方法。
此方法注解为 @Transactional . 因此,spring将为它启动一个新的事务。
错误发生了,您可以在数据库中的新事务中跟踪错误,但请注意,初始事务仍然存在。
尽管代码中没有描述,但在某种程度上,重试机制会发生,并更新重试计数。如果此操作是在初始事务(或更高级别的事务)内执行的,由于事务边界、数据库隔离级别和相关内容,则此事务(初始事务)可能从事务边界的Angular 获取实际过时但当前的, 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;
}

相关问题