第 9 章 编写使用事务的 Camel 应用程序
在配置三个可用到所参考的、服务类型后,您已准备好编写应用程序。三种类型的服务:
一个事务管理器是以下接口的实施:
-
javax.transaction.UserTransaction
-
javax.transaction.TransactionManager
-
org.springframework.transaction.PlatformTransactionManager
-
-
至少一个 JDBC 数据源,它实施
javax.sql.DataSource
.通常,有多个数据源。 -
至少一个 JMS 连接工厂实施
javax.jms.ConnectionFactory
接口。通常,有多个.
这部分论述了与管理事务、数据源和连接因素相关的 Camel 特定配置。
本节描述了几个与 Spring 相关的概念,如 SpringTransactionPolicy
。Spring XML DSL 和 Blueprint XML DSL 之间存在明确区分,它们是定义 Camel 上下文的 XML 语言。Spring XML DSL 现在在 Fuse 中弃用。但是,Camel 事务机制仍然在内部使用 Spring 库。
这里的大部分信息都不依赖于所使用的平台 过渡
者类型。如果 PlatformTransactionManager
是 Narayana 事务管理器,则会使用完整的 JTA 事务。如果 platformTransactionManager
定义为本地 Blueprint < bean>
;,例如,org.springframework.jms.connection.JmsTransactionManager
,则会使用本地事务。
事务分离指的是启动、提交和回滚事务的步骤。本节介绍可用于控制事务分离的机制,它们均按编程以及配置进行。
9.1. 通过标记路由来分离事务
Apache Camel 提供了在路由中启动事务的简单机制。在 Java DSL 中插入 transacted()
命令,或者在 XML DSL 中插入 & lt;trans
acted/> 标签。
图 9.1. Marking the Route 来分离

