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 命令
如果要在转换路由中触发回滚,您可以通过调用 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()
子句不会重新定义异常,因此文件端点不会进行任何重试,避免无限循环。