9.5. 错误处理和回滚
虽然您可以在事务路由中使用标准 Apache Camel 错误处理技术,但了解例外和事务处理之间的交互非常重要。特别是,您需要考虑抛出异常通常会导致事务回滚。请参见以下主题:
9.5.1. 如何回滚事务
您可以使用以下方法之一回滚事务:
9.5.1.1. 使用运行时例外来触发回滚
回滚 Spring 事务的最常见方法是抛出运行时(未检查)异常。换句话说,例外是 java.lang.RuntimeException
的一个实例或子类。java.lang.Error
类型的 Java 错误也会触发事务回滚。另一方面,检查异常不会触发回滚。
下图总结了 Java 错误和异常对事务的影响,其中触发回滚的类是灰色的。
Spring 框架还提供 XML 注解系统,允许您指定应或不应触发回滚的例外。详情请参阅 Spring 参考指南中的"回滚"。
如果在事务中处理运行时异常,也就是说,在异常有机会被仔细处理事务时,事务会被回滚。详情请查看 第 9.5.2 节 “如何定义死信队列”。
9.5.1.2. 使用 rollback ()
DSL 命令
如果要在 transacted 路由的中间触发回滚,您可以通过调用 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");
如果在前面的路由中触发回滚,它将在无限循环中捕获。这是因为 rollback ()
抛出 RollbackExchangeException
异常会在路由开始时传播到 文件
端点。File 组件有一个内置的可靠性功能,可导致它重新发送抛出异常的任何交换。在重新发送课程后,交换仅触发另一个回滚,从而导致无限循环。下一个示例演示了如何避免这种无限循环。
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"); } }
在前面的示例中,onException ()
配置为捕获 IllegalArgumentException
异常,并将终止交换发送到死信文件 deadLetters.xml
。当然,您可以更改此定义,以捕获应用程序出现的任何异常。改进行为和事务回滚行为的异常由 onException ()
子句中的以下特殊设置控制:
-
handled (true)
- 阻止重新增长异常。在这个特定示例中,rethrown 异常不可取,因为它会在重新传播到文件端点时触发无限循环。请参阅 第 9.5.1.3 节 “使用markRollbackOnly ()
DSL 命令”。然而,在某些情况下,可能可以接受重新增长异常(例如,如果路由开始时的端点没有实现重试功能)。 -
markRollbackOnly ()
- 在不抛出异常的情况下标记当前回滚的事务。请注意,在将交换路由到死信队列的to ()
命令后插入此 DSL 命令非常重要。否则,交换永远不会到达死信队列,因为markRollbackOnly ()
中断了处理链。
9.5.3. 捕获事务的例外
处理事务路由中的异常不是使用 onException ()
的简单方法是使用路由上的 doTry ()
和 doCatch ()
子句。例如,以下代码演示了如何捕获和处理事务路由中的 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 ()
子句不会重新增长异常,因此可以避免文件端点不会执行任何重试和无限循环。