翻译的处理器分离事务,如下所示:
- 当交换进入转换处理器时,转换的处理器调用默认事务管理器以启动事务并将事务附加到当前线程。
- 当交换达到其余路由结束时,转换处理器会调用事务管理器以提交当前的事务。
9.1.1. 带有 JDBC 资源的路由示例
图 9.1 “Marking the Route 来分离” 显示了一个路由示例,它通过将 transed()
DSL 命令添加到路由来实现事务。所有遵循 transacted()
节点的路由节点都包含在事务范围内。在本例中,两个节点访问 JDBC 资源。
9.1.2. Java DSL 中的路由定义
以下 Java DSL 示例演示了如何通过使用 transacted()
DSL 命令标记路由来定义事务路由:
import org.apache.camel.builder.RouteBuilder; class MyRouteBuilder extends RouteBuilder { public void configure() { from("file:src/data?noop=true") .transacted() .bean("accountService","credit") .bean("accountService","debit"); } }
在本例中,文件端点读取一些 XML 格式文件,它们描述了从一个帐户到另一个帐户的资金传输。第一个 bean()
调用会给受益辅助帐户指定资金总和,然后第二 bean()
调用会从发送者帐户中减去指定资金总和。两个 bean()
调用都会导致对数据库资源进行更新。假设数据库资源通过事务管理器绑定到事务,例如: 第 6 章 使用 JDBC 数据源。
9.1.3. Blueprint XML 中的路由定义
以上路由也可以在 Blueprint XML 中表示。& lt;transacted
/> 标签将路由标记为事务,如以下 XML 所示:
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ...> <camelContext xmlns="http://camel.apache.org/schema/blueprint"> <route> <from uri="file:src/data?noop=true" /> <transacted /> <bean ref="accountService" method="credit" /> <bean ref="accountService" method="debit" /> </route> </camelContext> </blueprint>
9.1.4. 默认事务管理器和转换策略
要分离事务,传输的处理器必须与特定的事务管理器实例关联。为了便于您每次调用 transacted()
时指定事务管理器,转换的处理器会自动选择可识别的默认值。例如,如果您的配置中只有一个事务管理器实例,则转换处理器隐式选取此事务管理器,并使用它来分离事务。
一个翻译的处理器还可使用转换策略( TransactedPolicy
类型)配置,它封装了传播策略和事务管理器(详情请参阅 第 9.4 节 “事务传播策略” )。以下规则用于选择默认事务管理器或事务策略:
如果只有
org.apache.camel.spi.TransactedPolicy
类型有一个 bean。注意TransactedPolicy
类型是SpringTransactionPolicy
类型的基础类型,如 第 9.4 节 “事务传播策略” 所述。因此,这里提到的信息可能是SpringTransactionPolicy
bean。-
如果存在 type,则
org.apache.camel.spi.TransactedPolicy
为,其ID
为PROPAGATION_REQUIRED
,请使用此 bean。 -
如果只有一个
org.springframework.transaction.transaction.PlatformTransactionManager
类型,则使用此 bean。
您还可以通过为 transacted()
提供 bean ID 作为参数来明确指定 bean。请参阅 第 9.4.4 节 “Java DSL 中带有 PROPAGATION_NEVER
策略的示例”。
9.1.5. 事务范围
如果您将转换处理器插入到路由中,则事务管理器每次通过此节点交换时会创建一个新的事务。事务的范围定义如下:
- 事务仅与当前线程关联。
- 事务范围包含在转换处理器后的所有路由节点。
任何在转换处理器前的路由节点都不在事务中。但是,如果路由从事务端点开始,则路由中的所有节点都位于事务中。请参阅 第 9.2.5 节 “路由开始时的事务端点”。
考虑以下路由:它不正确,因为 transacted()
DSL 命令错误地出现在第一个 bean()
调用后,它访问数据库资源:
// Java import org.apache.camel.builder.RouteBuilder; public class MyRouteBuilder extends RouteBuilder { ... public void configure() { from("file:src/data?noop=true") .bean("accountService", "credit") .transacted() // <-- WARNING: Transaction started in the wrong place! .bean("accountService", "debit"); } }
9.1.6. 事务路由中没有线程池
务必要清楚,给定的事务仅与当前线程关联。您不能在事务路由的中间创建线程池,因为新线程中的处理不会参与当前的事务。例如,以下路由绑定到造成问题:
// Java import org.apache.camel.builder.RouteBuilder; public class MyRouteBuilder extends RouteBuilder { ... public void configure() { from("file:src/data?noop=true") .transacted() .threads(3) // WARNING: Subthreads are not in transaction scope! .bean("accountService", "credit") .bean("accountService", "debit"); } }
上一个路由(如前面的路由)被损坏数据库,因为 thread()
DSL 命令与转换路由不兼容。即使 thread ()
调用前面是 transacted()
调用,路由也不会如预期的行为。
9.1.7. 将路由拆分为片段
如果要将路由分成碎片,并让每个路由片段参与到当前事务中,您可以使用 direct:
endpoint。例如,要发送交换来分隔路由片段,具体取决于传输量是否大(请求大于 100)还是小(不等于 100),您可以使用 choice()
DSL 命令和直接端点,如下所示:
// Java import org.apache.camel.builder.RouteBuilder; public class MyRouteBuilder extends RouteBuilder { ... public void configure() { from("file:src/data?noop=true") .transacted() .bean("accountService", "credit") .choice().when(xpath("/transaction/transfer[amount > 100]")) .to("direct:txbig") .otherwise() .to("direct:txsmall"); from("direct:txbig") .bean("accountService", "debit") .bean("accountService", "dumpTable") .to("file:target/messages/big"); from("direct:txsmall") .bean("accountService", "debit") .bean("accountService", "dumpTable") .to("file:target/messages/small"); } }
以 direct:tx
small 开头的片段开头是 direct:txsmall
,参与当前的事务,因为直接端点是同步的。这意味着片段在与第一个路由片段相同的线程中执行,因此它们会包含在相同的事务范围内。
您不必使用 seda
端点来加入路由片段。seda
使用者端点会创建一个新的线程(或线程),以执行路由片段(异步处理)。因此,片段不会参与原始事务。
9.1.8. 资源端点
以下 Apache Camel 组件在显示为路由目的地时用作资源端点,例如,如果它们出现在 to()
DSL 命令中。也就是说,这些端点可以访问事务的资源,如数据库或持久队列。资源端点可以参与当前的事务,只要它们与启动当前事务的转换处理器相同的事务管理器相关联。
- ActiveMQ
- AMQP
- Hibernate
- iBatis
- JavaSpace
- JBI
- JCR
- JDBC
- JMS
- JPA
- LDAP
9.1.9. 使用资源端点的路由示例
以下示例显示了包含资源端点的路由。这会将资金转让的顺序发送到两个不同的 JMS 队列。信贷
队列处理订单以对接收方的帐户进行计分。解除队列
处理顺序以分离发送者的帐户。只有在有相应的 debit 时才应有一个分数。因此,您要将 enqueueing 操作包含在一个事务中。如果事务成功,则订单和解除顺序都将排队。如果发生错误,则任何顺序都不会排队。
from("file:src/data?noop=true") .transacted() .to("jmstx:queue:credits") .to("jmstx:queue:debits");