Quarkus:即使外部事务回滚也会更新数据库表

问题描述 投票:0回答:1

我们的大多数 Quarkus 端点都遵循标准做法,其中用

@Transactional
进行注释,调用我们的业务层,如果抛出异常,则回滚整个事务。

但是,对于这种场景,即使事务回滚,我们也需要执行数据库更新。我们的数据库是MySQL 8。

// Quarkus Resource class
@POST
@Transactional
@Path("/document/generate")
public void generateDocument() {
    documentGeneratorComponent.generateDocument(...);
}

我们最初的尝试是使用

@Transactional(REQUIRES_NEW)
来更新状态。我们遇到的问题是我们遇到了锁定超时异常,因为我相信外部事务和嵌套事务都在尝试更新相同的跟踪记录。

@ApplicationScoped
public class DocumentGeneratorComponent {


    public void generateDocument(...) {
        Long trackingId = null;
        try {
            trackingId = createTrackingRecord(DocGenStatus.STARTED);

            // error prone stuff that may throw
            var input = getInputData(...);
            docGenService.sendDocRequest(input);
        } catch (Exception ex) {
            if (trackingId != null) {
                updateStatusWithError(trackingId);
            }
            throw ex;
        }
    }

    // updates tracking record even if error
    @Transactional(REQUIRES_NEW)
    public void updateStatusWithError(var trackingId) {
        updateTrackingRecord(trackingId, DocGenStatus.EXCEPTION);
    }
}

最初,我们认为可以从资源层中删除

@Transactional
注释并在组件中处理事务。问题是我们业务层中的其他代码可能也需要生成文档,并且他们可能在其事务范围内执行此操作。

如果有一种简单的方法可以在回调中执行代码(如下所示),那将非常方便。在 Quarkus 中是否有执行此类操作的最佳实践?

public void generateDocument(...) {
    Long trackingId = null;
    try {
    } finally {
       transactionManager.onCurrentTransactionRollback(
            () -> updateStatusWithError(trackingId)
        );
    // ...
    }
// ```
}

我查看了

@TransactionScoped
beans,但是
@PreDestroy
被记录为在事务回滚之前被调用,并且存在一个 开放缺陷,其中执行时的行为似乎未定义或与文档不一致。

还有事务监听器,但使用起来似乎有点不方便,因为它们监听所有事务,而不是特定的事务。

void onAfterEndTransaction(@Observes @Destroyed(TransactionScoped.class) Object event) {
  // will be invoked for every transaction in the application, not just the code in question
}

推荐的方法是什么?谢谢!

quarkus jta
1个回答
0
投票

JPA有能力控制必须回滚事务的异常。

@Transactional
注释有两个属性:
rollbackOn
dontRollbackOn

根据文档

可以设置

dontRollbackOn
元素来指示不得导致拦截器将事务标记为回滚的异常。相反,可以设置
rollbackOn
元素来指示必须导致拦截器将事务标记为回滚的异常。当为这些元素中的任何一个指定类时,指定的行为也适用于该类的子类。如果指定了两个元素,则
dontRollbackOn
优先。

在我的解决方案中,不需要在资源层启动事务,但它也可以与其他事务一起使用。

@ApplicationScoped
public class DocumentGeneratorComponent {

    @Transactional(dontRollbackOn = DocumentException.class)
    public void generateDocument() {

        var trackingRecord = createTrackingRecord(DocGenStatus.STARTED);
        try {
            var input = getInputData(trackingRecord.id);
            // ...

            // It was missing in the question, but I assume it would be helpful 
            // to mark when the document generation finished successfully.
            trackingRecord.docGenStatus = DocGenStatus.FINISHED;
        } catch (DocumentException e) {
            trackingRecord.docGenStatus = DocGenStatus.EXCEPTION;
            throw e;
        }
    }

    @Transactional
    TrackingRecord createTrackingRecord(DocGenStatus status) {
        TrackingRecord record = new TrackingRecord();
        record.docGenStatus = status;
        record.persistAndFlush();
        return record;
    }

    String getInputDate(Long trackingRecordId) {
        if (null == trackingRecordId) {
            throw new DocumentException("Invalid tracking record (null).");
        }
        if (trackingRecordId % 2 == 0L) {
            return "FOO";
        }
        throw new DocumentException("Sometimes it is happen.");
    }

}

示例

TrackingRecord
实体是:

@Entity
public class TrackingRecord extends PanacheEntity {
    @Enumerated(EnumType.STRING)
    public DocGenStatus docGenStatus;
}

正如您所看到的

createTrackingRecord(...)
方法返回实体而不是其 id。该实体附加到持久性上下文。

现在,资源方法不再需要

@Transactional
注释。

@Path("/document")
public class DocumentResource {
    
    @Inject
    DocumentGeneratorComponent documentGeneratorComponent;

    @POST
    @Path("/generate")
    public void generateDocument() {
        documentGeneratorComponent.generateDocument();
    }
}

好的,但是其他商业服务呢

可能还需要生成文档,他们可能会在其交易范围内这样做。

也是可以的。

@ApplicationScoped
public class OtherBusinessService {

    @Inject
    DocumentGeneratorComponent documentGeneratorComponent;

    @Transactional(value = Transactional.TxType.REQUIRES_NEW)
    public void whateverBusinessMethod() {
        var entity = new CustomBusinessEntity();
        entity.name = "John Doe";
        entity.persist();
        try {
            documentGeneratorComponent.generateDocument();
            entity.documentGenerated = true;
        } catch (DocumentException e) {
            entity.documentGenerated = false;
        }
    }
}

OtherBusinessService.whateverBusinessMethod()
将同时存在
CustomBusinessEntity
TrackingRecord
实体。

重要的是

OtherBusinessService
或处理自己事务的任何其他业务方法必须通过以下方式处理特定异常:

  • 捕获外部业务方法中的异常并且不重新抛出或
  • 如果需要重新抛出,则将该异常添加到外部
    @Transactional
    注释的
    dontRollbackOn
    属性中。
© www.soinside.com 2019 - 2024. All rights reserved.