第 9 章 编写使用事务的 Camel 应用程序
在配置了三个、可用的引用类型后,您可以编写一个应用程序。三种类型的服务有:
一个事务管理器,它是以下接口之一:
-
javax.transaction.UserTransaction
-
javax.transaction.TransactionManager
-
org.springframework.transaction.PlatformTransactionManager
-
-
至少有一个 JDBC 数据源实施
javax.sql.DataSource
. 接口。通常,有多个数据源。 -
至少一个实施
javax.jms.ConnectionFactory
接口的 JMS 连接工厂。通常,有多个。
这部分论述了与管理事务、数据源和连接工厂相关的特定于 Camel 的配置。
本节论述了几个与 Spring 相关的概念,如 SpringTransactionPolicy
。Spring XML DSL 和 Blueprint XML DSL 之间存在明显区别,它们是定义 Camel 上下文的 XML 语言。Spring XML DSL 现在在 Fuse 中被弃用。但是,Camel 事务机制仍然在内部使用 Spring 库。
这里的大部分信息都不依赖于所使用的 PlatformTransactionManager
类型。如果 PlatformTransactionManager
是 Narayana 事务管理器,则使用完整的 JTA 事务。如果 PlatformTransactionManager
定义为本地 Blueprint < bean>
;,例如 org.springframework.jms.connection.JmsTransactionManager
,则使用本地事务。
事务处理指的是启动、提交和回滚事务的步骤。本节介绍通过编程和配置控制事务处理的机制。
9.1. 通过标记路由进行事务处理
Apache Camel 提供了在路由中启动事务的简单机制。在 Java DSL 中插入 transacted ()
命令,或者在 XML DSL 中插入 <transacted
/> 标签。
图 9.1. 通过标记路由来划分
翻译处理器取消处理,如下所示:
- 当交换进入转换处理器时,转换的处理器调用默认事务管理器来开始事务,并将事务附加到当前线程。
- 当交换到达剩余的路由结束时,转换的处理器调用事务管理器来提交当前的事务。
9.1.1. 使用 JDBC 资源的路由示例
图 9.1 “通过标记路由来划分” 显示通过向路由中添加 transacted ()
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"); } }
在本例中,file 端点读取一些 XML 格式文件,这些文件描述了资金从一个帐户传输到另一个帐户的传输。第一个 bean ()
调用将指定资金总和计入 beneficiary 帐户,然后第二个 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 ()
时都指定事务管理器,转换的处理器会自动选择一个可靠的默认值。例如,如果您的配置中只有一个事务管理器实例,则转换的处理器隐式选择此事务管理器,并使用它来处理事务。
转换器的处理器也可以配置有 transacted 策略,该策略是 TransactedPolicy
类型,它封装了传播策略和事务管理器(详情请参阅 第 9.4 节 “事务传播策略” )。以下规则用于选择默认的事务管理器或事务策略:
如果只有一个 bean 是
org.apache.camel.spi.TransactedPolicy
类型,则使用此 bean。注意TransactedPolicy
类型是SpringTransactionPolicy
类型的基本类型,如 第 9.4 节 “事务传播策略” 中所述。因此,这里引用的 bean 可以是SpringTransactionPolicy
bean。-
如果存在类型为
org.apache.camel.spi.TransactedPolicy
的 bean,其ID
为 ,PROPAGATION_REQUIRED
,则使用此 bean。 -
如果只有一个 bean of
org.springframework.transaction.PlatformTransactionManager
类型,则使用此 bean。
您还可以通过向 transacted ()
提供 bean ID 作为参数来显式指定 bean。请参阅 第 9.4.4 节 “Java DSL 中带有 PROPAGATION_NEVER
策略的示例路由”。
9.1.5. 事务范围
如果您将转换的处理器插入到路由中,则事务管理器每次通过此节点时都会创建一个新的事务。事务的范围定义如下:
- 事务仅与当前线程关联。
- 事务范围包括所有遵循转换处理器的路由节点。
任何在 transacted 处理器之前的路由节点都不在事务中。但是,如果路由以事务端点开头,则路由中的所有节点都位于事务中。请参阅 第 9.2.5 节 “路由开始时的事务端点”。
考虑以下路由:它不正确,因为 transacted ()
DSL 命令错误地出现在访问数据库资源的 first 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"); } }
由于 threads ()
DSL 命令与 transacted 路由不兼容,如前面的路由是一定的破坏。即使 threads ()
调用在 transacted ()
调用前面,路由也不会按预期工作。
9.1.7. 将路由拆分为片段
如果您想要将路由拆分为片段,并且每个路由片段都参与当前事务,您可以使用 direct:
端点。例如,要将交换发送到单独的路由片段,具体取决于传输量大(不等于 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:txbig
和以 direct:txsmall
开头的片段都参与当前的事务,因为直接端点是同步的。这意味着,片段在与第一个路由片段相同的线程中执行,因此它们包含在相同的事务范围内。
您不能使用 seda
端点来加入路由片段。seda
消费者端点创建一个新的线程(或线程),以执行路由片段(异步处理)。因此,片段不会参与原始的事务。
9.1.8. 资源端点
当 Apache Camel 组件显示为路由的目标时,以下 Apache Camel 组件充当资源端点,例如,如果它们出现在 to ()
DSL 命令中。也就是说,这些端点可以访问事务资源,如数据库或持久队列。资源端点可以参与当前的事务,只要它们与启动当前事务的转换处理器相同的事务管理器关联。
- ActiveMQ
- AMQP
- 休眠
- 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");