9.5. 错误处理和回滚


虽然您可以在事务路由中使用标准 Apache Camel 错误处理技术,但了解异常和事务分离之间的交互非常重要。特别是,您需要考虑引发异常,通常会导致事务回滚。请参见以下主题:

9.5.1. 如何回滚事务

您可以使用以下方法之一来回滚事务:

9.5.1.1. 使用运行时例外来触发回滚

回滚 Spring 事务的最常见方法是抛出运行时(未检查)异常。换句话说,异常是 java.lang.RuntimeException 的一个实例或子类。java.lang.Error 类型的 Java 错误也会触发事务回滚。另一方面,检查异常不会触发回滚。

下图总结了触发回滚的灰色类如何影响事务的 Java 错误和例外。

Camel 例外
注意

Spring 框架也提供 XML 注解系统,您可以指定哪些例外,或者不应触发回滚。详情请查看 Spring 参考指南中的"回滚"。

警告

如果在事务中处理运行时异常,即在异常的几率前,与交易分离的代码不同,则不会回滚事务。详情请查看 第 9.5.2 节 “如何定义死信队列”

9.5.1.2. 使用 rollback() DSL 命令

如果要在转换路由中触发回滚,您可以通过调用 rollback() DSL 命令进行此操作,它会抛出 org.apache.camel.RollbackExchangeException 异常。换句话说,rollback() 命令使用标准方法抛出运行时异常来触发回滚。

例如,假设您决定对帐户服务应用中的资金传输大小有绝对限制。您可以在以下示例中使用代码超过 100 时触发回滚:

from("file:src/data?noop=true")
    .transacted()
    .bean("accountService","credit")
    .choice().when(xpath("/transaction/transfer[amount > 100]"))
        .rollback()
    .otherwise()
        .to("direct:txsmall");

from("direct:txsmall")
    .bean("accountService","debit")
    .bean("accountService","dumpTable")
    .to("file:target/messages/small");
注意

如果您在前面的路由中触发回滚,它将处于无限循环中。这样做的原因是,在路由开始时 回滚() 将回滚至 文件 端点来抛出 RollbackExchangeException 异常。文件组件具有内置可靠性功能,可导致它重新发送异常的交换。在重新发送课程后,交换仅会触发另一个回滚,从而导致死循环。下一个示例演示了如何避免这种无限循环。

9.5.1.3. 使用 markRollbackOnly() DSL 命令

markRollbackOnly() DSL 命令可让您强制当前事务回滚,而不抛出异常。在抛出异常的副作用时,这很有用,比如 第 9.5.1.2 节 “使用 rollback() DSL 命令” 中的示例。

以下示例演示了如何使用 markRollbackOnly() 命令替换 rollback() 命令来修改 第 9.5.1.2 节 “使用 rollback() DSL 命令” 中的示例。此版本的路由解决了无限循环的问题。在这种情况下,当资金传输量超过 100 时,将回滚当前交易,但不抛出异常。由于服务端点没有收到异常,它不会重试交换,且失败的事务会静默地丢弃。

以下代码会回滚一个带有 markRollbackOnly() 命令的一个例外:

from("file:src/data?noop=true")
    .transacted()
    .bean("accountService","credit")
    .choice().when(xpath("/transaction/transfer[amount > 100]"))
        .markRollbackOnly()
    .otherwise()
        .to("direct:txsmall");
...

前面的路由实施不是理想选择。虽然路由会完全回滚事务(使数据库处于一致状态),并避免无限循环处理,但它不会保留失败事务的任何记录。在现实应用中,您通常希望跟踪任何失败的事务。例如,您可能希望向相关客户写入一个字母,以说明事务未能成功的原因。跟踪失败事务的便捷方法是向路由添加一个死信队列。

9.5.2. 如何定义死信队列

要跟踪失败的事务,您可以定义 onException() 子句,它可让您将相关交换对象重新放入一个死信队列。但是,在事务上下文中使用时,您需要仔细了解如何定义 onException() 子句,因为异常处理和事务处理间的潜在的交互。下例演示了定义 onException() 子句的正确方法,假设您需要阻止重新行异常。

// Java
import org.apache.camel.builder.RouteBuilder;

public class MyRouteBuilder extends RouteBuilder {
    ...
    public void configure() {
        onException(IllegalArgumentException.class)
            .maximumRedeliveries(1)
            .handled(true)
            .to("file:target/messages?fileName=deadLetters.xml&fileExist=Append")
            .markRollbackOnly();  // NB: Must come *after* the dead letter endpoint.

        from("file:src/data?noop=true")
            .transacted()
            .bean("accountService","credit")
            .bean("accountService","debit")
            .bean("accountService","dumpTable")
            .to("file:target/messages");
    }
}

在前面的示例中,对Exception() 配置为捕获 IllegalArgumentException 异常,并将出错交换发送到死信文件 deadLetters.xml。当然,您可以更改此定义,以捕获应用程序中出现的任何异常。异常 rethrow 行为和事务回滚行为由 onException() 中的以下特殊设置控制:

  • handled(true) - 抑制异常。在这个特定示例中,响应异常不可取,因为它在将其传播到服务端点时触发无限循环。请参阅 第 9.5.1.3 节 “使用 markRollbackOnly() DSL 命令”。然而,在一些情况下,可能会对异常进行放量(例如,如果路由开头的端点没有实现重试功能)。
  • MarkRollbackOnly() - 在不抛出异常的情况下,标记当前回滚的事务。请注意,在 to() 命令将交换路由到死信队列后,插入此 DSL 命令非常重要。否则,交换永远不会到达死信队列,因为 markRollbackOnly() 会中断处理链。

9.5.3. 围绕一个事务捕获例外

在事务路由中使用 doTry()doCatch() 子句,不使用 onException() 处理异常的简单方法。例如,以下代码显示了在事务路由中如何捕获和处理 IllegalArgumentException,而不考虑陷入无限循环的风险。

// Java
import org.apache.camel.builder.RouteBuilder;

public class MyRouteBuilder extends RouteBuilder {
    ...
    public void configure() {
        from("file:src/data?noop=true")
            .doTry()
                .to("direct:split")
            .doCatch(IllegalArgumentException.class)
                .to("file:target/messages?fileName=deadLetters.xml&fileExist=Append")
            .end();

        from("direct:split")
            .transacted()
            .bean("accountService","credit")
            .bean("accountService","debit")
            .bean("accountService","dumpTable")
            .to("file:target/messages");
    }
}

在本例中,路由被分成两个片段。第一个网段(来自 file:src/data 端点)接收传入的交换,并使用 doTry()doCatch() 执行异常处理。第二段(来自 direct:split 端点)执行所有事务的工作。如果这个事务段中发生异常,它将第一个都传播到 transacted() 命令,从而导致回滚当前事务,然后然后由第一个路由网段中的 doCatch() 子句进行接收。doCatch() 子句不会重新定义异常,因此文件端点不会进行任何重试,避免无限循环。

Red Hat logoGithubRedditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

通过我们的产品和服务,以及可以信赖的内容,帮助红帽用户创新并实现他们的目标。

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

© 2024 Red Hat, Inc.