Apache Karaf 事务指南
前言
本指南为实施 Fuse 事务应用程序提供信息和说明。这些信息被组织如下:
使开源包含更多
红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。我们从这四个术语开始:master、slave、黑名单和白名单。由于此项工作十分艰巨,这些更改将在即将推出的几个发行版本中逐步实施。详情请查看我们的 CTO Chris Wright 信息。
第 1 章 事务简介
本章通过讨论一些基本事务概念以及事务管理器中重要的服务数量来介绍事务。这些信息被组织如下:
1.1. 什么是事务?
事务的原型是一个由单一步骤组成的操作(例如,从帐户 A 转移到帐户 B),但必须作为一系列步骤实施。这类操作容易受到系统故障的影响,因为失败可能会使一些步骤不完成,从而导致系统处于不一致的状态。例如,考虑将资金从帐户 A 转移到帐户 B 的操作。假设系统在 debit account A 之后失败,但在进行贡献之前,结果会消失。
要确保此操作如可靠,将其实施为 事务。事务保证了可靠的执行,因为它是原子的、一致、隔离和持久的。这些属性被称为事务的 ACID 属性。
1.2. 事务的 ACID 属性
事务的 ACID 属性定义如下:
- Atomic-a 事务只是一个全部或无程序。当事务完成后,单个更新会被汇编或同时中止(回车)。
- 致- 事务是把系统从一个一致性状态到另一个一致性状态的工作单元。
- 隔离- 当事务正在执行时,其部分结果会与其他实体隐藏。
- durable - 事务的结果是持久的,即使系统提交事务后立即失败。
1.3. 关于事务客户端
事务客户端 是一个 API 或对象,可让您启动和结束事务。通常,事务客户端会公开 开始、提交 或回滚 事务的操作。
在标准的 JavaEE 应用程序中,javax.transaction.UserTransaction
接口会公开事务客户端 API。在 Spring Framework 的上下文中,Spring Boot 是 org.springframework.transaction.PlatformTransactionManager
接口公开一个事务客户端 API。
1.4. 事务术语描述
下表定义了一些重要的事务术语:
术语 | 描述 |
---|---|
demarcation | 事务分离指的是启动和结束事务。结束事务意味着在交易中完成的工作会被提交或回滚。可以明确说明,例如调用事务客户端 API 或隐式,例如在从事务端点轮询消息时。详情请查看 第 9 章 编写使用事务的 Camel 应用程序。 |
Resources | 资源是 计算机系统的任何组件,可以进入持久或永久更改。在实践中,资源通常始终是数据库或数据库层次的服务,例如:具有持久性的消息服务。但是,其他类型的资源是可见的。例如,Automated Teller Machine(ATM)是一个资源类型。在客户从机器实际接受了现金后,交易就不能撤销。 |
事务管理器 | 事务管理器 负责在一个或多个资源之间协调事务。在很多情况下,事务管理器内置在一个资源中。例如,企业级数据库通常包括一种事务管理器,可以管理该数据库中更改内容的事务。涉及 多个资源 的事务通常需要 外部 事务管理器。 |
事务上下文 | 事务上下文是 封装要跟踪事务所需的信息的对象。事务上下文的格式完全取决于相关的事务管理器实施。事务上下文至少包含一个唯一的事务标识符。 |
分布式事务 | 分布式事务指的是分布式系统中的事务,其中事务范围跨越多个网络节点。支持分布式事务的基本先决条件是网络协议,支持以规范格式对事务上下文传输。分布式事务不在 Apache Camel 事务范围内。另请参阅: 第 3.2.3 节 “关于分布式事务管理器”。 |
X/Open XA 标准 | X/Open XA 标准描述了将资源与事务管理器集成的接口。要管理包含多个资源的事务,参与的资源必须支持 XA 标准。支持 XA 标准的资源公开一个特殊的对象( AIX 交换机 ),它允许事务管理器(或事务处理监视器)控制资源事务。XA 标准支持 1-phase 提交协议和 2 阶段提交协议。 |
1.5. 管理修改多个资源的事务
对于涉及 单个资源 的事务,通常可以使用资源内置的事务管理器。对于 涉及多个 资源的事务,需要使用外部事务管理器或事务处理(TP)监控器。在这种情况下,资源必须通过注册 XA 交换机来与事务管理器集成。
用于提交在单个资源系统上运行的事务与用于提交在多资源系统上运行的事务之间的协议之间有一个重要的区别:
- 1-phase 提交-is 用于单一资源系统。此协议在一个步骤中提交事务。
- 2-phase 提交-is 用于多资源系统。此协议在两个步骤中提交事务。
在事务中包含多个资源增加了系统故障的风险,在对某些资源提交事务后可能会发生,但并非所有资源。这会使系统处于不一致的状态。2 阶段提交协议旨在消除这一风险。它保证系统 在重启后始终可以 恢复到一致的状态。
1.6. 事务和线程之间的关系
为了理解事务处理,感谢交易和线程之间的基本关系:交易是特定于线程的 ,交易是特定于线程的。也就是说,当事务启动时,它会附加到特定线程。(技术上,创建 事务上下文 对象并与当前线程关联)。从此点到事务结束,该线程中的所有活动都会在这个事务范围内发生。任何其它线程中的活动 都不会 在这个事务范围内。但是,任何其他线程中的活动都可能属于某个其他事务的范围。
事务和线程之间的这种关系意味着:
- 只要在单独的线程中创建每个 事务,应用程序就可以同时处理多个 事务。
-
注意在事务中创建子线程。如果您在事务的中间,并且您创建一个新的线程池,例如调用 thread
()
Camel DSL 命令,新的线程 不在 原始事务范围内。 - 注意处理步骤在前面的步骤中隐式创建新的线程。
-
事务范围通常不会跨路由段进行扩展。也就是说,如果一个路由片段
以 (JoinEndpoint)
结尾,另一个路由片段从(JoinEndpoint)
开始,这些路由片段 通常不 属于同一个事务。然而,有一些例外情况。
某些高级交易管理器实施可让您自由分离和附加事务上下文,以及从位于 的线程中分离和附加事务上下文。例如,可以将事务上下文从一个线程移动到另一个线程。在某些情况下,也可以将单一事务上下文附加到多个线程。
1.7. 关于事务服务量
在选择实施交易系统的产品时,有很多数据库产品和交易经理,免费收费和一些商业。所有这些都对交易处理有突出支持,但这些产品支持的服务质量有显著的变化。本节概述了比较不同事务产品的可靠性和复杂性时需要考虑的功能类型。
1.7.1. 资源提供的服务质量
以下功能决定了资源服务质量:
1.7.1.1. 事务隔离级别
ANSI SQL 定义四个 事务隔离级别,如下所示:
SERIALIZABLE
- 事务完全相互隔离。也就是说,在交易提交前,一个事务不会影响任何其他事务。这种隔离级别被描述为 可序列化 的,因为当所有事务在另一个事务后执行一样(尽管在实践中,资源通常会优化算法,因此一些事务可以同时进行处理)。
REPEATABLE_READ
-
每次事务读取或更新数据库时,都会获得读取或写入锁定,直到事务结束为止。这提供了几乎完全的隔离。但在一种情况下,隔离是不完美的。考虑使用
WHERE
子句读取一系列行的 SQLSELECT
语句。如果另一个事务在第一个事务运行时添加一个行到这个范围,如果第一个事务正在运行,则第一个事务可以看到这个新行,如果它重复SELECT
调用(一 E phantom 读取)。 READ_COMMITTED
- 写入锁定在事务结束前进行。在事务结束前,读取锁定 不会被保留。因此,重复读取可能会提供不同的结果,因为由其他事务提交的更新对持续事务可见。
READ_UNCOMMITTED
- 在事务结束前,读取锁定或写入锁定都不会被保留。因此,脏的读量也有可能。脏系统就绪是未提交由其他事务所做的更改对持续的事务可见时。
数据库通常不支持所有不同的事务隔离级别。例如,一些空闲数据库只支持 READ_UNCOMMITTED
。此外,一些数据库以与 ANSI 标准微小的方式实施事务隔离级别。隔离是一项复杂的问题,它涉及数据库性能的权衡(例如,请参阅 Wikipedia 中的隔离)。
1.7.1.2. 支持 XA 标准
若要让参与涉及多个资源的事务的资源,需要支持 X/Open XA 标准。务必检查 XA 标准的资源实施是否受到任何特殊限制。例如,Accli 标准的一些实施仅限于单一数据库连接,这意味着一次只有一个线程可以处理涉及该资源的事务。
1.7.2. 事务管理器提供的服务数量
以下功能决定了事务管理器的服务质量:
1.7.2.1. 支持挂起/恢复和附加/缓冲
某些事务管理器支持操作事务上下文和应用程序线程之间的关联,如下所示:
- 挂起/恢复当前事务- 允许您暂时暂停当前事务上下文,同时应用程序会在当前线程中执行一些非事务工作。
- attach/detach 事务上下文- 允许您将事务上下文从一个线程移动到另一个线程,或扩展事务范围以包含多个线程。
1.7.2.2. 支持多个资源
事务管理器的一个关键区别是能够支持多个资源。这通常支持 XA 标准,其中事务管理器为资源提供了注册其 XA 交换机的方法。
严格说来,Alan standard 不是您可使用它来支持多个资源的唯一方法,而是最实用的方法。另一种方法通常涉及编写繁琐(和关键)自定义代码,以实施通常通过 XA 交换机提供的算法。
1.7.2.3. 分布式事务
某些事务管理器能够管理其范围包括分布式系统中的多个节点的事务。使用特殊协议(如 WS-AtomicTransactions 或 CORBA OTS),将事务上下文从节点传播到节点。
1.7.2.4. 事务监控
高级事务管理器通常提供视觉化工具来监控待处理事务的状态。这样的工具在系统失败后特别有用,它可以帮助识别和解决处于不确定状态(高流例外)的事务。
1.7.2.5. 从失败中恢复
在出现系统故障(崩溃)时,交易经理对交易经理有显著的区别。事务管理器使用的关键策略是在执行事务的每个步骤之前将数据写入持久日志。如果出现故障,日志中的数据可用于恢复事务。某些事务管理器比其他事务者更仔细实施此策略。例如,高端事务管理器通常重复持久性事务日志,并允许将每个日志存储在单独的主机机器上。
第 2 章 Karaf 上的事务开始(OSGi)
本节介绍使用事务访问SOURCE JMS 代理的 Camel 应用程序。这些信息被组织如下:
2.1. 先决条件
此 Camel 应用程序的实施有以下先决条件:
必须运行一个外部 AMQ 7 JMS 消息代理。
以下示例代码运行
amq-broker-7.1.0-bin.zip
的独立(非 Docker)版本。执行将创建并运行amq7
实例:$ pwd /data/servers/amq-broker-7.1.0 $ bin/artemis create --user admin --password admin --require-login amq7 Creating ActiveMQ Artemis instance at: /data/servers/amq-broker-7.1.0/amq7 Auto tuning journal ... done! Your system can make 27.78 writes per millisecond, your journal-buffer-timeout will be 36000 You can now start the broker by executing: "/data/servers/amq-broker-7.1.0/amq7/bin/artemis" run Or you can run the broker in the background using: "/data/servers/amq-broker-7.1.0/amq7/bin/artemis-service" start $ amq7/bin/artemis run __ __ ____ ____ _ /\ | \/ |/ __ \ | _ \ | | / \ | \ / | | | | | |_) |_ __ ___ | | _____ _ __ / /\ \ | |\/| | | | | | _ <| '__/ _ \| |/ / _ \ '__| / ____ \| | | | |__| | | |_) | | | (_) | < __/ | /_/ \_\_| |_|\___\_\ |____/|_| \___/|_|\_\___|_| Red Hat JBoss AMQ 7.1.0.GA 018-05-02 16:37:19,294 INFO [org.apache.activemq.artemis.integration.bootstrap] AMQ101000: Starting ActiveMQ Artemis Server ...
需要客户端库。Django 库可用于 Maven Central 或红帽存储库。例如,您可以使用:
-
mvn:org.apache.activemq/artemis-core-client/2.4.0.amq-710008-redhat-1
-
mvn:org.apache.activemq/artemis-jms-client/2.4.0.amq-710008-redhat-1
或者,Fuhad/AMQ 7 客户端库可以安装为 Karaf 功能,例如:
-
Karaf@root()> feature:install artemis-jms-client artemis-core-client
-
需要一些支持功能,它们提供 Karaf shell 命令或专用语支持:
karaf@root()> feature:install jms pax-jms-artemis pax-jms-config
所需的 Camel 功能包括:
karaf@root()> feature:install camel-jms camel-blueprint
2.2. 构建 camel-jms 项目
您可以从 Fuse Software Downloads 页面下载 快速入门
信息。
将 zip 文件的内容提取到本地文件夹,例如一个名为 Quickstart 的新文件夹。
然后,您可以构建并安装 /camel/camel-jms
示例,作为 OSGi 捆绑包。此捆绑包包含 Camel 路由的蓝图 XML 定义,该路由将消息发送到 AMQ 7 JMS 队列。
在以下示例中,$FUSE_HOME
是解压缩的 Fuse 分发的位置。构建此项目:
调用 Maven 来构建项目:
$ cd quickstarts $ mvn clean install -f camel/camel-jms/
创建 JMS 连接工厂配置,以便在 OSGi 运行时发布
javax.jms.ConnectionFactory
服务。为此,请将快速入门/camel/camel-jms/src/main/resources/etc/org.ops4j.connectionfactory-amq7.cfg
复制到$FUSE_HOME/etc
目录中。此配置将进行处理,以创建正常工作的连接工厂。例如:$ cp camel/camel-jms/src/main/resources/etc/org.ops4j.connectionfactory-amq7.cfg ../etc/
验证公布的连接工厂:
karaf@root()> service:list javax.jms.ConnectionFactory [javax.jms.ConnectionFactory] ----------------------------- felix.fileinstall.filename = file:$FUSE_HOME/etc/org.ops4j.connectionfactory-amq7.cfg name = artemis osgi.jndi.service.name = artemis password = admin pax.jms.managed = true service.bundleid = 251 service.factoryPid = org.ops4j.connectionfactory service.id = 436 service.pid = org.ops4j.connectionfactory.d6207fcc-3fe6-4dc1-a0d8-0e76ba3b89bf service.scope = singleton type = artemis url = tcp://localhost:61616 user = admin Provided by : OPS4J Pax JMS Config (251) karaf@root()> jms:info -u admin -p admin artemis Property │ Value ─────────┼────────────────────────── product │ ActiveMQ version │ 2.4.0.amq-711002-redhat-1 karaf@root()> jms:queues -u admin -p admin artemis JMS Queues ──────────────────────────────────── df2501d1-aa52-4439-b9e4-c0840c568df1 DLQ ExpiryQueue
安装捆绑包:
karaf@root()> install -s mvn:org.jboss.fuse.quickstarts/camel-jms/7.0.0.redhat-SNAPSHOT Bundle ID: 256
确认它正在运行:
karaf@root()> camel:context-list Context Status Total # Failed # Inflight # Uptime ------- ------ ------- -------- ---------- ------ jms-example-context Started 0 0 0 2 minutes karaf@root()> camel:route-list Context Route Status Total # Failed # Inflight # Uptime ------- ----- ------ ------- -------- ---------- ------ jms-example-context file-to-jms-route Started 0 0 0 2 minutes jms-example-context jms-cbr-route Started 0 0 0 2 minutes
-
Camel 路由启动后,您可以在 Fuse 安装中看到一个目录
work/jms/input
。将您在此 Quickstart 的src/main/data
目录中找到的文件复制到新创建的work/jms/input
目录中。 稍等片刻,您将在
work/jms/output
目录下找到由国家组织相同的文件:-
在
work/jms/output/others
中,order1.xml
、order2.xml
和order4.xml
-
work/jms/output/us
中的order3.xml
和order5.xml
-
work/jms/output/fr
中的order6.xml
-
在
查看日志以查看业务日志:
2018-05-02 17:20:47,952 | INFO | ile://work/jms/input | file-to-jms-route | 58 - org.apache.camel.camel-core - 2.21.0.fuse-000077 | Receiving order order1.xml 2018-05-02 17:20:48,052 | INFO | umer[incomingOrders] | jms-cbr-route | 58 - org.apache.camel.camel-core - 2.21.0.fuse-000077 | Sending order order1.xml to another country 2018-05-02 17:20:48,053 | INFO | umer[incomingOrders] | jms-cbr-route | 58 - org.apache.camel.camel-core - 2.21.0.fuse-000077 | Done processing order1.xml
查看队列是否动态创建:
karaf@root()> jms:queues -u admin -p admin artemis JMS Queues ──────────────────────────────────── DLQ 17767323-937f-4bad-a403-07cd63311f4e ExpiryQueue incomingOrders
检查 Camel 路由统计信息:
karaf@root()> camel:route-info jms-example-context file-to-jms-route Camel Route file-to-jms-route Camel Context: jms-example-context State: Started State: Started Statistics Exchanges Total: 1 Exchanges Completed: 1 Exchanges Failed: 0 Exchanges Inflight: 0 Min Processing Time: 67 ms Max Processing Time: 67 ms Mean Processing Time: 67 ms Total Processing Time: 67 ms Last Processing Time: 67 ms Delta Processing Time: 67 ms Start Statistics Date: 2018-05-02 17:14:17 Reset Statistics Date: 2018-05-02 17:14:17 First Exchange Date: 2018-05-02 17:20:48 Last Exchange Date: 2018-05-02 17:20:48
2.3. camel-jms 项目的说明
Camel 路由使用以下端点 URI:
<route id="file-to-jms-route"> ... <to uri="jms:queue:incomingOrders?transacted=true" /> </route> <route id="jms-cbr-route"> <from uri="jms:queue:incomingOrders?transacted=true" /> ... </route>
jms
组件使用以下代码片段配置:
<bean id="jms" class="org.apache.camel.component.jms.JmsComponent"> <property name="connectionFactory"> <reference interface="javax.jms.ConnectionFactory" /> </property> <property name="transactionManager" ref="transactionManager"/> </bean>
虽然 transactionManager
引用是:
<reference id="transactionManager" interface="org.springframework.transaction.PlatformTransactionManager" />
如您所见,JMS 连接工厂和 PlatformTransactionManager
的 Spring 接口都只是引用。在 Blueprint XML 中不需要定义它们。这些服务 由 Fuse 本身公开。
您已发现,使用 etc/org.ops4j.connectionfactory-amq7.cfg
创建了 javax.jms.ConnectionFactory
。
事务管理器是:
karaf@root()> service:list org.springframework.transaction.PlatformTransactionManager [org.springframework.transaction.PlatformTransactionManager] ------------------------------------------------------------ service.bundleid = 21 service.id = 527 service.scope = singleton Provided by : Red Hat Fuse :: Fuse Modules :: Transaction (21) Used by: Red Hat Fuse :: Quickstarts :: camel-jms (256)
检查从中注册实际事务管理器的其他接口:
karaf@root()> headers 21 Red Hat Fuse :: Fuse Modules :: Transaction (21) ------------------------------------------------ ... Bundle-Name = Red Hat Fuse :: Fuse Modules :: Transaction Bundle-SymbolicName = fuse-pax-transx-tm-narayana Bundle-Vendor = Red Hat ... karaf@root()> bundle:services -p 21 Red Hat Fuse :: Fuse Modules :: Transaction (21) provides: ---------------------------------------------------------- objectClass = [org.osgi.service.cm.ManagedService] service.bundleid = 21 service.id = 519 service.pid = org.ops4j.pax.transx.tm.narayana service.scope = singleton ---- objectClass = [javax.transaction.TransactionManager] provider = narayana service.bundleid = 21 service.id = 520 service.scope = singleton ---- objectClass = [javax.transaction.TransactionSynchronizationRegistry] provider = narayana service.bundleid = 21 service.id = 523 service.scope = singleton ---- objectClass = [javax.transaction.UserTransaction] provider = narayana service.bundleid = 21 service.id = 524 service.scope = singleton ---- objectClass = [org.jboss.narayana.osgi.jta.ObjStoreBrowserService] provider = narayana service.bundleid = 21 service.id = 525 service.scope = singleton ---- objectClass = [org.ops4j.pax.transx.tm.TransactionManager] provider = narayana service.bundleid = 21 service.id = 526 service.scope = singleton ---- objectClass = [org.springframework.transaction.PlatformTransactionManager] service.bundleid = 21 service.id = 527 service.scope = singleton
事务管理器在这些界面中可用:
-
javax.transaction.TransactionManager
-
javax.transaction.TransactionSynchronizationRegistry
-
javax.transaction.UserTransaction
-
org.jboss.narayana.osgi.jta.ObjStoreBrowserService
-
org.ops4j.pax.transx.tm.TransactionManager
-
org.springframework.transaction.PlatformTransactionManager
您可以在您需要的任何上下文中使用它们。例如 camel-jms
需要初始化 org.apache.camel.component.jms.JmsConfiguration.transactionManager
字段。这是示例使用的原因:
<reference id="transactionManager" interface="org.springframework.transaction.PlatformTransactionManager" />
例如:
<reference id="transactionManager" interface="javax.transaction.TransactionManager" />
第 3 章 用于配置和引用事务管理器的接口
JavaEE 和 Spring Boot 各自提供一个事务客户端接口,用于在 Fuse 中配置事务管理器以及在已部署的应用中使用事务管理器。配置(管理任务)与引用(开发任务)之间有明确的区别。应用程序开发人员负责将应用程序指向之前配置的事务管理器。
3.1. 事务管理器的作用
事务管理器是应用程序的一部分,负责在一个或多个资源之间协调事务。事务管理器的职责如下:
- demarcation - 使用开始、提交和回滚方法开始和结束事务。
- 管理事务上下文 - 事务上下文包含事务管理器跟踪事务需要的信息。事务管理器负责创建事务上下文并将其附加到当前线程。
协调多个资源中的事务 - 企业级交易经理通常具备在多个资源间协调事务的能力。这个功能需要 2-phase 提交协议和资源,且资源必须使用 XA 协议进行注册和管理。请参阅 第 1.7.1.2 节 “支持 XA 标准”。
这是所有事务管理器不支持的高级功能。
- 故障恢复 - 事务管理器负责确保在出现系统故障和应用失败时资源不处于不一致状态。在某些情况下,可能需要手动干预才能将系统恢复到一致的状态。
3.2. 关于本地、全局和分布式的事务管理器
事务管理器可以是本地、全局或分布式的。
3.2.1. 关于本地事务管理器
本地事务管理器 是一个事务管理器,只能协调单一资源的事务。本地事务管理器的实现通常嵌入到资源本身中,应用程序使用的事务管理器所使用的事务管理器是一个精简打包程序。
例如,Oracle 数据库有一个内置的事务管理器,它支持解包操作(使用 SQL BEGIN
、COMMIT
或 ROLLBACK
语句,或者使用原生 Oracle API)和各种事务隔离级别。对 Oracle 事务管理器的控制可以通过 JDBC 导出,此 JDBC API 则供应用程序用于分离事务。
在此上下文中,了解构成资源是非常重要的。例如,如果您使用 JMS 产品,JMS 资源是 JMS 产品的单一运行实例,而不是单个队列和主题。在有些情况下,如果以不同方式访问相同的基础资源,有时会出现多个资源可能是一个单一资源。例如,您的应用可以直接访问关系数据库(通过 JDBC),间接访问一个对象关系映射工具(如 Hibernate)。在这种情况下,涉及相同的基础事务管理器,因此应该可以在同一事务中注册这些代码片段。
无法保证在每次情况下都会保证这一点。虽然原则上有可能是原则,但一些在 Spring Framework 或其他打包程序层设计的细节可能会阻止其在实践中工作。
应用程序可以相互独立地工作很多不同的本地事务管理器。例如,您可以有一个用于操作 JMS 队列和主题的 Camel 路由,其中 JMS 端点引用 JMS 事务管理器。另一个路由可以通过 JDBC 访问关系数据库。但是,您不能将 JDBC 和 JMS 访问组合到同一路由中,并使他们参与同一个事务。
3.2.2. 关于全球事务管理器
全局事务管理器是一个事务管理器,可以在多个资源上协调事务。当您无法依赖资源本身内置的事务管理器时,这是必需的。外部系统有时称为事务处理监控器(TP 监控),能够在不同资源之间协调事务。
以下是对多个资源操作的事务的先决条件:
- 全局事务管理器或 TP 监控 - 实施 2 阶段提交协议的外部交易系统,用于协调多个 XA 资源。
- 支持 XA 标准 的资源 - 参与 2 阶段提交,资源必须支持 XA 标准。请参阅 第 1.7.1.2 节 “支持 XA 标准”。实际上,这意味着资源能够导出 XA 交换机 对象,提供对外部 TP 监视器的完整事务控制。
Spring Framework 本身并不提供 TP 监控来管理全局事务。但是,它提供了与 OSGi 提供的 TP 监控器集成或与 JavaEE 提供的 TP 监控(其中集成由 JtaTransactionManager 类实施)的支持。因此,如果您将应用程序部署到具有完整事务支持的 OSGi 容器中,您可以在 Spring 中使用多个事务资源。
3.2.3. 关于分布式事务管理器
通常,服务器会直接连接到事务中涉及的资源。但是,在分布式系统中,偶尔需要通过 Web 服务连接到仅间接公开的资源。在这种情况下,您需要一个支持分布式事务的 TP 监控。有几种标准来描述如何支持各种分布式协议的事务,例如,Web 服务的 WS-AtomicTransactions 规格。
3.3. 使用 JavaEE 事务客户端
在使用 JavaEE 时,与事务管理器交互的最资金和标准方法是 Java 事务 API(JTA)接口,javax.transaction.UserTransaction
。规范用法是:
InitialContext context = new InitialContext(); UserTransaction ut = (UserTransaction) context.lookup("java:comp/UserTransaction"); ut.begin(); // Access transactional, JTA-aware resources such as database and/or message broker ut.commit(); // or ut.rollback()
从 JNDI(Java 命名与目录接口)获取 UserTransaction
实例是获取事务客户端的一种方式。在 JavaEE 环境中,您可以使用 CDI(上下文和依赖项注入)访问事务客户端。
下图显示了 typica JavaEE Camel 应用。
图显示 Camel 代码和应用程序代码都可以访问:
-
javax.transaction.UserTransaction
实例,使用 SpringTransactionTemplate
类直接从应用程序或内部通过事务感知型 Camel 组件分离交易。 -
直接通过 JDBC API 的数据库,如使用 Spring 的
JdbcTemplate
或使用camel-jdbc
组件。 -
通过 JMS API 的消息代理通过使用 Spring 的
JmsTemplate
类或使用camel-jms
组件来直接通过 JMS API 进行消息代理。
在使用 javax.transaction.UserTransaction
对象时,您不需要清楚使用的实际事务管理器,因为您只与事务客户端直接合作。(请参阅 第 1.3 节 “关于事务客户端”。) Spring 和 Camel 采用不同的方法,因为它在内部使用 Spring 的事务工具。
JavaEE 应用
在典型的 JavaEE 场景中,应用部署到 JavaEE 应用程序服务器,通常是 WAR
或 EAR
存档。通过 JNDI 或 CDI,应用程序可以访问 javax.transaction.UserTransaction
服务的实例。然后,复制使用此事务客户端实例来分离事务。在事务中,应用执行 JDBC 和/或 JMS 访问权限。
Camel 组件和应用程序代码
这些代表执行 JMS/ JDBC 操作的代码。Camel 具有自己的高级方法,可访问 JMS/ JDBC 资源。应用代码可以直接使用给定的 API。
JMS ConnectionFactoryy
这是 javax.jms.ConnectionFactory
接口,用于获取 javax.jms.Connection
的实例,然后在 JMS 2.0 中获取 javax.jms.Session
(或 javax.jms.JmsContext
)。这可以直接由应用程序使用,或在 Camel 组件中间接使用,可在内部使用 org.springframework.jms.core.JmsTemplate
。应用程序代码或 Camel 组件都不需要此连接工厂的详细信息。连接工厂是在应用服务器上配置的。您可以在 JavaEE 服务器中看到此配置。OSGi 服务器(如 Fuse)类似。系统管理员会独立于应用程序配置连接工厂。通常,连接工厂实施池功能。
JDBC 数据源
这是 javax.sql.DataSource
接口,用于获取 java.sql.Connection
的实例。与 JMS 一样,该数据源可以直接或间接使用。例如,camel-sql
组件在内部使用 org.springframework.jdbc.core.JdbcTemplate
类。与 JMS 一样,应用程序代码和 Camel 都不需要此数据源的详细信息。该配置通过使用 第 4 章 配置 Narayana 事务管理器 中描述的方法在应用服务器或 OSGi 服务器内完成。
3.4. 使用 Spring Boot 事务客户端
Spring Framework(和 Spring Boot)主要目标之一是使 JavaEE API 更易于使用。所有主要的 JavaEE vanilla API 在 Spring Framework(Spring Boot)中都有它们的一部分。这些不是给定 API 的替代方案或替代,而是打包程序会添加更多配置选项或更一致的用法,例如,处理异常。
下表将给定的 JavaEE API 与其 Spring 相关的接口匹配:
JavaEE API | Spring 工具程序 | 配置使用 |
---|---|---|
JDBC |
|
|
JMS |
|
|
JTA |
|
|
JdbcTemplate
和 JmsTemplate
分别使用 javax.sql.DataSource
和 javax.jms.ConnectionFactory
。但 TransactionTemplate
使用 PlatformTransactionManager
的 Spring 接口。在这里,Spring 不只是 改进 JavaEE,而是将 JavaEE 客户端 API 替换为其自身的。
Spring 将 javax.transaction.UserTransaction
视为一个对于实际情况来说非常简单的接口。另外,因为 javax.transaction.UserTransaction
无法区分本地、单一资源事务和全球多资源事务,因此 org.springframework.transaction.transaction.PlatformTransactionManager
赋予开发人员更具自由度。
以下是 Spring Boot 的规范 API 用法:
// Create or get from ApplicationContext or injected with @Inject/@Autowired. JmsTemplate jms = new JmsTemplate(...); JdbcTemplate jdbc = new JdbcTemplate(...); TransactionTemplate tx = new TransactionTemplate(...); tx.execute((status) -> { // Perform JMS operations within transaction. jms.execute((SessionCallback<Object>)(session) -> { // Perform operations on JMS session return ...; }); // Perform JDBC operations within transaction. jdbc.execute((ConnectionCallback<Object>)(connection) -> { // Perform operations on JDBC connection. return ...; }); return ...; });
在上例中,所有三种类型的 模板都 只是实例化,但它们也可以从 Spring 应用的 Context
获取,或使用 @Autowired
注解注入。
3.4.1. 使用 Spring PlatformTransactionManager 接口
如前文中所述,javax.transaction.UserTransaction
通常来自 JavaEE 应用中的 JNDI。但是 Spring 会为很多场景提供这个界面的显式实现。您不需要完整的 JTA 情景,有时应用只需要访问单个资源,如 JDBC。
通常, org.springframework.transaction.PlatformTransactionManager
是 Spring 事务客户端 API,提供经典的事务客户端操作: 开始
、提交和
回滚
。换句话说,此接口提供了在运行时控制事务的基本方法。
任何事务系统的另一个关键方面是实施事务资源的 API。但是,事务资源通常由底层数据库实施,因此交易编程的此类方面很少是应用程序编程的关注。
3.4.1.1. PlatformTransactionManager 接口的定义
public interface PlatformTransactionManager { TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }
3.4.1.2. 关于 TransactionDefinition 接口
您可以使用 TransactionDefinition
接口指定新创建的事务的特征。您可以指定新事务的隔离级别和传播策略。详情请查看 第 9.4 节 “事务传播策略”。
3.4.1.3. TransactionStatus 接口的定义
您可以使用 TransactionStatus
接口来检查当前事务的状态,即与当前线程关联的事务,并为当前线程标记当前的事务进行回滚。这是接口定义:
public interface TransactionStatus extends SavepointManager, Flushable { boolean isNewTransaction(); boolean hasSavepoint(); void setRollbackOnly(); boolean isRollbackOnly(); void flush(); boolean isCompleted(); }
3.4.1.4. 由 PlatformTransactionManager 接口定义的方法
PlatformTransactionManager
接口定义以下方法:
- getTransaction()
-
创建一个新的事务,并通过传递一个
定义新事务
的特性来将其与当前线程相关联。这与许多其他事务客户端 API 的 start()
方法类似。 - commit()
- 提交当前的事务,这样对注册的资源的所有待处理的更改都是永久的。
- rollback()
- 回滚当前的事务,这会撤消对注册的资源的所有待处理更改。
3.4.2. 使用事务管理器的步骤
通常,您不会直接使用 PlatformTransactionManager
接口。在 Apache Camel 中,您通常使用事务管理器,如下所示:
- 创建事务管理器实例。Spring 中有几种不同的实现,请参阅 第 3.4 节 “使用 Spring Boot 事务客户端”。
-
将事务管理器实例传递到 Apache Camel 组件或路由中的
transacted()
DSL 命令。然后,事务组件或transacted()
命令负责分离事务。详情请查看 第 9 章 编写使用事务的 Camel 应用程序。
3.4.3. 关于 Spring PlatformTransactionManager 实现
本节概述了 Spring Framework 提供的事务管理器实现。该实施分为两类:本地事务管理器和全球交易经理。
从 Camel 开始:
-
camel-jms
组件使用的 org.apache.camel.component.JmsConfigurationorg.springframework.transaction.transaction.PlatformTransactionManager
接口的实例。 -
org.apache.camel.component.sql.SqlComponent
在内部使用org.springframework.jdbc.core.JdbcTemplate
类,此 JDBC 模板也与org.springframework.transaction.transaction.transaction.PlatformTransactionManager
集成。
如您所见,您必须 有一些 实现这个接口。根据具体情况,您可以配置所需的平台事务管理器。
3.4.3.1. Local PlatformTransactionManager 实现
以下列表总结了 Spring Framework 提供的本地事务管理器实现。这些事务管理器只支持单个资源。
- org.springframework.jms.connection.JmsTransactionManager
- 此事务管理器实施能够管理 单个 JMS 资源。您可以连接到任意数量的队列或主题,但前提是它们属于同一基础 JMS 消息传递产品实例。此外,您也无法在事务中列出其他类型的资源。
- org.springframework.jdbc.datasource.DataSourceTransactionManager
- 此事务管理器实施能够管理 单个 JDBC 数据库资源。您可以更新任意数量的不同数据库表,但前提是 它们属于同一基础数据库实例。
- org.springframework.orm.jpa.JpaTransactionManager
- 此事务管理器实施能够管理 Java Persistence API(JPA)资源。但是,无法同时将其他类型的资源列于事务中。
- org.springframework.orm.hibernate5.HibernateTransactionManager
- 此事务管理器实施能够管理 Hibernate 资源。但是,无法同时将其他类型的资源列于事务中。此外,JPA API 比原生 Hibernate API 更首选。
另外,还有其他不常使用的、不常使用的、平台 可靠性管理器的
实现。
3.4.3.2. Global PlatformTransactionManager 实现
Spring Framework 提供了一个全局事务管理器实施,用于在 OSGi 运行时使用。org.springframework.transaction.jta.JtaTransactionManager
支持对事务中的多个资源的操作。这个事务管理器支持 XA 事务 API,并可在事务中放入多个资源。要使用此事务管理器,您必须将应用程序部署到 OSGi 容器或 JavaEE 服务器中。
虽然 PlatformTransactionManager
的单资源实施是实际的 实现,但JtaTransactionManager
更加适用于标准 javax.transaction.TransactionManager
的实际实现。
因此,您可以在可以访问的环境中运行 PlatformTransactionManager
的 JtaTransactionManager
实施(通过 JNDI 或 CDI)已经配置了 javax.transaction.TransactionManager
且通常是 javax.transaction.UserTransaction
.通常,这两个接口都由单个对象/服务实施。
以下是配置/使用 JtaTransactionManager
的示例:
InitialContext context = new InitialContext(); UserTransaction ut = (UserTransaction) context.lookup("java:comp/UserTransaction"); TransactionManager tm = (TransactionManager) context.lookup("java:/TransactionManager"); JtaTransactionManager jta = new JtaTransactionManager(); jta.setUserTransaction(ut); jta.setTransactionManager(tm); TransactionTemplate jtaTx = new TransactionTemplate(jta); jtaTx.execute((status) -> { // Perform resource access in the context of global transaction. return ...; });
在上例中,JTA 对象(UserTransaction
和 TransactionManager
)的实际实例来自 JNDI。在 OSGi 中,它们也可以从 OSGi 服务注册表获得。
3.5. OSGi 在事务客户端和事务管理器间的接口
在有关 JavaEE 事务客户端 API 和 Spring Boot 事务客户端 API 的描述后,可以看到 OSGi 服务器(如 Fuse)中的关系。OSGi 的其中一个特性是全球服务 registry,可用于:
- 通过过滤或接口查找服务。
- 使用给定接口和属性注册服务。
与在 JavaEE 应用程序服务器中部署的应用程序一样,使用 JNDI(服务 定位方法)或由 CDI(依赖项注入 方法)获取对 javax.transaction.UserTransaction
的引用一样,您可以在以下位置获取相同的引用(直接或间接):
-
调用
org.osgi.framework.BundleContext.getServiceReference()
方法(服务 locator)。 - 并将它们注入到 Blueprint 容器中。
- 使用 Service Component Runtime(SCR)注解(依赖项注入)。
下图显示了在 OSGi 运行时部署的 Fuse 应用程序。应用程序代码和/或 Camel 组件使用其 API 获取事务管理器、数据源和连接因素的引用。
应用程序(bundle)与在 OSGi 注册表中注册的服务交互。访问通过 接口 来执行,这都应当与应用程序相关。
在 Fuse 中,实现(直接或通过小包装器)交易客户端接口的基本对象是 org.jboss.narayana.osgi.jta.internal.OsgiTransactionManager
。您可以使用以下接口访问事务管理器:
-
javax.transaction.TransactionManager
-
javax.transaction.UserTransaction
-
org.springframework.transaction.PlatformTransactionManager
-
org.ops4j.pax.transx.tm.TransactionManager
您可以直接使用任何这些接口,或通过选择 Camel 等框架或库来隐式使用它们。
有关在 Fuse 中配置 org.jboss.narayana.osgi.jta.internal.OsgiTransactionManager
的方法,请参考 第 4 章 配置 Narayana 事务管理器。本指南中的后续章节会基于本章中的信息构建,并描述如何配置和使用其他服务,如 JDBC 数据源和 JMS 连接工厂。
第 4 章 配置 Narayana 事务管理器
在 Fuse 中,全球交易经理是 JBoss Narayana 事务管理器,它是企业应用平台(EAP)7 使用的事务管理器。
在 OSGi 运行时,与用于 Karaf 的 Fuse 中一样,额外的集成层由 PAX TRANSX 项目提供。
以下主题讨论 Narayana 配置:
4.1. 关于 Narayana 安装
Narayana 事务管理器在以下接口以及一些额外的支持接口中使用:
-
javax.transaction.TransactionManager
-
javax.transaction.UserTransaction
-
org.springframework.transaction.PlatformTransactionManager
-
org.ops4j.pax.transx.tm.TransactionManager
7.11.1.fuse-7_11_1-00013-redhat-00003
分发使这些接口在启动时可用。
pax-transx-tm-narayana
功能包含一个嵌入 Narayana 的覆盖捆绑包:
karaf@root()> feature:info pax-transx-tm-narayana Feature pax-transx-tm-narayana 0.3.0 Feature has no configuration Feature has no configuration files Feature depends on: pax-transx-tm-api 0.0.0 Feature contains followed bundles: mvn:org.jboss.fuse.modules/fuse-pax-transx-tm-narayana/7.0.0.fuse-000191-redhat-1 (overriden from mvn:org.ops4j.pax.transx/pax-transx-tm-narayana/0.3.0) Feature has no conditionals.
fuse-pax-transx-tm-narayana
捆绑包提供的服务有:
karaf@root()> bundle:services fuse-pax-transx-tm-narayana Red Hat Fuse :: Fuse Modules :: Transaction (21) provides: ---------------------------------------------------------- [org.osgi.service.cm.ManagedService] [javax.transaction.TransactionManager] [javax.transaction.TransactionSynchronizationRegistry] [javax.transaction.UserTransaction] [org.jboss.narayana.osgi.jta.ObjStoreBrowserService] [org.ops4j.pax.transx.tm.TransactionManager] [org.springframework.transaction.PlatformTransactionManager]
由于此捆绑包注册了 org.osgi.service.ManagedService
,所以它会跟踪并对 CM 配置中的更改做出反应:
karaf@root()> bundle:services -p fuse-pax-transx-tm-narayana Red Hat Fuse :: Fuse Modules :: Transaction (21) provides: ---------------------------------------------------------- objectClass = [org.osgi.service.cm.ManagedService] service.bundleid = 21 service.id = 232 service.pid = org.ops4j.pax.transx.tm.narayana service.scope = singleton ...
默认的 org.ops4j.pax.transx.tm.narayana
PID 为:
karaf@root()> config:list '(service.pid=org.ops4j.pax.transx.tm.narayana)' ---------------------------------------------------------------- Pid: org.ops4j.pax.transx.tm.narayana BundleLocation: ? Properties: com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.communicationStore.localOSRoot = communicationStore com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.communicationStore.objectStoreDir = /data/servers/7.11.1.fuse-7_11_1-00013-redhat-00003/data/narayana com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.communicationStore.objectStoreType = com.arjuna.ats.internal.arjuna.objectstore.ShadowNoFileLockStore com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.localOSRoot = defaultStore com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.objectStoreDir = /data/servers/7.11.1.fuse-7_11_1-00013-redhat-00003/data/narayana com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.objectStoreType = com.arjuna.ats.internal.arjuna.objectstore.ShadowNoFileLockStore com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.stateStore.localOSRoot = stateStore com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.stateStore.objectStoreDir = /data/servers/7.11.1.fuse-7_11_1-00013-redhat-00003/data/narayana com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.stateStore.objectStoreType = com.arjuna.ats.internal.arjuna.objectstore.ShadowNoFileLockStore com.arjuna.ats.arjuna.common.RecoveryEnvironmentBean.recoveryBackoffPeriod = 10 felix.fileinstall.filename = file:/data/servers/7.11.1.fuse-7_11_1-00013-redhat-00003/etc/org.ops4j.pax.transx.tm.narayana.cfg service.pid = org.ops4j.pax.transx.tm.narayana
概述:
- 适用于 Karaf 的 Fuse 包括功能齐全的全球、Narayana 事务经理。
- 事务管理器在不同的客户端接口(JTA、Spring-tx、PAX JMS)下正确公开。
-
您可以使用标准的 OSGi 方法( Configuration Admin)配置 Narayana,它可在
org.ops4j.pax.transx.tm.narayana
中找到。 -
默认配置在
$FUSE_HOME/etc/org.ops4j.pax.transx.tm.narayana.cfg
中提供。
4.2. 支持的事务协议
Narayana 事务管理器 是 EAP 中使用的 JBoss/红帽产品。Narayana 是一个事务工具包,它为使用各种基于标准的事务协议开发的应用程序提供支持:
- JTA
- JTS
- Web 服务事务
- REST 事务
- STM
- XATMI/TX
4.3. 关于 Narayana 配置
pax-transx-tm-narayana
捆绑包包括 jbossts-properties.xml
文件,它为事务管理器的不同方面提供默认配置。所有这些属性可以在 $FUSE_HOME/etc/org.ops4j.pax.transx.tm.narayana.cfg
文件中使用 Configuration Admin API 覆盖。
Narayana 的基本配置是通过各种 EnvironmentBean
对象来完成的。所有这些 bean 都可使用不同的前缀的属性来配置。下表提供了所使用的配置对象和前缀概述:
Configuration Bean | 属性前缀 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
前缀 可以简化配置。但是,您通常应该使用以下格式之一:
NameEnvironmentBean.propertyName
(首选格式)或
fully-qualified-class-name.field-name
例如,考虑 com.arjuna.ats.arjuna.common.CoordinatorEnvironmentBean.commitOnePhase
字段。它可通过使用 com.arjuna.ats.arjuna.common.CoordinatorEnvironmentBean.commitOnePhase
属性进行配置,也可以使用更简单(首选)表单协调 Bean.commitOnePhase
进行配置。可在 Narayana 产品文档 中找到如何设置属性以及哪些 Beans 的完整详情。
有些 Bean(如 ObjectStoreEnvironmentBean
)可能会多次配置,每个 命名 实例为不同的目的提供配置。在本例中,实例的名称在前缀(以上任意一个)和 field-name
之间使用。例如,可以使用名为 communicationStore
的属性配置 ObjectStoreEnvironmentBean
实例的对象存储:
-
com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.communicationStore.objectStoreType
-
ObjectStoreEnvironmentBean.communicationStore.objectStoreType
4.4. 配置日志存储
最重要的配置是对象存储的类型和位置。通常有三种实现 com.arjuna.ats.arjuna.objectstore.ObjectStoreAPI
接口:
- com.arjuna.ats.internal.arjuna.objectstore.hornetq.HornetqObjectStoreAdaptor
-
从 AMQ 7 内部使用
org.apache.activemq.artemis.core.journal.Journal
存储。 - com.arjuna.ats.internal.arjuna.objectstore.jdbc.JDBCStore
- 使用 JDBC 来保持 TX 日志文件。
- com.arjuna.ats.internal.arjuna.objectstore.FileSystemStore(及专用实施)
- 使用基于文件的自定义日志存储。
默认情况下,Fuse 使用 com.arjuna.ats.internal.arjuna.objectstore.ShadowNoFileLockStore
,这是 FileSystemStore
的专用实施。
Narayana 用来保存事务/对象日志的三个 存储 :
-
defaultStore
-
communicationStore
-
stateStore
如需了解更多详细信息,请参阅 Narayana 文档中的 状态管理。
这三个 存储 的默认配置是:
# default store com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.objectStoreType = com.arjuna.ats.internal.arjuna.objectstore.ShadowNoFileLockStore com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.objectStoreDir = ${karaf.data}/narayana com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.localOSRoot = defaultStore # communication store com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.communicationStore.objectStoreType = com.arjuna.ats.internal.arjuna.objectstore.ShadowNoFileLockStore com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.communicationStore.objectStoreDir = ${karaf.data}/narayana com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.communicationStore.localOSRoot = communicationStore # state store com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.stateStore.objectStoreType = com.arjuna.ats.internal.arjuna.objectstore.ShadowNoFileLockStore com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.stateStore.objectStoreDir = ${karaf.data}/narayana com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean.stateStore.localOSRoot = stateStore
ShadowNoFileLockStore
配置有基础目录(objectStoreDir
)和特定存储的目录(localOSRoot
)。
很多配置选项包括在 Narayana 文档指南中。但是,Narayana 文档指出,对配置选项的规范引用是各种 EnvironmentBean
类的 Javadoc。
第 5 章 使用 Narayana 事务管理器
本节介绍了通过实施 javax.transaction.UserTransaction
接口、org.springframework.transaction.PlatformTransactionManager
接口或 javax.transaction.Transaction
Manager 接口来使用 Narayana 事务管理器。您选择的要使用的接口取决于应用程序的需求。在本章最后,讨论获取 XA 资源的问题。这些信息被组织如下:
有关 Java 事务 API 详情,请查看 Java 事务 API(JTA)1.2 规格和 Javadoc。
5.1. 使用 UserTransaction 对象
为交易递减实施 javax.transaction.UserTransaction
接口。用于开始、提交或回滚事务。这是您可能直接在应用程序代码中使用的 JTA 接口。但是,UserTransaction
接口只是计算事务的方法之一。有关您可以分离事务的不同方法的讨论,请参阅 第 9 章 编写使用事务的 Camel 应用程序。
5.1.1. UserTransaction 接口的定义
JTA UserTransaction
接口定义如下:
public interface javax.transaction.UserTransaction { public void begin(); public void commit(); public void rollback(); public void setRollbackOnly(); public int getStatus(); public void setTransactionTimeout(int seconds); }
5.1.2. UserTransaction 方法的描述
UserTransaction
接口定义以下方法:
- begin()
- 启动新事务并将其与当前线程相关联。如果任何 XA 资源都与此事务相关联,则事务会隐式成为 XA 事务。
- commit()
正常完成当前的事务,以便所有待处理的更改都变成永久更改。提交后,不再有一个与当前线程关联的事务。
注意但是,如果当前事务仅标记为回滚,则在调用
commit()
时,实际上将回滚事务。- rollback()
- 立即中止事务,以便丢弃所有待处理的更改。回滚后,不再有一个与当前线程关联的事务。
- setRollbackOnly()
- 修改当前事务的状态,以便回滚是唯一可能的结果,但还没有执行回滚。
- getStatus()
返回当前事务的状态,可以是以下整数值之一,如
javax.transaction.Status
接口中定义的:-
STATUS_ACTIVE
-
STATUS_COMMITTED
-
STATUS_COMMITTING
-
STATUS_MARKED_ROLLBACK
-
STATUS_NO_TRANSACTION
-
STATUS_PREPARED
-
STATUS_PREPARING
-
STATUS_ROLLEDBACK
-
STATUS_ROLLING_BACK
-
STATUS_UNKNOWN
-
- setTransactionTimeout()
- 自定义当前事务的超时,以秒数为单位。如果在指定超时内没有解决事务,则事务管理器会自动回滚。
5.2. 使用 TransactionManager 对象
使用 javax.transaction.TransactionManager
对象的最常见方法是将它传递到框架 API,例如 Camel JMS 组件。这可让框架在您进行事务后查找。有时,您可能想要直接使用 TransactionManager
对象。当您需要访问高级事务 API(如 suspend()
和 resume()
方法)时,这很有用。
5.2.1. TransactionManager 接口的定义
JTA TransactionManager 接口有以下定义:
interface javax.transaction.TransactionManager { // Same as UserTransaction methods public void begin(); public void commit(); public void rollback(); public void setRollbackOnly(); public int getStatus(); public void setTransactionTimeout(int seconds); // Extra TransactionManager methods public Transaction getTransaction(); public Transaction suspend() ; public void resume(Transaction tobj); }
5.2.2. TransactionManager 方法的描述
TransactionManager
接口支持 UserTransaction
接口中找到的所有方法。您可以使用 TransactionManager
对象进行事务分离。另外,TransactionManager
对象支持这些方法:
- getTransaction()
-
获取对当前事务的引用,这是与当前线程关联的事务。如果没有当前事务,此方法会返回
null
。 - suspend()
将当前的事务从当前线程分离,并返回对事务的引用。调用此方法后,当前线程不再有事务上下文。此时您所做的所有工作都不会在事务上下文中不再执行。
注意并非所有事务管理器都支持挂起事务。但是,Narayana 支持此功能。
- resume()
- 将暂停的事务重新关联到当前线程上下文。在调用此方法后,事务上下文会被恢复,并在事务上下文中完成这个点后执行的所有工作。
5.3. 使用事务对象
如果您要暂停/恢复事务,或者需要明确列出资源,您可能需要直接使用 javax.transaction.Transaction
对象。如 第 5.4 节 “解决 XA 总结问题” 所述,框架或容器通常负责自动列出资源。
5.3.1. 事务接口的定义
JTA 事务
接口有以下定义:
interface javax.transaction.Transaction { public void commit(); public void rollback(); public void setRollbackOnly(); public int getStatus(); public boolean enlistResource(XAResource xaRes); public boolean delistResource(XAResource xaRes, int flag); public void registerSynchronization(Synchronization sync); }
5.3.2. 事务方法描述
commit()
, rollback()
, setRollbackOnly()
和 getStatus()
方法的行为与 UserTransaction
界面中对应的方法相同。实际上,UserTransaction
对象是一种方便的打包程序,它可检索当前的事务,然后在 Transaction
对象上调用对应的方法。
另外,Transaction
接口定义了以下方法,在 UserTransaction
接口中没有对应的项:
- enlistResource()
将 XA 资源与当前的事务相关联。
注意此方法在 XA 事务上下文中的关键重要性。它是详细地将多个 XA 资源及当前事务所定为 XA 事务的功能。另一方面,明确列出资源是一个细微差别,您通常期望您的框架或容器为您完成此操作。例如,请参阅 第 5.4 节 “解决 XA 总结问题”。
- delistResource()
解除事务中的指定资源。标志参数可以采用
javax.transaction.Transaction
接口中定义的以下整数值之一:-
TMSUCCESS
-
TMFAIL
-
TMSUSPEND
-
- registerSynchronization()
-
使用当前事务注册
javax.transaction.Synchronization
对象。Synchronization
对象仅在提交的准备阶段前收到回调,并在事务完成后收到回调。
5.4. 解决 XA 总结问题
编写 XA 资源的标准 JTA 方法是明确将 XA 资源添加到当前的 javax.transaction.Transaction
对象中,即当前事务。换而言之,每次都开始新事务时,您必须明确获取 XA 资源。
5.4.1. 如何获取 XA 资源
Enlisting an XA 资源及事务涉及在 Transaction
接口上调用 enlistResource()
方法。例如,给定一个 TransactionManager
对象和一个 XAResource
对象,您可以按照如下所示列出 XAResource
对象:
// Java import javax.transaction.Transaction; import javax.transaction.TransactionManager; import javax.transaction.xa.XAResource; ... // Given: // 'tm' of type TransactionManager // 'xaResource' of type XAResource // Start the transaction tm.begin(); Transaction transaction = tm.getTransaction(); transaction.enlistResource(xaResource); // Do some work... ... // End the transaction tm.commit();
列出资源的一个技巧是,资源必须在每个新的事务上被列出,且在开始使用资源前必须被列出该资源。如果您明确获取资源,则可能会得到一个容易出错的代码,这些代码已被 enlistResource()
调用。此外,有时候,在正确的位置调用 enlistResource()
可能比较困难,例如,如果您使用可隐藏部分事务详情的框架,就很困难。
5.4.2. 关于自动清单
通过使用支持Aselisting of XA 资源的功能,而不是明确列出了 XA 资源,而是更容易且更安全。例如,在使用 JMS 和 JDBC 资源的情况下,标准技术是使用支持自动清单的 wrapper 类。
JDBC 和 JMS 访问的通用模式是:
-
应用代码要求
javax.sql.DataSource
用于 JDBC 访问,而用于 JMS 的javax.jms.ConnectionFactory
以获取 JDBC 或 JMS 连接。 - 在应用程序/OSGi 服务器中,注册这些接口的数据库或代理特定实现。
- 应用程序/OSGi 服务器将特定于数据库/代理的因素嵌套成通用池,以及列出因素。
这样,应用程序代码仍然使用 javax.sql.DataSource
和 javax.jms.ConnectionFactory
,但在访问这些时,还有额外的功能,通常有问题:
- 连接池 - 并非每次都创建新连接数据库/消息代理,而是使用预先初始化的 连接池。池的 另一个 方面可能是连接的定期验证。
-
JTA enlistment - 在返回
java.sql.Connection
(JDBC)或javax.jms.Connection
(JMS)实例之前,如果实际连接对象是 true XA 资源,则会注册实际连接对象。如果注册可用,则在 JTA 事务内进行注册。
通过自动列出,应用程序代码不需要更改。
有关为 JDBC 数据源和列出打包程序以及 JMS 连接工厂的更多信息,请参阅 第 6 章 使用 JDBC 数据源 和 第 7 章 使用 JMS 连接工厂。
第 6 章 使用 JDBC 数据源
以下主题讨论 Fuse OSGi 运行时中的 JDBC 数据源的使用:
6.1. 关于连接接口
用于执行数据操作的最重要 对象是 java.sql.Connection
接口的实施。从 Fuse 配置的角度来看,了解如何 获取 连接
对象非常重要。
包含相关对象的库有:
-
postgresql:
mvn:org.postgresql/postgresql/42.2.5
-
MySQL:
mvn:mysql/mysql-connector-java/5.1.34
现有实现(包括在 驱动 JARs中)提供:
-
postgresql:
org.postgresql.jdbc.PgConnection
-
MySQL:
com.mysql.jdbc. JDBC4Connection
(请参阅com.mysql.jdbc.Driver
的各种connect*()
方法)
这些实施包含数据库特定的逻辑,用于执行 DML、DDL 和简单的事务管理。
理论上,可以手动创建这些连接对象,但有两个 JDBC 方法隐藏了详情以提供清理 API:
-
java.sql.Driver.connect()
- 这个方法在一个独立的应用程序中使用。 -
javax.sql.DataSource.getConnection()
- 这是使用 factory 模式的首选方法。类似的方法从 JMS 连接工厂获取 JMS 连接。
此处 不讨论驱动程序管理器 方法。这个方法只是一个小 层,对于给定连接对象来说,这个方法只是一个小型层。
除了 java.sql.Connection
之外,它有效地实施特定于数据库的通信协议,还有另外两个专用的 连接 接口:
-
javax.sql.PooledConnection
代表物理连接。您的代码不会直接与这个池连接交互。相反,会使用getConnection()
方法获取的连接。这种间接寻址支持在应用服务器的级别管理连接池。使用getConnection()
获取的连接通常是代理。当这种代理连接关闭时,物理连接不会关闭,而是会在受管连接池中再次可用。 -
javax.sql.XAConnection
允许获取与javax.transaction.xa.XAResource
对象关联的 javax.transaction.xa.XAResource 对象,以用于javax.transaction.TransactionManager
。由于javax.sql.XAConnection
扩展javax.sql.PooledConnection,它还提供了"getConnection()
方法,它与典型的 DML/DQL 方法的访问。
6.2. JDBC 数据源概述
JDBC 1.4 标准引入了 javax.sql.DataSource
接口,它充当 java.sql.Connection
对象的 工厂。通常,这些数据源绑定到 JNDI 注册表,并位于 或 servlet 等 Java EE 组件中。关键方面是这些数据源是在 应用服务器 中配置,并按名称在部署的应用程序 中引用。
以下 连接 对象有自己的 数据源 :
数据源 | 连接 |
---|---|
|
|
|
|
|
|
以上每个 数据源 之间最重要的区别如下:
javax.sql.DataSource
最重要的是一个类似于java.sql.Connection
实例的 工厂。大多数javax.sql.DataSource
实现通常执行 连接池 不应更改图片的事实。这是应用程序代码应使用的唯一接口。您正在实施以下哪些方法无关紧要:- 直接 JDBC 访问
-
JPA 持久性单元配置(<
jta-data-source
> 或 <non-jta-data-source>
) - 常见库,如 Apache Camel 或 Spring Framework
javax.sql.ConnectionPoolDataSource
最重要的是通用(特定于数据库)连接池/数据源之间的 桥接。它可以被视为 SPI 接口。应用程序代码通常处理从 JNDI 获取的通用javax.sql.DataSource
对象,并由应用服务器实施(可能使用commons-dbcp2
等库)。在另一个结束时,应用程序代码没有直接与javax.sql.ConnectionPoolDataSource
的接口。它用于应用服务器和特定于数据库的驱动程序。以下序列图显示了这一点:
javax.sql.XADataSource
是一种获取javax.sql.XAConnection
和javax.transaction.xAResource
的方法。与javax.sql.ConnectionPoolDataSource
相同,它在应用服务器和特定于数据库的驱动程序之间使用。以下是带有不同执行器的稍有修改的图表,其中包括 JTA 事务管理器:
如上图中所示,您与 App Server 交互,这是可在其中配置 javax.sql.DataSource
和 javax.transaction.UserTransaction
实例的一般实体。这些实例可以通过 JNDI 进行访问,也可以使用 CDI 或其他依赖项机制注入。
重要的一点是,即使应用使用 XA 事务和/或连接池,应用也会与 javax.sql.DataSource
进行交互,而不是其他 JDBC 数据源接口。
6.2.1. 特定于数据库及通用数据源
JDBC 数据源实施分为两个类别:
通用
javax.sql.DataSource
实现,例如:- Apache Commons DBCP(2)
- Apache Tomcat JDBC(基于 DBCP)
-
javax.sql.DataSource
、javax.sql.XADataSource
和javax.sql.ConnectionPoolDataSource
的数据库具体实施
常见 javax.sql.DataSource
实现可能会令人困惑,无法自行创建特定于数据库的连接。即使 通用 数据源可以使用 java.sql.Driver.connect()
或 java.sql.DriverManager.getConnection()
,通常最好使用特定于数据库的 javax.sql.DataSource
实现配置此 通用 数据源。
当 通用 数据源与 JTA 交互时,它 必须配置有 javax.sql.XADataSource
的数据库特定实施。
要关闭图片,通用 数据源 通常不需要 特定于数据库的 javax.sql.ConnectionPoolDataSource
来执行连接池。现有池通常处理没有标准 JDBC 接口(javax.sql.ConnectionPoolDataSource
和 javax.sql.PooledConnection
)的池,而是使用自己的自定义实施。
6.2.2. 有些通用数据源
例如,众所周知的通用数据源 Apache Commons DBCP(2).
javax.sql.XADataSource 实现
DBCP2 不包括 javax.sql.XADataSource
的任何实施,这是预期的。
javax.sql.ConnectionPoolDataSource implementations
DBCP2 确实 包括 javax.sql.ConnectionPoolDataSource
的实施:org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS
。它通过调用 java.sql.DriverManager.getConnection()
创建 javax.sql.PooledConnection
对象。这个池不应该被直接使用,它应该被视为 驱动程序的适配器 :
-
不要提供自己的
javax.sql.ConnectionPoolDataSource
实现 - 您需要根据 JDBC 建议用于 连接池来使用
如上图中所示,驱动程序直接提供 javax.sql.ConnectionPoolDataSource
,或利用 org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS
适配器的 帮助,而 DBCP2 实施了 应用服务器 合同之一:
-
org.apache.commons.dbcp2.datasources.PerUserPoolDataSource
-
org.apache.commons.dbcp2.datasources.SharedPoolDataSource
这两个池均在配置阶段获取 javax.sql.ConnectionPoolDataSource
的实例。
这是 DBCP2 中最重要的和有趣的部分:
javax.sql.DataSource 实现
要实现连接池功能,您不必遵循 JDBC 建议 以使用 javax.sql.ConnectionPoolDataSource
→ javax.sql.PooledConnection
SPI。
以下是 DBCP 2 的一般数据源列表:
-
org.apache.commons.dbcp2.BasicDataSource
-
org.apache.commons.dbcp2.managed.BasicManagedDataSource
-
org.apache.commons.dbcp2.PoolingDataSource
-
org.apache.commons.dbcp2.managed.ManagedDataSource
这里有 两个问题 :
基本 与 池
这个 axis 决定 池配置 方面。
两种数据源都执行 java.sql.Connection
对象的 池。唯一的 区别是:
-
使用 bean 属性 配置基本的 数据源,如
maxTotal
或minIdle
,用于配置org.apache.commons.pool2.impl.GenericObjectPool
的内部实例。 -
池 数据源配置有外部创建/配置的
org.apache.commons.pool2.ObjectPool
。
受管 与非管理
这个 axis 决定 连接创建 信息以及 JTA 行为:
非管理的基本 数据源在内部使用 java.sql.Driver.
Driver.connect()创建
实例。java.sql.
Connection非管理的池 数据源使用传递的
org.apache.commons.pool2.ObjectPool
对象来创建java.sql.Connection
实例。受管池 数据源将
java.sql.Connection
实例嵌套在org.apache.commons.dbcp2.managed.ManagedConnection
对象,以确保 JTA 上下文中需要调用javax.transaction.Transaction.enlistResource()
。但是,仍从池配置的任何org.apache.commons.pool2.ObjectPool
对象获取已嵌套的实际连接。受管的基本 数据源可从配置专用
org.apache.commons.pool2.ObjectPool
。相反,配置现有的、真实的、特定于数据库的javax.sql.XADataSource
对象就足够了。bean 属性用于创建org.apache.commons.pool2.impl.GenericObjectPool
的内部实例,后者被传递给 受管 池数据源(org.apache.commons.dbcp2.managed.ManagedDataSource
)的内部实例。
DBCP2 唯一无法执行的事情是 XA 事务恢复。DBCP2 正确放入活跃 JTA 事务中的 XAResources,但不执行恢复。这应该单独完成,配置通常特定于所选交易管理器实施(如 Narayana)。
6.2.3. 要使用的模式
推荐的模式是:
-
创建或获取带有特定 数据库 的配置(URL、凭证等)的、特定于数据库的
javax.sql.XADataSource
实例,或获取可以创建连接/XA 连接的数据库。 -
创建或获取 非特定于数据库的
javax.sql.DataSource
实例(内部配置了上述、特定于数据库的数据源),具有非特定于数据库的配置(连接池、事务管理器等)。 -
使用
javax.sql.DataSource
获取java.sql.Connection
的实例并执行 JDBC 操作。
以下是一个 规范的示例 :
// Database-specific, non-pooling, non-enlisting javax.sql.XADataSource PGXADataSource postgresql = new org.postgresql.xa.PGXADataSource(); // Database-specific configuration postgresql.setUrl("jdbc:postgresql://localhost:5432/reportdb"); postgresql.setUser("fuse"); postgresql.setPassword("fuse"); postgresql.setCurrentSchema("report"); postgresql.setConnectTimeout(5); // ... // Non database-specific, pooling, enlisting javax.sql.DataSource BasicManagedDataSource pool = new org.apache.commons.dbcp2.managed.BasicManagedDataSource(); // Delegate to database-specific XADatasource pool.setXaDataSourceInstance(postgresql); // Delegate to JTA transaction manager pool.setTransactionManager(transactionManager); // Non database-specific configuration pool.setMinIdle(3); pool.setMaxTotal(10); pool.setValidationQuery("select schema_name, schema_owner from information_schema.schemata"); // ... // JDBC code: javax.sql.DataSource applicationDataSource = pool; try (Connection c = applicationDataSource.getConnection()) { try (Statement st = c.createStatement()) { try (ResultSet rs = st.executeQuery("select ...")) { // ....
在 Fuse 环境中,有很多配置选项且不需要使用 DBCP2。
6.3. 配置 JDBC 数据源
如 OSGi 事务架构 中所述,在 OSGi 服务 registry 中必须注册一些服务。正如您 找到 (查找)事务管理器实例(例如 javax.transaction.UserTransaction
接口)的事务管理器实例一样,您可以使用 javax.sql.DataSource
接口与 JDBC 数据源相同。要求是:
- 与目标数据库通信的数据库特定数据源
- 您可以配置池以及可能的事务管理(XA)的通用数据源
在 OSGi 环境中,如 Fuse,如果它们注册为 OSGi 服务,则可从应用程序访问数据源。基本上,它按如下方式完成:
org.osgi.framework.BundleContext.registerService(javax.sql.DataSource.class, dataSourceObject, properties); org.osgi.framework.BundleContext.registerService(javax.sql.XADataSource.class, xaDataSourceObject, properties);
注册这些服务的方法有两种:
-
使用
jdbc:ds-create
Karaf console 命令发布数据源。这是 配置方法。 -
使用 Blueprint、OSGi Declative Services(SCR)或只使用
BundleContext.registerService()
API 调用来发布数据源。这个方法需要专用的 OSGi 捆绑包,其中包含代码和/或元数据。这是 the_deployment 方法_。
6.4. 使用 OSGi JDBC 服务
Enterprise R6 规范的第 125 章在 org.osgi.service.jdbc
软件包中定义一个接口。这是OSOS 如何处理数据源:
public interface DataSourceFactory { java.sql.Driver createDriver(Properties props); javax.sql.DataSource createDataSource(Properties props); javax.sql.ConnectionPoolDataSource createConnectionPoolDataSource(Properties props); javax.sql.XADataSource createXADataSource(Properties props); }
如前文所述,普通的 java.sql.Connection
连接可以直接从 java.sql.Driver
获取。
Generic org.osgi.service.jdbc.DataSourceFactory
org.osgi.service.jdbc.DataSourceFactory
最简单的实施是 org.ops4j.pax.jdbc.impl.DriverDataSourceFactory
by mvn:org.ops4j.pax.pax.jdbc/pax-jdbc/1.3.0
bundle。它会跟踪包含标准 Java™ ServiceLoader 实用程序的 /META-INF/services/java.sql.Driver
描述符的捆绑包。如果您安装任何标准 JDBC 驱动程序,pax-jdbc
捆绑包注册一个可用于(而非直接)的 DataSourceFactory
,以便通过 java.sql.Driver.connect()
调用来获取连接。
karaf@root()> install -s mvn:org.osgi/org.osgi.service.jdbc/1.0.0 Bundle ID: 223 karaf@root()> install -s mvn:org.ops4j.pax.jdbc/pax-jdbc/1.3.0 Bundle ID: 224 karaf@root()> install -s mvn:org.postgresql/postgresql/42.2.5 Bundle ID: 225 karaf@root()> install -s mvn:mysql/mysql-connector-java/5.1.34 Bundle ID: 226 karaf@root()> bundle:services -p org.postgresql.jdbc42 PostgreSQL JDBC Driver JDBC42 (225) provides: --------------------------------------------- objectClass = [org.osgi.service.jdbc.DataSourceFactory] osgi.jdbc.driver.class = org.postgresql.Driver osgi.jdbc.driver.name = PostgreSQL JDBC Driver osgi.jdbc.driver.version = 42.2.5 service.bundleid = 225 service.id = 242 service.scope = singleton karaf@root()> bundle:services -p com.mysql.jdbc Oracle Corporation's JDBC Driver for MySQL (226) provides: ---------------------------------------------------------- objectClass = [org.osgi.service.jdbc.DataSourceFactory] osgi.jdbc.driver.class = com.mysql.jdbc.Driver osgi.jdbc.driver.name = com.mysql.jdbc osgi.jdbc.driver.version = 5.1.34 service.bundleid = 226 service.id = 243 service.scope = singleton ----- objectClass = [org.osgi.service.jdbc.DataSourceFactory] osgi.jdbc.driver.class = com.mysql.fabric.jdbc.FabricMySQLDriver osgi.jdbc.driver.name = com.mysql.jdbc osgi.jdbc.driver.version = 5.1.34 service.bundleid = 226 service.id = 244 service.scope = singleton karaf@root()> service:list org.osgi.service.jdbc.DataSourceFactory [org.osgi.service.jdbc.DataSourceFactory] ----------------------------------------- osgi.jdbc.driver.class = org.postgresql.Driver osgi.jdbc.driver.name = PostgreSQL JDBC Driver osgi.jdbc.driver.version = 42.2.5 service.bundleid = 225 service.id = 242 service.scope = singleton Provided by : PostgreSQL JDBC Driver JDBC42 (225) [org.osgi.service.jdbc.DataSourceFactory] ----------------------------------------- osgi.jdbc.driver.class = com.mysql.jdbc.Driver osgi.jdbc.driver.name = com.mysql.jdbc osgi.jdbc.driver.version = 5.1.34 service.bundleid = 226 service.id = 243 service.scope = singleton Provided by : Oracle Corporation's JDBC Driver for MySQL (226) [org.osgi.service.jdbc.DataSourceFactory] ----------------------------------------- osgi.jdbc.driver.class = com.mysql.fabric.jdbc.FabricMySQLDriver osgi.jdbc.driver.name = com.mysql.jdbc osgi.jdbc.driver.version = 5.1.34 service.bundleid = 226 service.id = 244 service.scope = singleton Provided by : Oracle Corporation's JDBC Driver for MySQL (226)
使用以上命令时,javax.sql.DataSource
服务仍然没有注册,但您更接近一步。以上中介 org.osgi.service.jdbc.DataSourceFactory
服务可用于获取:
-
java.sql.Driver
-
javax.sql.DataSource
通过传递属性:url
、
到用户和密码
createDataSource()
方法。
您无法从普通的 org.osgi.jdbc.DataSource 获取
,由一个非数据库特定的 javax.sql.ConnectionPoolDataSource
或 javax.sql.XADataSource
Factorypax-jdbc
捆绑包创建。
mvn:org.postgresql/postgresql/42.2.5
捆绑包可以正确地实现 OSGi JDBC 规格,并注册一个 org.osgi.service.jdbc.DataSourceFactory
实例,并附带所有实施的方法,包括创建 XA 和 ConnectionPool 数据源。
专用、特定于数据库的 org.osgi.service.jdbc.DataSource factory
实施
还有其他捆绑包,比如以下所示:
-
mvn:org.ops4j.pax.jdbc/pax-jdbc-mysql/1.3.0
-
mvn:org.ops4j.pax.jdbc/pax-jdbc-db2/1.3.0
- …
这些捆绑包注册特定于数据库的 org.osgi.service.jdbc.DataSource factory
服务,这些服务可以返回所有 工厂,包括 javax.sql.ConnectionPoolDataSource
和 javax.sql.XADataSource
。例如:
karaf@root()> install -s mvn:org.ops4j.pax.jdbc/pax-jdbc-mysql/1.3.0 Bundle ID: 227 karaf@root()> bundle:services -p org.ops4j.pax.jdbc.mysql OPS4J Pax JDBC MySQL Driver Adapter (227) provides: --------------------------------------------------- objectClass = [org.osgi.service.jdbc.DataSourceFactory] osgi.jdbc.driver.class = com.mysql.jdbc.Driver osgi.jdbc.driver.name = mysql service.bundleid = 227 service.id = 245 service.scope = singleton karaf@root()> service:list org.osgi.service.jdbc.DataSourceFactory ... [org.osgi.service.jdbc.DataSourceFactory] ----------------------------------------- osgi.jdbc.driver.class = com.mysql.jdbc.Driver osgi.jdbc.driver.name = mysql service.bundleid = 227 service.id = 245 service.scope = singleton Provided by : OPS4J Pax JDBC MySQL Driver Adapter (227)
6.4.1. PAX- JDBC 配置服务
使用 pax-jdbc
(或 pax-jdbc-mysql
,pax-jdbc-oracle
, …)捆绑包时,您可以具有 org.osgi.service.jdbc.DataSourceFactory
服务,注册可用于获取给定数据库的数据源(请参阅 第 6.2.1 节 “特定于数据库及通用数据源”)。但是您还没有实际数据源。
mvn:org.ops4j.pax.jdbc/pax-jdbc-config/1.3.0
捆绑包提供了一个可执行以下操作的托管服务工厂:
跟踪
org.osgi.service.jdbc.DataSource
factory OSGi 服务,以调用其方法:public DataSource createDataSource(Properties props); public XADataSource createXADataSource(Properties props); public ConnectionPoolDataSource createConnectionPoolDataSource(Properties props);
-
跟踪
org.ops4j.datasource
factory PID,以收集以上方法需要的属性。如果您使用 Configuration Admin 服务可用的任何方法创建 工厂配置,例如,创建一个${karaf.etc}/org.ops4j.datasource-mysql.cfg
文件,您可以执行最后一步公开实际的数据库特定数据源。
以下是从全新的 Fuse 安装开始的详细 规范 步骤指南。
您可以明确安装捆绑包而不是功能,以精确显示需要哪些捆绑包。为方便起见,PAX JDBC 项目为几个数据库产品和配置方法提供了功能。
使用
/META-INF/services/java.sql.Driver
安装 JDBC 驱动程序:karaf@root()> install -s mvn:mysql/mysql-connector-java/5.1.34 Bundle ID: 223
安装 OSGi JDBC 服务捆绑包和
pax-jdbc-mysql
捆绑包,该捆绑包注册 中介org.osgi.service.jdbc.DataSourceFactory
:karaf@root()> install -s mvn:org.osgi/org.osgi.service.jdbc/1.0.0 Bundle ID: 224 karaf@root()> install -s mvn:org.ops4j.pax.jdbc/pax-jdbc-mysql/1.3.0 Bundle ID: 225 karaf@root()> service:list org.osgi.service.jdbc.DataSourceFactory [org.osgi.service.jdbc.DataSourceFactory] ----------------------------------------- osgi.jdbc.driver.class = com.mysql.jdbc.Driver osgi.jdbc.driver.name = mysql service.bundleid = 225 service.id = 242 service.scope = singleton Provided by : OPS4J Pax JDBC MySQL Driver Adapter (225)
安装
pax-jdbc
捆绑包和pax-jdbc-config
捆绑包,该捆绑包跟踪org.osgi.service.jdbc.DataSourceFactory
服务和org.ops4j.datasource
factory PIDs:karaf@root()> install -s mvn:org.ops4j.pax.jdbc/pax-jdbc/1.3.0 Bundle ID: 226 karaf@root()> install -s mvn:org.ops4j.pax.jdbc/pax-jdbc-pool-common/1.3.0 Bundle ID: 227 karaf@root()> install -s mvn:org.ops4j.pax.jdbc/pax-jdbc-config/1.3.0 Bundle ID: 228 karaf@root()> bundle:services -p org.ops4j.pax.jdbc.config OPS4J Pax JDBC Config (228) provides: ------------------------------------- objectClass = [org.osgi.service.cm.ManagedServiceFactory] service.bundleid = 228 service.id = 245 service.pid = org.ops4j.datasource service.scope = singleton
创建 factory 配置 (假设 MySQL 服务器正在运行):
karaf@root()> config:edit --factory --alias mysql org.ops4j.datasource karaf@root()> config:property-set osgi.jdbc.driver.name mysql karaf@root()> config:property-set dataSourceName mysqlds karaf@root()> config:property-set url jdbc:mysql://localhost:3306/reportdb karaf@root()> config:property-set user fuse karaf@root()> config:property-set password fuse karaf@root()> config:update karaf@root()> config:list '(service.factoryPid=org.ops4j.datasource)' ---------------------------------------------------------------- Pid: org.ops4j.datasource.a7941498-9b62-4ed7-94f3-8c7ac9365313 FactoryPid: org.ops4j.datasource BundleLocation: ? Properties: dataSourceName = mysqlds felix.fileinstall.filename = file:${karaf.etc}/org.ops4j.datasource-mysql.cfg osgi.jdbc.driver.name = mysql password = fuse service.factoryPid = org.ops4j.datasource service.pid = org.ops4j.datasource.a7941498-9b62-4ed7-94f3-8c7ac9365313 url = jdbc:mysql://localhost:3306/reportdb user = fuse
检查
pax-jdbc-config
是否处理配置到javax.sql.DataSource
服务:karaf@root()> service:list javax.sql.DataSource [javax.sql.DataSource] ---------------------- dataSourceName = mysqlds felix.fileinstall.filename = file:${karaf.etc}/org.ops4j.datasource-mysql.cfg osgi.jdbc.driver.name = mysql osgi.jndi.service.name = mysqlds password = fuse pax.jdbc.managed = true service.bundleid = 228 service.factoryPid = org.ops4j.datasource service.id = 246 service.pid = org.ops4j.datasource.a7941498-9b62-4ed7-94f3-8c7ac9365313 service.scope = singleton url = jdbc:mysql://localhost:3306/reportdb user = fuse Provided by : OPS4J Pax JDBC Config (228)
您现在有一个特定于数据库的(还没有池)数据源。您已经将其注入了需要的位置。例如,您可以使用 Karaf 命令来查询数据库:
karaf@root()> feature:install -v jdbc Adding features: jdbc/[4.2.0.fuse-000237-redhat-1,4.2.0.fuse-000237-redhat-1] ... karaf@root()> jdbc:ds-list Mon May 14 08:46:22 CEST 2018 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification. Name │ Product │ Version │ URL │ Status ────────┼─────────┼─────────┼──────────────────────────────────────┼─────── mysqlds │ MySQL │ 5.7.21 │ jdbc:mysql://localhost:3306/reportdb │ OK karaf@root()> jdbc:query mysqlds 'select * from incident' Mon May 14 08:46:46 CEST 2018 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification. date │ summary │ name │ details │ id │ email ──────────────────────┼────────────┼────────┼───────────────────────────────┼────┼───────────────── 2018-02-20 08:00:00.0 │ Incident 1 │ User 1 │ This is a report incident 001 │ 1 │ user1@redhat.com 2018-02-20 08:10:00.0 │ Incident 2 │ User 2 │ This is a report incident 002 │ 2 │ user2@redhat.com 2018-02-20 08:20:00.0 │ Incident 3 │ User 3 │ This is a report incident 003 │ 3 │ user3@redhat.com 2018-02-20 08:30:00.0 │ Incident 4 │ User 4 │ This is a report incident 004 │ 4 │ user4@redhat.com
在上例中,您可以看到 MySQL 警告。这不是一个问题。可能不提供任何属性(而不仅限于 OSGi JDBC 特定属性):
karaf@root()> config:property-set --pid org.ops4j.datasource.a7941498-9b62-4ed7-94f3-8c7ac9365313 useSSL false karaf@root()> jdbc:ds-list Name │ Product │ Version │ URL │ Status ────────┼─────────┼─────────┼──────────────────────────────────────┼─────── mysqlds │ MySQL │ 5.7.21 │ jdbc:mysql://localhost:3306/reportdb │ OK
6.4.2. 处理的属性摘要
管理 工厂 PID 配置的属性传递到相关的 org.osgi.service.jdbc.DataSource
factory 实施。
通用
org.ops4j.pax.jdbc.impl.DriverDataSourceFactory
properties:
-
url
-
user
-
password
DB2
org.ops4j.pax.jdbc.db2.impl.DB2DataSource
factory 属性包含这些实施类的所有 bean 属性:
-
com.ibm.db2.jcc.DB2SimpleDataSource
-
com.ibm.db2.jcc.DB2ConnectionPoolDataSource
-
com.ibm.db2.jcc.DB2XADataSource
PostgreSQL
Nnative org.postgresql.osgi.PGDataSource
factory 属性包括 org.postgresql.PGProperty
中指定的所有属性。
HSQLDB
org.ops4j.pax.jdbc.hsqldb.impl.HsqldbDataSourceFactory
properties:
-
url
-
user
-
password
-
databaseName
所有 bean 属性
-
org.hsqldb.jdbc.JDBCDataSource
-
org.hsqldb.jdbc.pool.JDBCPooledDataSource
-
org.hsqldb.jdbc.pool.JDBCXADataSource
-
SQL 服务器和 Sybase
org.ops4j.pax.jdbc.jtds.impl.JTDSDataSource
factory 属性包含 net.sourceforge.jtds.jdbcx.JtdsDataSource
的所有属性。
SQL Server
org.ops4j.pax.jdbc.mssql.impl.MSSQLDataSource factory
属性:
-
url
-
user
-
password
-
databaseName
-
serverName
-
portNumber
所有 bean 属性
-
com.microsoft.sqlserver.jdbc.SQLServerDataSource
-
com.microsoft.sqlserver.jdbc.SQLServerConnectionPoolDataSource
-
com.microsoft.sqlserver.jdbc.SQLServerXADataSource
-
MySQL
org.ops4j.pax.jdbc.mysql.impl.MysqlDataSourceFactory
properties:
-
url
-
user
-
password
-
databaseName
-
serverName
-
portNumber
所有 bean 属性
-
com.mysql.jdbc.jdbc2.optional.MysqlDataSource
-
com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource
-
com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
-
Oracle
org.ops4j.pax.jdbc.oracle.impl.OracleDataSourceFactory
properties:
-
url
-
databaseName
-
serverName
-
user
-
password
所有 bean 属性
-
oracle.jdbc.pool.OracleDataSource
-
oracle.jdbc.pool.OracleConnectionPoolDataSource
-
oracle.jdbc.xa.client.OracleXADataSource
-
SQLite
org.ops4j.pax.jdbc.sqlite.impl.SqliteDataSourceFactory
properties:
-
url
-
databaseName
-
org.sqlite.SQLiteDataSource
的所有 bean 属性
6.4.3. pax-jdb-config 捆绑包如何处理属性
pax-jdbc-config
捆绑包处理前缀为 jdbc.
的属性。所有这些属性将删除此前缀,其余名称将随即传递。
下面是一下例子,从全新的 Fuse 安装开始:
karaf@root()> install -s mvn:mysql/mysql-connector-java/5.1.34 Bundle ID: 223 karaf@root()> install -s mvn:org.osgi/org.osgi.service.jdbc/1.0.0 Bundle ID: 224 karaf@root()> install -s mvn:org.ops4j.pax.jdbc/pax-jdbc-mysql/1.3.0 Bundle ID: 225 karaf@root()> install -s mvn:org.ops4j.pax.jdbc/pax-jdbc/1.3.0 Bundle ID: 226 karaf@root()> install -s mvn:org.ops4j.pax.jdbc/pax-jdbc-pool-common/1.3.0 Bundle ID: 227 karaf@root()> install -s mvn:org.ops4j.pax.jdbc/pax-jdbc-config/1.3.0 Bundle ID: 228 karaf@root()> config:edit --factory --alias mysql org.ops4j.datasource karaf@root()> config:property-set osgi.jdbc.driver.name mysql karaf@root()> config:property-set dataSourceName mysqlds karaf@root()> config:property-set dataSourceType DataSource karaf@root()> config:property-set jdbc.url jdbc:mysql://localhost:3306/reportdb karaf@root()> config:property-set jdbc.user fuse karaf@root()> config:property-set jdbc.password fuse karaf@root()> config:property-set jdbc.useSSL false karaf@root()> config:update karaf@root()> config:list '(service.factoryPid=org.ops4j.datasource)' ---------------------------------------------------------------- Pid: org.ops4j.datasource.7c3ee718-7309-46a0-ae3a-64b38b17a0a3 FactoryPid: org.ops4j.datasource BundleLocation: ? Properties: dataSourceName = mysqlds dataSourceType = DataSource felix.fileinstall.filename = file:/data/servers/7.11.1.fuse-7_11_1-00013-redhat-00003/etc/org.ops4j.datasource-mysql.cfg jdbc.password = fuse jdbc.url = jdbc:mysql://localhost:3306/reportdb jdbc.useSSL = false jdbc.user = fuse osgi.jdbc.driver.name = mysql service.factoryPid = org.ops4j.datasource service.pid = org.ops4j.datasource.7c3ee718-7309-46a0-ae3a-64b38b17a0a3 karaf@root()> service:list javax.sql.DataSource [javax.sql.DataSource] ---------------------- dataSourceName = mysqlds dataSourceType = DataSource felix.fileinstall.filename = file:${karaf.etc}/org.ops4j.datasource-mysql.cfg jdbc.password = fuse jdbc.url = jdbc:mysql://localhost:3306/reportdb jdbc.user = fuse jdbc.useSSL = false osgi.jdbc.driver.name = mysql osgi.jndi.service.name = mysqlds pax.jdbc.managed = true service.bundleid = 228 service.factoryPid = org.ops4j.datasource service.id = 246 service.pid = org.ops4j.datasource.7c3ee718-7309-46a0-ae3a-64b38b17a0a3 service.scope = singleton Provided by : OPS4J Pax JDBC Config (228)
pax-jdbc-config
捆绑包需要以下属性:
-
osgi.jdbc.driver.name
-
dataSourceName
-
dataSourceType
定位并调用相关的 org.osgi.service.jdbc.DataSourceFactory
方法。带有 jdbc.
前缀的属性传递(在删除前缀后),例如 org.osgi.service.jdbc.DataSource factory.createDataSource(properties)
。但是,这些属性会在没有删除前缀的情况下添加,例如 javax.sql.DataSource
OSGi 服务的属性。
6.5. 使用 JDBC 控制台命令
Fuse 提供 jdbc
功能,该功能在 jdbc:*
范围内包含 shell 命令。前面的示例显示了 jdbc:query
的用法。还有一些命令隐藏了创建 Configuration Admin 配置的必要命令。
从一个新 Fuse 实例开始,您可以使用一个通用 DataSource factory 服务注册特定于数据库的数据源
,如下所示:
karaf@root()> feature:install jdbc karaf@root()> jdbc:ds-factories Name │ Class │ Version ─────┼───────┼──────── karaf@root()> install -s mvn:mysql/mysql-connector-java/5.1.34 Bundle ID: 228 karaf@root()> jdbc:ds-factories Name │ Class │ Version ───────────────┼─────────────────────────────────────────┼──────── com.mysql.jdbc │ com.mysql.jdbc.Driver │ 5.1.34 com.mysql.jdbc │ com.mysql.fabric.jdbc.FabricMySQLDriver │ 5.1.34
以下是注册 MySQL 特定 DataSource factory 服务的示例
:
karaf@root()> feature:repo-add mvn:org.ops4j.pax.jdbc/pax-jdbc-features/1.3.0/xml/features-gpl Adding feature url mvn:org.ops4j.pax.jdbc/pax-jdbc-features/1.3.0/xml/features-gpl karaf@root()> feature:install pax-jdbc-mysql karaf@root()> la -l|grep mysql 232 │ Active │ 80 │ 5.1.34 │ mvn:mysql/mysql-connector-java/5.1.34 233 │ Active │ 80 │ 1.3.0 │ mvn:org.ops4j.pax.jdbc/pax-jdbc-mysql/1.3.0 karaf@root()> jdbc:ds-factories Name │ Class │ Version ───────────────┼─────────────────────────────────────────┼──────── com.mysql.jdbc │ com.mysql.jdbc.Driver │ 5.1.34 mysql │ com.mysql.jdbc.Driver │ com.mysql.jdbc │ com.mysql.fabric.jdbc.FabricMySQLDriver │ 5.1.34
上表中可能会令人困惑,但前提是,只有 pax-jdbc-数据库
捆绑包之一可以注册 org.osgi.service.jdbc.DataSourceFactory
实例,它们可以创建标准/XA/连接池数据源,不 简单地委托给 java.sql.Driver.connect()
。
以下示例创建并检查 MySQL 数据源:
karaf@root()> jdbc:ds-create -dt DataSource -dn mysql -url 'jdbc:mysql://localhost:3306/reportdb?useSSL=false' -u fuse -p fuse mysqlds karaf@root()> jdbc:ds-list Name │ Product │ Version │ URL │ Status ────────┼─────────┼─────────┼───────────────────────────────────────────────────┼─────── mysqlds │ MySQL │ 5.7.21 │ jdbc:mysql://localhost:3306/reportdb?useSSL=false │ OK karaf@root()> jdbc:query mysqlds 'select * from incident' date │ summary │ name │ details │ id │ email ──────────────────────┼────────────┼────────┼───────────────────────────────┼────┼───────────────── 2018-02-20 08:00:00.0 │ Incident 1 │ User 1 │ This is a report incident 001 │ 1 │ user1@redhat.com 2018-02-20 08:10:00.0 │ Incident 2 │ User 2 │ This is a report incident 002 │ 2 │ user2@redhat.com 2018-02-20 08:20:00.0 │ Incident 3 │ User 3 │ This is a report incident 003 │ 3 │ user3@redhat.com 2018-02-20 08:30:00.0 │ Incident 4 │ User 4 │ This is a report incident 004 │ 4 │ user4@redhat.com karaf@root()> config:list '(service.factoryPid=org.ops4j.datasource)' ---------------------------------------------------------------- Pid: org.ops4j.datasource.55b18993-de4e-4e0b-abb2-a4c13da7f78b FactoryPid: org.ops4j.datasource BundleLocation: mvn:org.ops4j.pax.jdbc/pax-jdbc-config/1.3.0 Properties: dataSourceName = mysqlds dataSourceType = DataSource osgi.jdbc.driver.name = mysql password = fuse service.factoryPid = org.ops4j.datasource service.pid = org.ops4j.datasource.55b18993-de4e-4e0b-abb2-a4c13da7f78b url = jdbc:mysql://localhost:3306/reportdb?useSSL=false user = fuse
如您所见,已为您创建 org.ops4j.datasource
factory PID。但是,它不会自动存储在 ${karaf.etc}
中,这可以通过 config:update
。
6.6. 使用加密配置值
pax-jdbc-config
功能可以处理加密值的 Configuration Admin 配置。一个常用的解决方案是使用 Jasypt 加密服务,它们也被 Blueprint 使用。
如果有任何 org.jasypt.encryption.encryption.StringEncryptor
服务通过任何 别名
服务属性注册,您可以在数据源 工厂 PID 并使用加密密码的情况下退出它。下面是一个示例:
felix.fileinstall.filename = */etc/org.ops4j.datasource-mysql.cfg dataSourceName = mysqlds dataSourceType = DataSource decryptor = my-jasypt-decryptor osgi.jdbc.driver.name = mysql url = jdbc:mysql://localhost:3306/reportdb?useSSL=false user = fuse password = ENC(<encrypted-password>)
用于查找解密器服务的服务过滤器为 (&(objectClass=org.jasypt.encryption.StringEncryptor)(alias=<alias>))
,其中 < alias
> 是来自数据源配置 工厂 PID 的 decryptor
属性的值。
6.7. 使用 JDBC 连接池
本节提供使用 JDBC 连接池的介绍,然后显示如何使用这些连接池模块:
本章介绍数据源管理内部的信息。虽然提供了 DBCP2 连接池功能的信息,但请记住,这个连接池提供正确的 JTA enlist 功能,但不提供 XA 恢复。
为确保 XA 恢复 就位,使用 pax-jdbc-pool-transx
或 pax-jdbc-pool-narayana
连接池模块。
6.7.1. 使用 JDBC 连接池简介
前面的示例显示了如何注册特定于数据库的数据源 工厂。由于 数据源 本身是连接的工厂,因此 org.osgi.service.jdbc.DataSource
onnectionFactoryy 可以被视为一个 元数据工厂,应能够生成三种类型的数据源,再加上一种好处,它是 java.sql.Driver
):
-
javax.sql.DataSource
-
javax.sql.ConnectionPoolDataSource
-
javax.sql.XADataSource
例如,pax-jdbc-mysql
注册一个 org.ops4j.pax.jdbc.mysql.impl.MysqlDataSource
factory,它将生成:
-
javax.sql.DataSource
→com.mysql.jdbc.jdbc2.optional.MysqlDataSource
-
javax.sql.ConnectionPoolDataSource
→com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource
-
javax.sql.XADataSource
→com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
-
java.sql.Driver
→com.mysql.jdbc.Driver
PostgreSQL 驱动程序本身实施 OSGi JDBC 服务并生成:
-
javax.sql.DataSource
→org.postgresql.jdbc2.optional.PoolingDataSource
(如果指定了池相关的属性)或org.postgresql.jdbc2.optional.SimpleDataSource
-
javax.sql.ConnectionPoolDataSource
→org.postgresql.jdbc2.optional.ConnectionPool
-
javax.sql.XADataSource
→org.postgresql.xa.PGXADataSource
-
java.sql.Driver
→org.postgresql.Driver
如 规范 DataSource 示例 所示,任何 池、通用 数据源(如果希望在 JTA 环境中工作)都需要一个 数据库特定的 数据源来实际获取(XA)连接。
我们已拥有后者,我们需要实际的通用、可靠的连接池。
规范 DataSource 示例 显示如何使用特定数据库数据源配置通用池。pax-jdbc-pool-*
捆绑包与上述 org.osgi.service.jdbc.DataSourceFactory
服务一起工作。
正如 OSGI Enterprise R6 JDBC 规范提供 org.osgi.service.jdbc.DataSourceFactory
标准接口,pax-jdbc-pool-common
提供 专有 org.ops4j.pax.jdbc.pool.common.PooledDataSourceFactory
接口:
public interface PooledDataSourceFactory { javax.sql.DataSource create(org.osgi.service.jdbc.DataSourceFactory dsf, Properties config) }
这个界面完全符合这个重要备注,前文中会值得重复操作:
即使应用使用 XA 事务和/或连接池,应用程序也会与 javax.sql.DataSource
进行交互,而不是两个其他 JDBC 数据源接口。
这个接口只是从特定数据库(非后台数据)创建一个池化数据源。或者更精确地说,它是一个数据源工厂 (元数据工厂),将特定于数据库的数据源的工厂转变为池化数据源的因素。
使用 org.osgi.service.jdbc.DataSources.jdbc.DataSourcey 服务可以防止应用程序为
Source 对象配置池,因此无法为 javax.sql.DataSource
Sourcejavax.sql.DataSource
对象返回池。
下表显示了哪些捆绑包注册了池化数据源因素。在表格中,o.o.p.j.p
的实例代表 org.ops4j.pax.jdbc.pool
。
捆绑包(Bundle) | PooledDataSourceFactory | 池密钥 |
---|---|---|
|
|
|
|
|
|
|
|
|
以上捆绑包只安装数据源因子,而不是数据源本身。应用程序需要调用 javax.sql.DataSource create(org.osgi.service.jdbc.DataSourceFactory dsf, Properties config)
方法的内容。
6.7.2. 使用 dbcp2
连接池模块
关于通用数据源的小节 提供了有关如何使用和配置 Apache Commons DBCP 模块的示例。本节演示了如何在 Fuse OSGi 环境中进行此操作。
考虑 第 6.4.1 节 “PAX- JDBC 配置服务” 捆绑包。除了跟踪以下内容外:
-
org.osgi.service.jdbc.DataSourceFactory
services -
org.ops4j.datasource
factory PIDs
该捆绑包还跟踪 org.ops4j.pax.jdbc.pool.common.PooledDataSource elementy
的实例,这些实例由 pax-jdbc-pool-*
捆绑包之一注册。
如果 factory 配置中包含 pool
属性,则 pax-jdbc-config
捆绑包注册的最终数据源是特定于数据库的数据,但是如果 pool=dbcp2
之一嵌套在以下之一中:
-
org.apache.commons.dbcp2.PoolingDataSource
-
org.apache.commons.dbcp2.managed.ManagedDataSource
这与 通用数据源示例 一致。除 pool
属性外,以及 boolean xa
属性(选择非 xa 或 xa 数据源外,org.ops4j.datasource
factory PID 可以包含 前缀 属性:
-
pool.*
-
factory.*
使用每个属性的位置取决于使用哪个 pax-jdbc-pool-*
捆绑包。对于 DBCP2,它是:
-
pool.*
: bean 属性org.apache.commons.pool2.impl.GenericObjectPoolConfig
(xa 和 non-xa 情境) -
factory.*
:org.apache.commons.dbcp2.managed.PoolableManagedConnectionFactory
(xa)或org.apache.commons.dbcp2.PoolableConnectionFactory
(non-xa)的属性
6.7.2.1. BasicDataSource 的配置属性
下表列出了 BasicDataSource 的通用配置属性。
参数 | 默认 | 描述 |
---|---|---|
| 要传递给 JDBC 驱动程序的连接用户名,以建立连接。 | |
| 要传递给 JDBC 驱动程序的连接密码,以建立连接。 | |
| 要传递给 JDBC 驱动程序的连接 URL 来建立连接。 | |
| 要使用的 JDBC 驱动程序的完全限定 Java 类名称。 | |
| 0 | 池启动时创建的初始连接数。 |
| 8 | 可同时从这个池中分配的最大活跃连接数,或对没有限制的负数。 |
| 8 | 池中可以保持闲置的最大连接数,没有释放的额外连接数,或对没有限制的负数。 |
| 0 | 池中可以保持闲置的最少连接数,如果没有创建额外数量,或零可用于创建 none。 |
| 无限期 | 在引发异常前,池要等待的最大毫秒数(当没有可用的连接时),然后再放弃异常或 -1 以无限期地等待连接。 |
| 在将连接返回给调用者之前,将用于验证来自这个池的连接的 SQL 查询。如果指定,此查询 MUST 是返回至少一个行的 SQL SELECT 语句。如果没有指定,则连接将通过调用 isValid()方法进行验证。 | |
| 没有超时 | 连接验证查询失败前的超时时间(以秒为单位)。如果设置为正值,则该值将通过执行验证查询的声明的 setQueryTimeout 方法传递到驱动程序。 |
| false | 创建后验证对象是否会被验证。如果对象无法验证,则触发对象的创建尝试将失败。 |
| true | 在从池中分离前验证对象是否会被验证。如果对象无法验证,它将从池中丢弃,我们将尝试再浏览。 |
| false | 表示在返回到池之前验证对象是否会被验证。 |
| false | 表明对象是否由空闲对象驱除器(若有)验证。如果对象无法验证,它将从池中丢弃。 |
| -1 | 空闲对象驱除器线程之间休眠的毫秒数。当非正向时,将不会运行闲置对象驱除或线程。 |
| 3 | 每次运行闲置对象驱除器线程时要检查的对象数量(若有)。 |
| 1000 * 60 * 30 | 对象在有空闲对象驱除或满足驱除前可能会处于空闲状态的最小时间(若有)。 |
6.7.2.2. 如何配置 DBCP2 池示例
以下是一个现实示例(如使用 SSL=false
)配置 DBCP2 池(org.ops4j.datasource-mysql
factory PID),它使用带有 jdbc.
-prefixed 属性的便捷语法:
# Configuration for pax-jdbc-config to choose and configure specific org.osgi.service.jdbc.DataSourceFactory dataSourceName = mysqlds dataSourceType = DataSource osgi.jdbc.driver.name = mysql jdbc.url = jdbc:mysql://localhost:3306/reportdb jdbc.user = fuse jdbc.password = fuse jdbc.useSSL = false # Hints for pax-jdbc-config to use org.ops4j.pax.jdbc.pool.common.PooledDataSourceFactory pool = dbcp2 xa = false # dbcp2 specific configuration of org.apache.commons.pool2.impl.GenericObjectPoolConfig pool.minIdle = 10 pool.maxTotal = 100 pool.initialSize = 8 pool.blockWhenExhausted = true pool.maxWaitMillis = 2000 pool.testOnBorrow = true pool.testWhileIdle = false pool.timeBetweenEvictionRunsMillis = 120000 pool.evictionPolicyClassName = org.apache.commons.pool2.impl.DefaultEvictionPolicy # dbcp2 specific configuration of org.apache.commons.dbcp2.PoolableConnectionFactory factory.maxConnLifetimeMillis = 30000 factory.validationQuery = select schema_name from information_schema.schemata factory.validationQueryTimeout = 2
在上述配置中,池和
xa
键是 提示 (服务过滤器属性)用于选择一个注册的 org.ops4j.pax.jdbc.pool.common.PooledDataSource factorsy
服务。对于 DBCP2,这是:
karaf@root()> feature:install pax-jdbc-pool-dbcp2 karaf@root()> bundle:services -p org.ops4j.pax.jdbc.pool.dbcp2 OPS4J Pax JDBC Pooling DBCP2 (230) provides: -------------------------------------------- objectClass = [org.ops4j.pax.jdbc.pool.common.PooledDataSourceFactory] pool = dbcp2 service.bundleid = 230 service.id = 337 service.scope = singleton xa = false ----- objectClass = [org.ops4j.pax.jdbc.pool.common.PooledDataSourceFactory] pool = dbcp2 service.bundleid = 230 service.id = 338 service.scope = singleton xa = true
为了完整性,以下是添加至 上一示例 中的连接池配置的完整示例。这假设您正在从全新的 Fuse 安装开始。
安装 JDBC 驱动程序:
karaf@root()> install -s mvn:mysql/mysql-connector-java/5.1.34 Bundle ID: 223
安装
jdbc
、pax-jdbc-mysql
和pax-jdbc-pool-dbcp2
功能:karaf@root()> feature:repo-add mvn:org.ops4j.pax.jdbc/pax-jdbc-features/1.3.0/xml/features-gpl Adding feature url mvn:org.ops4j.pax.jdbc/pax-jdbc-features/1.3.0/xml/features-gpl karaf@root()> feature:install jdbc pax-jdbc-mysql pax-jdbc-pool-dbcp2 karaf@root()> service:list org.osgi.service.jdbc.DataSourceFactory ... [org.osgi.service.jdbc.DataSourceFactory] ----------------------------------------- osgi.jdbc.driver.class = com.mysql.jdbc.Driver osgi.jdbc.driver.name = mysql service.bundleid = 232 service.id = 328 service.scope = singleton Provided by : OPS4J Pax JDBC MySQL Driver Adapter (232) karaf@root()> service:list org.ops4j.pax.jdbc.pool.common.PooledDataSourceFactory [org.ops4j.pax.jdbc.pool.common.PooledDataSourceFactory] -------------------------------------------------------- pool = dbcp2 service.bundleid = 233 service.id = 324 service.scope = singleton xa = false Provided by : OPS4J Pax JDBC Pooling DBCP2 (233) [org.ops4j.pax.jdbc.pool.common.PooledDataSourceFactory] -------------------------------------------------------- pool = dbcp2 service.bundleid = 233 service.id = 332 service.scope = singleton xa = true Provided by : OPS4J Pax JDBC Pooling DBCP2 (233)
创建 factory 配置 :
karaf@root()> config:edit --factory --alias mysql org.ops4j.datasource karaf@root()> config:property-set osgi.jdbc.driver.name mysql karaf@root()> config:property-set dataSourceName mysqlds karaf@root()> config:property-set dataSourceType DataSource karaf@root()> config:property-set jdbc.url jdbc:mysql://localhost:3306/reportdb karaf@root()> config:property-set jdbc.user fuse karaf@root()> config:property-set jdbc.password fuse karaf@root()> config:property-set jdbc.useSSL false karaf@root()> config:property-set pool dbcp2 karaf@root()> config:property-set xa false karaf@root()> config:property-set pool.minIdle 2 karaf@root()> config:property-set pool.maxTotal 10 karaf@root()> config:property-set pool.blockWhenExhausted true karaf@root()> config:property-set pool.maxWaitMillis 2000 karaf@root()> config:property-set pool.testOnBorrow true karaf@root()> config:property-set pool.testWhileIdle alse karaf@root()> config:property-set pool.timeBetweenEvictionRunsMillis 120000 karaf@root()> config:property-set factory.validationQuery 'select schema_name from information_schema.schemata' karaf@root()> config:property-set factory.validationQueryTimeout 2 karaf@root()> config:update
检查
pax-jdbc-config
是否处理配置到javax.sql.DataSource
服务:karaf@root()> service:list javax.sql.DataSource [javax.sql.DataSource] ---------------------- dataSourceName = mysqlds dataSourceType = DataSource factory.validationQuery = select schema_name from information_schema.schemata factory.validationQueryTimeout = 2 felix.fileinstall.filename = file:${karaf.etc}/org.ops4j.datasource-mysql.cfg jdbc.password = fuse jdbc.url = jdbc:mysql://localhost:3306/reportdb jdbc.user = fuse jdbc.useSSL = false osgi.jdbc.driver.name = mysql osgi.jndi.service.name = mysqlds pax.jdbc.managed = true pool.blockWhenExhausted = true pool.maxTotal = 10 pool.maxWaitMillis = 2000 pool.minIdle = 2 pool.testOnBorrow = true pool.testWhileIdle = alse pool.timeBetweenEvictionRunsMillis = 120000 service.bundleid = 225 service.factoryPid = org.ops4j.datasource service.id = 338 service.pid = org.ops4j.datasource.fd7aa3a1-695b-4342-b0d6-23d018a46fbb service.scope = singleton Provided by : OPS4J Pax JDBC Config (225)
使用数据源:
karaf@root()> jdbc:query mysqlds 'select * from incident' date │ summary │ name │ details │ id │ email ──────────────────────┼────────────┼────────┼───────────────────────────────┼────┼───────────────── 2018-02-20 08:00:00.0 │ Incident 1 │ User 1 │ This is a report incident 001 │ 1 │ user1@redhat.com 2018-02-20 08:10:00.0 │ Incident 2 │ User 2 │ This is a report incident 002 │ 2 │ user2@redhat.com 2018-02-20 08:20:00.0 │ Incident 3 │ User 3 │ This is a report incident 003 │ 3 │ user3@redhat.com 2018-02-20 08:30:00.0 │ Incident 4 │ User 4 │ This is a report incident 004 │ 4 │ user4@redhat.com
6.7.3. 使用 narayana
连接池模块
pax-jdbc-pool-narayna
模块几乎都是 pax-jdbc-pool-dbcp2
。它为 XA 和非 XA 场景安装 DBCP2- 特定 org.ops4j.pax.jdbc.pool.common.PooledDataSourceFactory
。唯一的 区别是,在 XA 情景中,会有一个额外的集成点。org.jboss.tm.XAResourceRecovery
OSGi 服务由 com.arjuna.ats.arjuna.recovery.recoveryManager(recovery.RecoveryManager
)进行注册,这是 Narayana 事务管理器的一部分。
6.7.4. 使用 transx
连接池模块
pax-jdbc-pool-transx
捆绑包基于其在 pax-transx-jdbc
bundle 上的 org.ops4j.pax.jdbc.pool.common.PooledDataSourceFactory
服务。pax-transx-jdbc
捆绑包通过使用 org.ops4j.pax.transx.jdbc.ManagedDataSourceBuilder
工具来创建 javax.sql.DataSource
SourceSourceSourceSources。这是 JCA(Java™ 连接器架构)解决方案,后文中 所述。
6.8. 将数据源部署为工件
本章介绍了 OSGi JDBC 服务,演示了 pax-jdbc
捆绑包在注册数据库特定和通用数据源方面以及如何从 OSGi 服务与配置配置的角度看。虽然可以使用 Configuration Admin factory PIDs( 对 pax-jdbc-config
捆绑包的帮助)进行配置,但这两种数据源 的配置通常最好使用 部署方法。
在部署方法中,javax.sql.DataSource
服务直接由应用程序代码注册,通常是在 Blueprint 容器内。蓝图 XML 可能是一般的 OSGi 捆绑包的一部分,通过使用 mvn:
URI 进行安装,并存储在 Maven 存储库中(本地或远程)。与配置管理配置进行比较,版本控制此类捆绑包要更容易。
pax-jdbc-config
捆绑包版本 1.3.0 为数据源配置添加部署方法。应用程序开发人员注册 javax.sql.(XA)DataSource
服务(通常使用 Bluerpint XML)并指定服务属性。pax-jdbc-config
捆绑包会检测到此类注册的数据库特定数据源,并使用服务属性(使用服务属性)将服务嵌套在通用、非特定于数据库的连接池中。
为保证完整性,下面是三种使用 Blueprint XML 的 部署方法。Fuse 提供了一个 快速入门
下载,其中包含了 Fuse 不同方面的不同示例。您可以从 Fuse Software Downloads 页面下载 快速入门
zip 文件。
将快速入门 zip 文件的内容提取到本地文件夹中。
在以下示例中,Quickstart /persistence
目录被称为 $PQ_HOME
。
6.8.1. 手动部署数据源
这个手动部署数据源的示例使用了基于 docker 的 PostgreSQL 安装。在此方法中,不需要 pax-jdbc-config
。应用程序代码负责注册特定于数据库及通用数据源。
需要三个捆绑包:
-
mvn:org.postgresql/postgresql/42.2.5
-
mvn:org.apache.commons/commons-pool2/2.5.0
-
mvn:org.apache.commons/commons-dbcp2/2.1.1
<!-- Database-specific, non-pooling, non-enlisting javax.sql.XADataSource --> <bean id="postgresql" class="org.postgresql.xa.PGXADataSource"> <property name="url" value="jdbc:postgresql://localhost:5432/reportdb" /> <property name="user" value="fuse" /> <property name="password" value="fuse" /> <property name="currentSchema" value="report" /> <property name="connectTimeout" value="5" /> </bean> <!-- Fuse/Karaf exports this service from fuse-pax-transx-tm-narayana bundle --> <reference id="tm" interface="javax.transaction.TransactionManager" /> <!-- Non database-specific, generic, pooling, enlisting javax.sql.DataSource --> <bean id="pool" class="org.apache.commons.dbcp2.managed.BasicManagedDataSource"> <property name="xaDataSourceInstance" ref="postgresql" /> <property name="transactionManager" ref="tm" /> <property name="minIdle" value="3" /> <property name="maxTotal" value="10" /> <property name="validationQuery" value="select schema_name, schema_owner from information_schema.schemata" /> </bean> <!-- Expose datasource to use by application code (like Camel, Spring, ...) --> <service interface="javax.sql.DataSource" ref="pool"> <service-properties> <entry key="osgi.jndi.service.name" value="jdbc/postgresql" /> </service-properties> </service>
以上蓝图 XML 片段与 规范 DataSource 示例 匹配。以下是显示如何使用它的 shell 命令:
karaf@root()> install -s mvn:org.postgresql/postgresql/42.2.5 Bundle ID: 233 karaf@root()> install -s mvn:org.apache.commons/commons-pool2/2.5.0 Bundle ID: 224 karaf@root()> install -s mvn:org.apache.commons/commons-dbcp2/2.1.1 Bundle ID: 225 karaf@root()> install -s blueprint:file://$PQ_HOME/databases/blueprints/postgresql-manual.xml Bundle ID: 226 karaf@root()> bundle:services -p 226 Bundle 226 provides: -------------------- objectClass = [javax.sql.DataSource] osgi.jndi.service.name = jdbc/postgresql osgi.service.blueprint.compname = pool service.bundleid = 226 service.id = 242 service.scope = bundle ----- objectClass = [org.osgi.service.blueprint.container.BlueprintContainer] osgi.blueprint.container.symbolicname = postgresql-manual.xml osgi.blueprint.container.version = 0.0.0 service.bundleid = 226 service.id = 243 service.scope = singleton karaf@root()> feature:install jdbc karaf@root()> jdbc:ds-list Name │ Product │ Version │ URL │ Status ────────────────┼────────────┼───────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼─────── jdbc/postgresql │ PostgreSQL │ 10.3 (Debian 10.3-1.pgdg90+1) │ jdbc:postgresql://localhost:5432/reportdb?prepareThreshold=5&preparedStatementCacheQueries=256&preparedStatementCacheSizeMiB=5&databaseMetadataCacheFields=65536&databaseMetadataCacheFieldsMiB=5&defaultRowFetchSize=0&binaryTransfer=true&readOnly=false&binaryTransferEnable=&binaryTransferDisable=&unknownLength=2147483647&logUnclosedConnections=false&disableColumnSanitiser=false&tcpKeepAlive=false&loginTimeout=0&connectTimeout=5&socketTimeout=0&cancelSignalTimeout=10&receiveBufferSize=-1&sendBufferSize=-1&ApplicationName=PostgreSQL JDBC Driver&jaasLogin=true&useSpnego=false&gsslib=auto&sspiServiceClass=POSTGRES&allowEncodingChanges=false¤tSchema=report&targetServerType=any&loadBalanceHosts=false&hostRecheckSeconds=10&preferQueryMode=extended&autosave=never&reWriteBatchedInserts=false │ OK karaf@root()> jdbc:query jdbc/postgresql 'select * from incident'; date │ summary │ name │ details │ id │ email ────────────────────┼────────────┼────────┼───────────────────────────────┼────┼───────────────── 2018-02-20 08:00:00 │ Incident 1 │ User 1 │ This is a report incident 001 │ 1 │ user1@redhat.com 2018-02-20 08:10:00 │ Incident 2 │ User 2 │ This is a report incident 002 │ 2 │ user2@redhat.com 2018-02-20 08:20:00 │ Incident 3 │ User 3 │ This is a report incident 003 │ 3 │ user3@redhat.com 2018-02-20 08:30:00 │ Incident 4 │ User 4 │ This is a report incident 004 │ 4 │ user4@redhat.com
如上方列表所示,Bartner 捆绑包将导出 javax.sql.DataSource
服务,该服务是一个通用、非特定于数据库的连接池。特定于数据库的 javax.sql.XADataSource
捆绑包 没有 作为 OSGi 服务注册,因为 Blueprint XML 没有明确的 < service ref="postgresql">
声明。
6.8.2. 数据源的工厂部署
数据源的工厂部署使用 pax-jdbc-config
捆绑包以规范的方式部署。这与 Fuse 6.x 中推荐的方法有点不同,它要求将配置用作服务属性。
下面是蓝图 XML 示例:
<!-- A database-specific org.osgi.service.jdbc.DataSourceFactory that can create DataSource/XADataSource/ /ConnectionPoolDataSource/Driver using properties. It is registered by pax-jdbc-* or for example mvn:org.postgresql/postgresql/42.2.5 bundle natively. --> <reference id="dataSourceFactory" interface="org.osgi.service.jdbc.DataSourceFactory" filter="(osgi.jdbc.driver.class=org.postgresql.Driver)" /> <!-- Non database-specific org.ops4j.pax.jdbc.pool.common.PooledDataSourceFactory that can create pooled data sources using some org.osgi.service.jdbc.DataSourceFactory. dbcp2 pool is registered by pax-jdbc-pool-dbcp2 bundle. --> <reference id="pooledDataSourceFactory" interface="org.ops4j.pax.jdbc.pool.common.PooledDataSourceFactory" filter="(&(pool=dbcp2)(xa=true))" /> <!-- Finally, use both factories to expose pooled, xa-aware data source. --> <bean id="pool" factory-ref="pooledDataSourceFactory" factory-method="create"> <argument ref="dataSourceFactory" /> <argument> <props> <!-- Properties needed by postgresql-specific org.osgi.service.jdbc.DataSourceFactory. Cannot prepend them with 'jdbc.' prefix as the DataSourceFactory is implemented directly by PostgreSQL driver, not by pax-jdbc-* bundle. --> <prop key="url" value="jdbc:postgresql://localhost:5432/reportdb" /> <prop key="user" value="fuse" /> <prop key="password" value="fuse" /> <prop key="currentSchema" value="report" /> <prop key="connectTimeout" value="5" /> <!-- Properties needed by dbcp2-specific org.ops4j.pax.jdbc.pool.common.PooledDataSourceFactory --> <prop key="pool.minIdle" value="2" /> <prop key="pool.maxTotal" value="10" /> <prop key="pool.blockWhenExhausted" value="true" /> <prop key="pool.maxWaitMillis" value="2000" /> <prop key="pool.testOnBorrow" value="true" /> <prop key="pool.testWhileIdle" value="false" /> <prop key="factory.validationQuery" value="select schema_name from information_schema.schemata" /> <prop key="factory.validationQueryTimeout" value="2" /> </props> </argument> </bean> <!-- Expose data source for use by application code (such as Camel, Spring, ...). --> <service interface="javax.sql.DataSource" ref="pool"> <service-properties> <entry key="osgi.jndi.service.name" value="jdbc/postgresql" /> </service-properties> </service>
本例使用 factory an 使用数据源创建数据源。您不需要显式引用 javax.transaction.TransactionManager
服务,因为它由 XA-aware PooledDataSourceFactory
内部跟踪。
以下示例在 Fuse/Karaf shell 中相同。
要使原生 org.osgi.service.jdbc.DataSourcFactory
捆绑包注册,请安装 mvn:org.osgi/org.osgi.service.jdbc/1.0.0
,然后安装 PostgreSQL 驱动程序。
karaf@root()> feature:install jdbc pax-jdbc-config pax-jdbc-pool-dbcp2 karaf@root()> install -s mvn:org.postgresql/postgresql/42.2.5 Bundle ID: 232 karaf@root()> install -s blueprint:file://$PQ_HOME/databases/blueprints/postgresql-pax-jdbc-factory-dbcp2.xml Bundle ID: 233 karaf@root()> bundle:services -p 233 Bundle 233 provides: -------------------- objectClass = [javax.sql.DataSource] osgi.jndi.service.name = jdbc/postgresql osgi.service.blueprint.compname = pool service.bundleid = 233 service.id = 336 service.scope = bundle ----- objectClass = [org.osgi.service.blueprint.container.BlueprintContainer] osgi.blueprint.container.symbolicname = postgresql-pax-jdbc-factory-dbcp2.xml osgi.blueprint.container.version = 0.0.0 service.bundleid = 233 service.id = 337 service.scope = singleton karaf@root()> jdbc:ds-list Name │ Product │ Version │ URL │ Status ────────────────┼────────────┼───────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼─────── jdbc/postgresql │ PostgreSQL │ 10.3 (Debian 10.3-1.pgdg90+1) │ jdbc:postgresql://localhost:5432/reportdb?prepareThreshold=5&preparedStatementCacheQueries=256&preparedStatementCacheSizeMiB=5&databaseMetadataCacheFields=65536&databaseMetadataCacheFieldsMiB=5&defaultRowFetchSize=0&binaryTransfer=true&readOnly=false&binaryTransferEnable=&binaryTransferDisable=&unknownLength=2147483647&logUnclosedConnections=false&disableColumnSanitiser=false&tcpKeepAlive=false&loginTimeout=0&connectTimeout=5&socketTimeout=0&cancelSignalTimeout=10&receiveBufferSize=-1&sendBufferSize=-1&ApplicationName=PostgreSQL JDBC Driver&jaasLogin=true&useSpnego=false&gsslib=auto&sspiServiceClass=POSTGRES&allowEncodingChanges=false¤tSchema=report&targetServerType=any&loadBalanceHosts=false&hostRecheckSeconds=10&preferQueryMode=extended&autosave=never&reWriteBatchedInserts=false │ OK karaf@root()> jdbc:query jdbc/postgresql 'select * from incident'; date │ summary │ name │ details │ id │ email ────────────────────┼────────────┼────────┼───────────────────────────────┼────┼───────────────── 2018-02-20 08:00:00 │ Incident 1 │ User 1 │ This is a report incident 001 │ 1 │ user1@redhat.com 2018-02-20 08:10:00 │ Incident 2 │ User 2 │ This is a report incident 002 │ 2 │ user2@redhat.com 2018-02-20 08:20:00 │ Incident 3 │ User 3 │ This is a report incident 003 │ 3 │ user3@redhat.com 2018-02-20 08:30:00 │ Incident 4 │ User 4 │ This is a report incident 004 │ 4 │ user4@redhat.com
如上方列表所示,Bartner 捆绑包将导出 javax.sql.DataSource
服务,该服务是一个通用、非特定于数据库的连接池。特定于数据库的 javax.sql.XADataSource
没有 注册为 OSGi 服务,因为 Blueprint XML 没有明确的 < service ref="postgresql">
声明。
6.8.3. 数据源的混合部署
在混合数据源部署中,pax-jdbc-config
1.3.0 捆绑包增加了使用服务属性将特定于数据库数据源嵌套在数据源中的另一方式。此方法与 Fuse 6.x 中的工作方式一致。
以下是 Blueprint XML 示例:
<!-- Database-specific, non-pooling, non-enlisting javax.sql.XADataSource --> <bean id="postgresql" class="org.postgresql.xa.PGXADataSource"> <property name="url" value="jdbc:postgresql://localhost:5432/reportdb" /> <property name="user" value="fuse" /> <property name="password" value="fuse" /> <property name="currentSchema" value="report" /> <property name="connectTimeout" value="5" /> </bean> <!-- Expose database-specific data source with service properties. No need to expose pooling, enlisting, non database-specific javax.sql.DataSource. It is registered automatically by pax-jdbc-config with the same properties as this <service>, but with higher service.ranking. --> <service id="pool" ref="postgresql" interface="javax.sql.XADataSource"> <service-properties> <!-- "pool" key is needed for pax-jdbc-config to wrap database-specific data source inside connection pool --> <entry key="pool" value="dbcp2" /> <entry key="osgi.jndi.service.name" value="jdbc/postgresql" /> <!-- Other properties that configure given connection pool, as indicated by pool=dbcp2 --> <entry key="pool.minIdle" value="2" /> <entry key="pool.maxTotal" value="10" /> <entry key="pool.blockWhenExhausted" value="true" /> <entry key="pool.maxWaitMillis" value="2000" /> <entry key="pool.testOnBorrow" value="true" /> <entry key="pool.testWhileIdle" value="false" /> <entry key="factory.validationQuery" value="select schema_name from information_schema.schemata" /> <entry key="factory.validationQueryTimeout" value="2" /> </service-properties> </service>
在上例中,只有数据库特定的数据源会被手动注册。pool=dbcp2
service 属性是数据源跟踪器的提示,它由 pax-jdbc-config
捆绑包管理。带有这个服务属性的数据源服务嵌套在池数据源内,本例中为 pax-jdbc-pool-dbcp2
。
以下是 Fuse/Karaf shell 中的相同示例:
karaf@root()> feature:install jdbc pax-jdbc-config pax-jdbc-pool-dbcp2 karaf@root()> install -s mvn:org.postgresql/postgresql/42.2.5 Bundle ID: 232 karaf@root()> install -s blueprint:file://$PQ_HOME/databases/blueprints/postgresql-pax-jdbc-discovery.xml Bundle ID: 233 karaf@root()> bundle:services -p 233 Bundle 233 provides: -------------------- factory.validationQuery = select schema_name from information_schema.schemata factory.validationQueryTimeout = 2 objectClass = [javax.sql.XADataSource] osgi.jndi.service.name = jdbc/postgresql osgi.service.blueprint.compname = postgresql pool = dbcp2 pool.blockWhenExhausted = true pool.maxTotal = 10 pool.maxWaitMillis = 2000 pool.minIdle = 2 pool.testOnBorrow = true pool.testWhileIdle = false service.bundleid = 233 service.id = 336 service.scope = bundle ----- objectClass = [org.osgi.service.blueprint.container.BlueprintContainer] osgi.blueprint.container.symbolicname = postgresql-pax-jdbc-discovery.xml osgi.blueprint.container.version = 0.0.0 service.bundleid = 233 service.id = 338 service.scope = singleton karaf@root()> service:list javax.sql.XADataSource [javax.sql.XADataSource] ------------------------ factory.validationQuery = select schema_name from information_schema.schemata factory.validationQueryTimeout = 2 osgi.jndi.service.name = jdbc/postgresql osgi.service.blueprint.compname = postgresql pool = dbcp2 pool.blockWhenExhausted = true pool.maxTotal = 10 pool.maxWaitMillis = 2000 pool.minIdle = 2 pool.testOnBorrow = true pool.testWhileIdle = false service.bundleid = 233 service.id = 336 service.scope = bundle Provided by : Bundle 233 Used by: OPS4J Pax JDBC Config (224) karaf@root()> service:list javax.sql.DataSource [javax.sql.DataSource] ---------------------- factory.validationQuery = select schema_name from information_schema.schemata factory.validationQueryTimeout = 2 osgi.jndi.service.name = jdbc/postgresql osgi.service.blueprint.compname = postgresql pax.jdbc.managed = true pax.jdbc.service.id.ref = 336 pool.blockWhenExhausted = true pool.maxTotal = 10 pool.maxWaitMillis = 2000 pool.minIdle = 2 pool.testOnBorrow = true pool.testWhileIdle = false service.bundleid = 224 service.id = 337 service.ranking = 1000 service.scope = singleton Provided by : OPS4J Pax JDBC Config (224) karaf@root()> jdbc:ds-list Name │ Product │ Version │ URL │ Status ────────────────┼────────────┼───────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼─────── jdbc/postgresql │ PostgreSQL │ 10.3 (Debian 10.3-1.pgdg90+1) │ jdbc:postgresql://localhost:5432/reportdb?prepareThreshold=5&preparedStatementCacheQueries=256&preparedStatementCacheSizeMiB=5&databaseMetadataCacheFields=65536&databaseMetadataCacheFieldsMiB=5&defaultRowFetchSize=0&binaryTransfer=true&readOnly=false&binaryTransferEnable=&binaryTransferDisable=&unknownLength=2147483647&logUnclosedConnections=false&disableColumnSanitiser=false&tcpKeepAlive=false&loginTimeout=0&connectTimeout=5&socketTimeout=0&cancelSignalTimeout=10&receiveBufferSize=-1&sendBufferSize=-1&ApplicationName=PostgreSQL JDBC Driver&jaasLogin=true&useSpnego=false&gsslib=auto&sspiServiceClass=POSTGRES&allowEncodingChanges=false¤tSchema=report&targetServerType=any&loadBalanceHosts=false&hostRecheckSeconds=10&preferQueryMode=extended&autosave=never&reWriteBatchedInserts=false │ OK jdbc/postgresql │ PostgreSQL │ 10.3 (Debian 10.3-1.pgdg90+1) │ jdbc:postgresql://localhost:5432/reportdb?prepareThreshold=5&preparedStatementCacheQueries=256&preparedStatementCacheSizeMiB=5&databaseMetadataCacheFields=65536&databaseMetadataCacheFieldsMiB=5&defaultRowFetchSize=0&binaryTransfer=true&readOnly=false&binaryTransferEnable=&binaryTransferDisable=&unknownLength=2147483647&logUnclosedConnections=false&disableColumnSanitiser=false&tcpKeepAlive=false&loginTimeout=0&connectTimeout=5&socketTimeout=0&cancelSignalTimeout=10&receiveBufferSize=-1&sendBufferSize=-1&ApplicationName=PostgreSQL JDBC Driver&jaasLogin=true&useSpnego=false&gsslib=auto&sspiServiceClass=POSTGRES&allowEncodingChanges=false¤tSchema=report&targetServerType=any&loadBalanceHosts=false&hostRecheckSeconds=10&preferQueryMode=extended&autosave=never&reWriteBatchedInserts=false │ OK karaf@root()> jdbc:query jdbc/postgresql 'select * from incident' date │ summary │ name │ details │ id │ email ────────────────────┼────────────┼────────┼───────────────────────────────┼────┼───────────────── 2018-02-20 08:00:00 │ Incident 1 │ User 1 │ This is a report incident 001 │ 1 │ user1@redhat.com 2018-02-20 08:10:00 │ Incident 2 │ User 2 │ This is a report incident 002 │ 2 │ user2@redhat.com 2018-02-20 08:20:00 │ Incident 3 │ User 3 │ This is a report incident 003 │ 3 │ user3@redhat.com 2018-02-20 08:30:00 │ Incident 4 │ User 4 │ This is a report incident 004 │ 4 │ user4@redhat.com
在本列表中,如您在 jdbc:ds-list
输出中看到的一样,有两个 数据源,即原始数据源和 wrapper 数据源。
javax.sql.XADataSource
从 Blueprint 捆绑包中注册,并且具有 pool = dbcp2
属性声明。
javax.sql.DataSource
在 pax-jdbc-config
捆绑包中注册,并且:
-
没有
pool = dbcp2
属性(它会在注册打包程序数据源时被删除)。 -
具有
service.ranking = 1000
属性,因此它始终是何时首选版本,例如,按名称查找数据源。 -
具有
pax.jdbc.managed = true
属性,因此不会尝试再次换行。 -
具有
pax.jdbc.service.id.ref = 336
属性,用于指示连接池中嵌套的原始数据源服务。
6.9. 使用 Java™ 持久性 API 的数据源
从事务管理的角度来看,了解数据源与 Java™ Persistence API(JPA)搭配使用非常重要。本节不描述 JPA 规格本身的详情,以及 Hibernate 的更多详情,这是最已知的 JPA 实施。本节演示了如何将 JPA 持久单元指向数据源。
6.9.1. 关于数据源引用
META-INF/persistence.xml
描述符(请参阅 JPA 2.1 规格 8.2.1.5 jta-data-source)定义两种数据源引用:
-
<JTA-data-source
> - 这是对启用了 JTA 的数据源的 JNDI 引用,用于JTA
事务。 -
<non-jta-data-source
> - 这是对启用了 JTA 的数据源的 JNDI 引用,可在JTA
事务之外使用。此数据源通常用于初始化阶段,例如使用hibernate.hbm2ddl.auto
属性将 Hibernate 配置为自动创建数据库架构。
这两个数据源与 javax.sql.DataSource
或 javax.sql.XADataSource
无关!这是开发 JPA 应用程序时常见的误解。两个 JNDI 名称都必须引用 JNDI 绑定 javax.sql.DataSource
服务。
6.9.2. 参考 JNDI 名称
当您使用 osgi.jndi.service.name
属性注册 OSGi 服务时,它已在 OSGi JNDI 服务中 绑定。在 OSGi 运行时(如 Fuse/Karaf)中,JNDI 不是 name → value 对的简单字典。通过 OSGi 中的 JNDI 名称引用对象,涉及服务查找和其他更复杂的 OSGi 机制,如服务 hook。
在全新的 Fuse 安装中,以下列表显示了在 JNDI 中注册数据源的方式:
karaf@root()> install -s mvn:mysql/mysql-connector-java/5.1.34 Bundle ID: 223 karaf@root()> install -s mvn:org.osgi/org.osgi.service.jdbc/1.0.0 Bundle ID: 224 karaf@root()> install -s mvn:org.ops4j.pax.jdbc/pax-jdbc-mysql/1.3.0 Bundle ID: 225 karaf@root()> install -s mvn:org.ops4j.pax.jdbc/pax-jdbc/1.3.0 Bundle ID: 226 karaf@root()> install -s mvn:org.ops4j.pax.jdbc/pax-jdbc-pool-common/1.3.0 Bundle ID: 227 karaf@root()> install -s mvn:org.ops4j.pax.jdbc/pax-jdbc-config/1.3.0 Bundle ID: 228 karaf@root()> config:edit --factory --alias mysql org.ops4j.datasource karaf@root()> config:property-set osgi.jdbc.driver.name mysql karaf@root()> config:property-set dataSourceName mysqlds karaf@root()> config:property-set osgi.jndi.service.name jdbc/mysqlds karaf@root()> config:property-set dataSourceType DataSource karaf@root()> config:property-set jdbc.url jdbc:mysql://localhost:3306/reportdb karaf@root()> config:property-set jdbc.user fuse karaf@root()> config:property-set jdbc.password fuse karaf@root()> config:property-set jdbc.useSSL false karaf@root()> config:update karaf@root()> feature:install jndi karaf@root()> jndi:names JNDI Name │ Class Name ──────────────────────────┼─────────────────────────────────────────────── osgi:service/jndi │ org.apache.karaf.jndi.internal.JndiServiceImpl osgi:service/jdbc/mysqlds │ com.mysql.jdbc.jdbc2.optional.MysqlDataSource
如您所见,数据源位于 osgi:service/jdbc/mysqlds
JNDI 名称下。
但是,如果 OSGi 中的 JPA,您必须使用 完整的 JNDI 名称。以下是指定数据源参考的 META-INF/persistence.xml
片段示例:
<jta-data-source> osgi:service/javax.sql.DataSource/(osgi.jndi.service.name=jdbc/mysqlds) </jta-data-source> <non-jta-data-source> osgi:service/javax.sql.DataSource/(osgi.jndi.service.name=jdbc/mysqlds) </non-jta-data-source>
如果没有上述配置,您可能会得到这个错误:
Persistence unit "pu-name" refers to a non OSGi service DataSource
第 7 章 使用 JMS 连接工厂
本章论述了如何在 OSGi 中使用 JMS 连接工厂。根本性,您可以使用以下方法实现它:
org.osgi.framework.BundleContext.registerService(javax.jms.ConnectionFactory.class, connectionFactoryObject, properties); org.osgi.framework.BundleContext.registerService(javax.jms.XAConnectionFactory.class, xaConnectionFactoryObject, properties);
注册此类服务的方法有两种:
-
使用
jms:create
Karaf console 命令发布连接工厂。这是 配置方法。 -
使用 Blueprint、OSGi Declative Services(SCR)或只使用
BundleContext.registerService()
API 调用来发布连接工厂。这个方法需要专用的 OSGi 捆绑包,其中包含代码和/或元数据。这是 部署方法。
详情在以下主题中:
7.1. 关于 OSGi JMS 服务
处理 JDBC 数据源的 OSGi 方法与两个接口相关:
-
standard
org.osgi.service.jdbc.DataSourceFactory
-
proprietary
org.ops4j.pax.jdbc.pool.common.PooledDataSourceFactory
对于 JMS,请参考以下类似:
-
专有
org.ops4j.pax.jms.service.ConnectionFactoryonnectionFactory
y 与标准 OSGi JDBCorg.osgi.service.jdbc.DataSourceFactory
相同 -
专有
org.ops4j.pax.jms.service.PooledConnectionFactoryonnectionFactory
y 与 proprietary pax-jdbcorg.ops4j.pax.jdbc.pool.common.PooledDataSource
onnectionFactoryy 相同
对于专用于代理的专用、特定于代理的、org.ops4j.pax.jms.service.ConnectionFactoryonnectionFactory
y 实施,有如下捆绑包:
-
mvn:org.ops4j.pax.jms/pax-jms-artemis/1.0.0
-
mvn:org.ops4j.pax.jms/pax-jms-ibmmq/1.0.0
-
mvn:org.ops4j.pax.jms/pax-jms-activemq/1.0.0
这些捆绑包注册了特定于代理的 org.ops4j.pax.jms.service.ConnectionFactoryFactory
服务,这些服务可以返回 JMS 工厂,如 javax.jms.ConnectionFactory
和 javax.jms.XAConnectionFactory
。例如:
karaf@root()> feature:install pax-jms-artemis karaf@root()> bundle:services -p org.ops4j.pax.jms.pax-jms-config OPS4J Pax JMS Config (248) provides: ------------------------------------ objectClass = [org.osgi.service.cm.ManagedServiceFactory] service.bundleid = 248 service.id = 328 service.pid = org.ops4j.connectionfactory service.scope = singleton karaf@root()> bundle:services -p org.ops4j.pax.jms.pax-jms-artemis OPS4J Pax JMS Artemis Support (247) provides: --------------------------------------------- objectClass = [org.ops4j.pax.jms.service.ConnectionFactoryFactory] service.bundleid = 247 service.id = 327 service.scope = singleton type = artemis
7.2. 关于 PAX-JMS 配置服务
mvn:org.ops4j.pax.jms/pax-jms-config/1.0.0
捆绑包提供了一个 Managed Service Factory,它具有以下作用:
跟踪
org.ops4j.pax.jms.service.ConnectionFactoryonnectionFactory
y OSGi 服务以调用其方法:public ConnectionFactory createConnectionFactory(Map<String, Object> properties); public XAConnectionFactory createXAConnectionFactory(Map<String, Object> properties);
-
跟踪
org.ops4j.connection factory factory PID
,以收集以上方法需要的属性。如果您使用任何可用于 Configuration Admin 服务的方法创建 工厂配置,例如,创建一个${karaf.etc}/org.ops4j.connectionfactory-artemis.cfg
文件,您可以执行最后的步骤公开代理特定的连接工厂。 -
跟踪
javax.jms.ConnectionFactory
和javax.jms.XAConnectionFactory
服务,将它们嵌套在 JMS 连接工厂内。
详情在以下主题中:
7.2.1. 为 AMQ 7.1 创建连接工厂
下面是一本例行的、规范、分步指南,用于创建一个对 runc 代理的连接因子。
使用
pax-jms-artemis
功能和pax-jms-config
功能安装 FreeIPA 驱动程序:karaf@root()> feature:install pax-jms-artemis karaf@root()> bundle:services -p org.ops4j.pax.jms.pax-jms-config OPS4J Pax JMS Config (248) provides: ------------------------------------ objectClass = [org.osgi.service.cm.ManagedServiceFactory] service.bundleid = 248 service.id = 328 service.pid = org.ops4j.connectionfactory service.scope = singleton karaf@root()> bundle:services -p org.ops4j.pax.jms.pax-jms-artemis OPS4J Pax JMS Artemis Support (247) provides: --------------------------------------------- objectClass = [org.ops4j.pax.jms.service.ConnectionFactoryFactory] service.bundleid = 247 service.id = 327 service.scope = singleton type = artemis
创建 工厂配置 :
karaf@root()> config:edit --factory --alias artemis org.ops4j.connectionfactory karaf@root()> config:property-set type artemis karaf@root()> config:property-set osgi.jndi.service.name jms/artemis # "name" property may be used too karaf@root()> config:property-set connectionFactoryType ConnectionFactory # or XAConnectionFactory karaf@root()> config:property-set jms.url tcp://localhost:61616 karaf@root()> config:property-set jms.user admin karaf@root()> config:property-set jms.password admin karaf@root()> config:property-set jms.consumerMaxRate 1234 karaf@root()> config:update karaf@root()> config:list '(service.factoryPid=org.ops4j.connectionfactory)' ---------------------------------------------------------------- Pid: org.ops4j.connectionfactory.965d4eac-f5a7-4f65-ba1a-15caa4c72703 FactoryPid: org.ops4j.connectionfactory BundleLocation: ? Properties: connectionFactoryType = ConnectionFactory felix.fileinstall.filename = file:${karar.etc}/org.ops4j.connectionfactory-artemis.cfg jms.consumerMaxRate = 1234 jms.password = admin jms.url = tcp://localhost:61616 jms.user = admin osgi.jndi.service.name = jms/artemis service.factoryPid = org.ops4j.connectionfactory service.pid = org.ops4j.connectionfactory.965d4eac-f5a7-4f65-ba1a-15caa4c72703 type = artemis
检查
pax-jms-config
是否处理配置到javax.jms.ConnectionFactory
服务:karaf@root()> service:list javax.jms.ConnectionFactory [javax.jms.ConnectionFactory] ----------------------------- connectionFactoryType = ConnectionFactory felix.fileinstall.filename = file:${karaf.etc}/org.ops4j.connectionfactory-artemis.cfg jms.consumerMaxRate = 1234 jms.password = admin jms.url = tcp://localhost:61616 jms.user = admin osgi.jndi.service.name = jms/artemis pax.jms.managed = true service.bundleid = 248 service.factoryPid = org.ops4j.connectionfactory service.id = 342 service.pid = org.ops4j.connectionfactory.965d4eac-f5a7-4f65-ba1a-15caa4c72703 service.scope = singleton type = artemis Provided by : OPS4J Pax JMS Config (248)
注意如果您指定额外的 ROLES 配置,特别是
protocol=amqp
,则将使用 QPID JMS 库,而不是 Environments JMS 客户端。然后,必须使用amqp://
协议用于jms.url
属性。- 测试连接。
现在,您有特定于代理(没有池)连接工厂,您可以在需要时注入。例如,您可以使用 jms
功能中的 Karaf 命令:
karaf@root()> feature:install -v jms Adding features: jms/[4.2.0.fuse-000237-redhat-1,4.2.0.fuse-000237-redhat-1] ... karaf@root()> jms:connectionfactories JMS Connection Factory ────────────────────── jms/artemis karaf@root()> jms:info -u admin -p admin jms/artemis Property │ Value ─────────┼────────────────────────── product │ ActiveMQ version │ 2.4.0.amq-711002-redhat-1 karaf@root()> jms:send -u admin -p admin jms/artemis DEV.QUEUE.1 "Hello Artemis" karaf@root()> jms:browse -u admin -p admin jms/artemis DEV.QUEUE.1 Message ID │ Content │ Charset │ Type │ Correlation ID │ Delivery Mode │ Destination │ Expiration │ Priority │ Redelivered │ ReplyTo │ Timestamp ────────────────────────────────────────┼───────────────┼─────────┼──────┼────────────────┼───────────────┼────────────────────────────┼────────────┼──────────┼─────────────┼─────────┼────────────────────────────── ID:2b6ea56d-574d-11e8-971a-7ee9ecc029d4 │ Hello Artemis │ UTF-8 │ │ │ Persistent │ ActiveMQQueue[DEV.QUEUE.1] │ Never │ 4 │ false │ │ Mon May 14 10:02:38 CEST 2018
以下列表显示了切换协议时会发生什么:
karaf@root()> config:list '(service.factoryPid=org.ops4j.connectionfactory)' ---------------------------------------------------------------- Pid: org.ops4j.connectionfactory.965d4eac-f5a7-4f65-ba1a-15caa4c72703 FactoryPid: org.ops4j.connectionfactory BundleLocation: ? Properties: connectionFactoryType = ConnectionFactory felix.fileinstall.filename = file:${karaf.etc}/org.ops4j.connectionfactory-artemis.cfg jms.consumerMaxRate = 1234 jms.password = fuse jms.url = tcp://localhost:61616 jms.user = fuse osgi.jndi.service.name = jms/artemis service.factoryPid = org.ops4j.connectionfactory service.pid = org.ops4j.connectionfactory.965d4eac-f5a7-4f65-ba1a-15caa4c72703 type = artemis karaf@root()> config:edit org.ops4j.connectionfactory.312eb09a-d686-4229-b7e1-2ea38a77bb0f karaf@root()> config:property-set protocol amqp karaf@root()> config:property-delete user karaf@root()> config:property-set username admin # mind the difference between artemis-jms-client and qpid-jms-client karaf@root()> config:property-set jms.url amqp://localhost:61616 karaf@root()> config:update karaf@root()> jms:info -u admin -p admin jms/artemis Property │ Value ─────────┼──────────────── product │ QpidJMS version │ 0.30.0.redhat-1 karaf@root()> jms:browse -u admin -p admin jms/artemis DEV.QUEUE.1 Message ID │ Content │ Charset │ Type │ Correlation ID │ Delivery Mode │ Destination │ Expiration │ Priority │ Redelivered │ ReplyTo │ Timestamp ───────────┼───────────────┼─────────┼──────┼────────────────┼───────────────┼─────────────┼────────────┼──────────┼─────────────┼─────────┼────────────────────────────── │ Hello Artemis │ UTF-8 │ │ │ Persistent │ DEV.QUEUE.1 │ Never │ 4 │ false │ │ Mon May 14 10:02:38 CEST 2018
7.2.2. 为 IBM MQ 8 或 IBM MQ 9 创建连接工厂
本节演示了如何连接到 IBM MQ 8 和 IBM MQ 9。尽管 pax-jms-ibmmq
安装相关的 pax-jms
捆绑包,但由于许可原因,也不会安装 IBM MQ 驱动程序。
- 进入 https://developer.ibm.com/messaging/mq-downloads/
- 登录。
- 点击要安装的版本,例如,点 IBM MQ 8.0 Client 或 IBM MQ 9.0 Client。
- 在底部页面的下载版本表中,点击您想要的版本。
-
在下一页中,选择后缀
IBM-MQ-Install-Java-All
的最新版本。例如,下载8.0.0.10-WS-MQ-Install-Java-All
或9.0.0.4-IBM-MQ-Install-Java-All
。 - 提取下载的 JAR 文件的内容。
执行
bundle:install
命令。例如,如果您将内容提取到/home/Downloads
目录,则您应该输入以下命令:`bundle:install -s wrap:file:////home/Downloads/9.0.0.4-IBM-MQ-Install-Java-All/ibmmq9/wmq/JavaSE/com.ibm.mq.allclient.jar`.
创建连接工厂,如下所示:
安装
pax-jms-ibmmq
:karaf@root()> feature:install pax-jms-ibmmq karaf@root()> bundle:services -p org.ops4j.pax.jms.pax-jms-ibmmq OPS4J Pax JMS IBM MQ Support (239) provides: -------------------------------------------- objectClass = [org.ops4j.pax.jms.service.ConnectionFactoryFactory] service.bundleid = 239 service.id = 346 service.scope = singleton type = ibmmq
创建 工厂配置 :
karaf@root()> config:edit --factory --alias ibmmq org.ops4j.connectionfactory karaf@root()> config:property-set type ibmmq karaf@root()> config:property-set osgi.jndi.service.name jms/mq9 # "name" property may be used too karaf@root()> config:property-set connectionFactoryType ConnectionFactory # or XAConnectionFactory karaf@root()> config:property-set jms.queueManager FUSEQM karaf@root()> config:property-set jms.hostName localhost karaf@root()> config:property-set jms.port 1414 karaf@root()> config:property-set jms.transportType 1 # com.ibm.msg.client.wmq.WMQConstants.WMQ_CM_CLIENT karaf@root()> config:property-set jms.channel DEV.APP.SVRCONN karaf@root()> config:property-set jms.CCSID 1208 # com.ibm.msg.client.jms.JmsConstants.CCSID_UTF8 karaf@root()> config:update karaf@root()> config:list '(service.factoryPid=org.ops4j.connectionfactory)' ---------------------------------------------------------------- Pid: org.ops4j.connectionfactory.eee4a757-a95d-46b8-b8b6-19aa3977d863 FactoryPid: org.ops4j.connectionfactory BundleLocation: ? Properties: connectionFactoryType = ConnectionFactory felix.fileinstall.filename = file:${karaf.etc}/org.ops4j.connectionfactory-ibmmq.cfg jms.CCSID = 1208 jms.channel = DEV.APP.SVRCONN jms.hostName = localhost jms.port = 1414 jms.queueManager = FUSEQM jms.transportType = 1 osgi.jndi.service.name = jms/mq9 service.factoryPid = org.ops4j.connectionfactory service.pid = org.ops4j.connectionfactory.eee4a757-a95d-46b8-b8b6-19aa3977d863 type = ibmmq
检查
pax-jms-config
是否处理配置为javax.jms.ConnectionFactory
服务:karaf@root()> service:list javax.jms.ConnectionFactory [javax.jms.ConnectionFactory] ----------------------------- connectionFactoryType = ConnectionFactory felix.fileinstall.filename = file:/data/servers/7.11.1.fuse-7_11_1-00013-redhat-00003/etc/org.ops4j.connectionfactory-ibmmq.cfg jms.CCSID = 1208 jms.channel = DEV.APP.SVRCONN jms.hostName = localhost jms.port = 1414 jms.queueManager = FUSEQM jms.transportType = 1 osgi.jndi.service.name = jms/mq9 pax.jms.managed = true service.bundleid = 237 service.factoryPid = org.ops4j.connectionfactory service.id = 347 service.pid = org.ops4j.connectionfactory.eee4a757-a95d-46b8-b8b6-19aa3977d863 service.scope = singleton type = ibmmq Provided by : OPS4J Pax JMS Config (237)
测试连接:
karaf@root()> feature:install -v jms Adding features: jms/[4.2.0.fuse-000237-redhat-1,4.2.0.fuse-000237-redhat-1] ... karaf@root()> jms:connectionfactories JMS Connection Factory ────────────────────── jms/mq9 karaf@root()> jms:info -u app -p fuse jms/mq9 Property │ Value ─────────┼──────────────────── product │ IBM MQ JMS Provider version │ 8.0.0.0 karaf@root()> jms:send -u app -p fuse jms/mq9 DEV.QUEUE.1 "Hello IBM MQ 9" karaf@root()> jms:browse -u app -p fuse jms/mq9 DEV.QUEUE.1 Message ID │ Content │ Charset │ Type │ Correlation ID │ Delivery Mode │ Destination │ Expiration │ Priority │ Redelivered │ ReplyTo │ Timestamp ────────────────────────────────────────────────────┼─────────────────────────────┼─────────┼──────┼────────────────┼───────────────┼──────────────────────┼────────────┼──────────┼─────────────┼─────────┼────────────────────────────── ID:414d512046555345514d202020202020c940f95a038b3220 │ Hello IBM MQ 9 │ UTF-8 │ │ │ Persistent │ queue:///DEV.QUEUE.1 │ Never │ 4 │ false │ │ Mon May 14 10:17:01 CEST 2018
您还可以检查信息是否已从 IBM MQ Explorer 或 web 控制台发送。
7.2.3. 在 Apache Karaf 的 Fuse 中使用 JBoss A-MQ 6.3 客户端
您可以从 Fuse Software Downloads 页面下载 Fuse 快速入门
。
将快速入门 zip 文件的内容提取到本地文件夹,如名为 Quickstart 的文件夹。
您可以将 快速入门/camel/camel-jms
示例作为 OSGi 捆绑包来构建并安装。此捆绑包包含 Camel 路由的蓝图 XML 定义,用于将消息发送到 JBoss A-MQ 6.3 JMS 队列。为 JBoss A-MQ 6.3 代理创建连接工厂的步骤如下:
7.2.3.1. 先决条件
- 已安装 Maven 3.3.1 或更高版本。
- 您已在机器上安装了 Red Hat Fuse。
- 您已在机器上安装了 JBoss A-MQ Broker 6.3。
- 您已从客户门户网站下载并提取了 Fuse on visual Quickstart zip 文件。
7.2.3.2. 流程
-
进入
快速入门/camel/camel-jms/src/main/resources/OSGI-INF/blueprint/
目录。 将以下 bean 替换为
camel-context.xml
文件中的 id="jms":<bean id="jms" class="org.apache.camel.component.jms.JmsComponent"> <property name="connectionFactory"> <reference interface="javax.jms.ConnectionFactory" /> </property> <property name="transactionManager" ref="transactionManager"/> </bean>
通过以下部分实例化 JBoss A-MQ 6.3 连接工厂:
<bean id="jms" class="org.apache.camel.component.jms.JmsComponent"> <property name="connectionFactory" ref="activemqConnectionFactory"/> <property name="transactionManager" ref="transactionManager"/> </bean> <bean id="activemqConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://localhost:61616"/> <property name="userName" value="admin"/> <property name="password" value="admin"/> </bean>
JBoss A-MQ 6.3 连接工厂配置为连接到侦听
tcp://localhost:61616
的代理。默认情况下,JBoss A-MQ 使用 IP 端口值61616
。连接工厂也被配置为使用 userName/password 凭证 admin/admin。确保此用户在您的代理 cofiguration 中启用(或者您可以在此处自定义这些设置以匹配代理配置)。-
保存
camel-context.xml
文件。 构建
camel-jms
Quickstart:$ cd quickstarts/camel/camel-jms $ mvn install
成功安装 Quickstart 后,导航到
$FUSE_HOME/
目录,再运行以下命令在 Apache Karaf 服务器上启动 Fuse:$ ./bin/fuse
在 Apache 手册的 Fuse 上安装
activemq-client
功能及camel-jms
功能:karaf@root()> feature:install activemq-client karaf@root()> feature:install camel-jms
安装
camel-jms
Quickstart 捆绑包:karaf@root()> install -s mvn:org.jboss.fuse.quickstarts/camel-jms/{$fuseversion}
其中,将
{$fuseversion}
替换为您刚才构建的 Maven 工件的实际版本(使用 camel-jms quickstart README 文件)。启动
JBoss A-MQ 6.3
代理(需要安装 JBoss A-MQ 6.3)。打开另一个终端窗口,进入 JBOSS_AMQ_63_INSTALLDIR 目录:$ cd JBOSS_AMQ_63_INSTALLDIR $ ./bin/amq
-
Camel 路由启动后,您可以在 Fuse 安装中看到一个目录
work/jms/input
。将您在此 Quickstart 的src/main/data 目录中找到的
文件复制到新创建的work/jms/input
目录中。 稍等片刻,您将在
work/jms/output
目录下找到由国家组织相同的文件:order1.xml, order2.xml and order4.xml in work/jms/output/others order3.xml and order5.xml in work/jms/output/us order6.xml in work/jms/output/fr
使用
log:display
检查业务日志:Receiving order order1.xml Sending order order1.xml to another country Done processing order1.xml
7.2.4. 处理的属性摘要
Configuration Admin factory PID 中的属性传递到相关的 org.ops4j.pax.jms.service.ConnectionFactoryonnectionFactoryy
实施。
ActiveMQ
org.ops4j.pax.jms.activemq.ActiveMQConnectionFactoryFactory
(JMS 1.1 only)传递给
org.apache.activemq.ActiveMQConnectionFactory.buildFromMap()
方法的属性pata
org.ops4j.pax.jms.artemis.ArtemisConnectionFactoryFactory
如果
protocol=amqp
,则属性将传递给org.apache.qpid.jms.util.PropertyUtil.setProperties()
方法来配置org.apache.qpid.jms.JmsConnectionFactory
实例。否则,为
org.apache.activemq.artemis.utils.uri.BeanSupport.setData()
调用,用于org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory
实例。IBM MQ
org.ops4j.pax.jms.ibmmq.MQConnectionFactoryFactory
com.ibm.mq.jms.MQConnectionFactory
或com.ibm.mq.jms.MQXAConnectionFactory
的 bean 属性被处理。
7.3. 使用 JMS 控制台命令
Apache Karaf 提供 jms
功能,该功能在 jms:*
范围内包含 shell 命令。您已看到一些使用这些命令检查手动配置的连接因素的示例。还有一些命令隐藏了创建 Configuration Admin 配置的必要命令。
从一个新 Fuse 实例开始,您可以注册特定于代理的连接工厂。以下列表显示来自 Karaf 的 jms
功能的安装,并从 pax-jms
安装 pax-jms-artemis
:
karaf@root()> feature:install jms pax-jms-artemis karaf@root()> jms:connectionfactories JMS Connection Factory ────────────────────── karaf@root()> service:list javax.jms.ConnectionFactory # should be empty karaf@root()> service:list org.ops4j.pax.jms.service.ConnectionFactoryFactory [org.ops4j.pax.jms.service.ConnectionFactoryFactory] ---------------------------------------------------- service.bundleid = 250 service.id = 326 service.scope = singleton type = artemis Provided by : OPS4J Pax JMS Artemis Support (250)
下表显示了如何创建和检查 ROLES 连接工厂:
karaf@root()> jms:create -t artemis -u admin -p admin --url tcp://localhost:61616 artemis karaf@root()> jms:connectionfactories JMS Connection Factory ────────────────────── jms/artemis karaf@root()> jms:info -u admin -p admin jms/artemis Property │ Value ─────────┼────────────────────────── product │ ActiveMQ version │ 2.4.0.amq-711002-redhat-1 karaf@root()> jms:send -u admin -p admin jms/artemis DEV.QUEUE.1 "Hello Artemis" karaf@root()> jms:browse -u admin -p admin jms/artemis DEV.QUEUE.1 Message ID │ Content │ Charset │ Type │ Correlation ID │ Delivery Mode │ Destination │ Expiration │ Priority │ Redelivered │ ReplyTo │ Timestamp ────────────────────────────────────────┼───────────────┼─────────┼──────┼────────────────┼───────────────┼────────────────────────────┼────────────┼──────────┼─────────────┼─────────┼────────────────────────────── ID:7a944470-574f-11e8-918e-7ee9ecc029d4 │ Hello Artemis │ UTF-8 │ │ │ Persistent │ ActiveMQQueue[DEV.QUEUE.1] │ Never │ 4 │ false │ │ Mon May 14 10:19:10 CEST 2018 karaf@root()> config:list '(service.factoryPid=org.ops4j.connectionfactory)' ---------------------------------------------------------------- Pid: org.ops4j.connectionfactory.9184db6f-cb5f-4fd7-b5d7-a217090473ad FactoryPid: org.ops4j.connectionfactory BundleLocation: mvn:org.ops4j.pax.jms/pax-jms-config/1.0.0 Properties: name = artemis osgi.jndi.service.name = jms/artemis password = admin service.factoryPid = org.ops4j.connectionfactory service.pid = org.ops4j.connectionfactory.9184db6f-cb5f-4fd7-b5d7-a217090473ad type = artemis url = tcp://localhost:61616 user = admin
如您所见,系统会为您创建 org.ops4j.connectionfactory
factory PID。但是,它不会自动存储在 ${karaf.etc}
中,这可以通过 config:update
。无法指定其他属性,但稍后您可以添加它们。
7.4. 使用加密配置值
与 pax-jdbc-config
捆绑包一样,您可以使用 Jasypt 加密属性。
如果有任何 org.jasypt.encryption.encryption.StringEncryptor
服务,则使用任何 别名
服务属性将其引用,您可以在 连接工厂 PID 并使用加密密码中引用它。例如:
felix.fileinstall.filename = */etc/org.ops4j.connectionfactory-artemis.cfg name = artemis type = artemis decryptor = my-jasypt-decryptor url = tcp://localhost:61616 user = fuse password = ENC(<encrypted-password>)
用于查找解密器服务的服务过滤器为 (&(objectClass=org.jasypt.encryption.StringEncryptor)(alias=<alias>))
,其中 & lt;alias
> 是 连接工厂配置出厂 PID 中的 decryptor
属性的值。
7.5. 使用 JMS 连接池
本节讨论 JMS 连接/会话池选项。JDBC 的选择数量比这里少。这些信息被分为以下主题:
要使用 XA 恢复,您应使用 pax-jms-pool-transx
或 pax-jms-pool-narayana
连接池模块。
7.5.1. 使用 JMS 连接池简介
目前,您已注册了特定于代理的 连接工厂。由于 连接工厂 本身是连接工厂的工厂,因此 org.ops4j.pax.jms.service.ConnectionFactoryonnectionFactory
y 服务可以被视为元数据 工厂。它应该可以生成两种连接工厂:
-
javax.jms.ConnectionFactory
-
javax.jms.XAConnectionFactory
pax-jms-pool-*
捆绑包可以与 org.ops4j.pax.jms.service.ConnectionFactoryonnectionFactoryy
服务一起正常工作。这些捆绑包提供了 org.ops4j.pax.jms.service.PooledConnectionFactoryonnectionFactory
y 的实施,它通过使用一组属性和原始 org.ops4j.pax.jms.service.ConnectionFactor
yy 来创建池连接工厂。例如:
public interface PooledConnectionFactoryFactory { ConnectionFactory create(ConnectionFactoryFactory cff, Map<String, Object> props); }
下表显示了哪些捆绑包注册了池连接工厂。在表格中,o.o.p.j.p
代表 org.ops4j.pax.jms.pool
。
捆绑包(Bundle) | PooledConnectionFactoryFactory | 池密钥 |
---|---|---|
|
|
|
|
|
|
|
|
|
pax-jms-pool-narayana
工厂名为 PooledJms(XA)PooledConnectionFactoryonnectionFactory
y,因为它基于 pooled-jms
库。它为 XA 恢复增加了 Narayana 事务管理器的集成。
以上捆绑包只安装连接工厂。要安装的捆绑包本身不安装连接。因此,需要调用 javax.jms.ConnectionFactory org.ops4j.pax.jms.service.PooledConnectionFactoryonnectionFactoryy.create()
方法所需要的内容。
7.5.2. 使用 pax-jms-pool-pooledjms 连接池模块
了解如何使用 pax-jms-pool-pooledjms
捆绑包可帮助您仅使用 pax-jms-pool-pooledjms
捆绑包,但 pax-jms-pool-narayna
bundle 几乎都是 pax-jms-pool-pooledjms
。
pax-jms-config 捆绑包会跟踪以下内容:
-
org.ops4j.pax.jms.service.ConnectionFactoryFactory
services -
org.ops4j.connectionfactory
factory PIDs -
org.ops4j.pax.jms.service.PooledConnectionFactoryonnectionFactory
y 的实例由pax-jms-pool-*
捆绑包注册。
如果 工厂配置 包含 pool
属性,则由 pax-jms-config
捆绑包注册的最终连接工厂为代理特定的连接工厂。如果 pool=pooledjms
,则连接工厂将在以下之一内换行:
-
org.messaginghub.pooled.jms.JmsPoolConnectionFactory
(xa=false
) -
org.messaginghub.pooled.jms.JmsPoolXAConnectionFactory
(xa=true
)
除了 池
属性(以及布尔值 xa
属性外,它选择非 xa/xa 连接工厂之一),org.ops4j.connection factory PID
可能包含以 pool.
前缀的属性。
对于 pooled-jms
库,这些前缀属性被使用(删除前缀后)来配置实例:
-
org.messaginghub.pooled.jms.JmsPoolConnectionFactory
, or -
org.messaginghub.pooled.jms.JmsPoolXAConnectionFactory
以下列表是 池ed-jms
池(org.ops4j.connectionfactory-artemis
factory PID工厂)的真实配置,它使用带有 jms.
-prefixed 的便捷语法:
# configuration for pax-jms-config to choose and configure specific org.ops4j.pax.jms.service.ConnectionFactoryFactory name = jms/artemis connectionFactoryType = ConnectionFactory jms.url = tcp://localhost:61616 jms.user = fuse jms.password = fuse # org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory specific coniguration jms.callTimeout = 12000 # ... # hints for pax-jms-config to use selected org.ops4j.pax.jms.service.PooledConnectionFactoryFactory pool = pooledjms xa = false # pooled-jms specific configuration of org.messaginghub.pooled.jms.JmsPoolConnectionFactory pool.idleTimeout = 10 pool.maxConnections = 100 pool.blockIfSessionPoolIsFull = true # ...
在上述配置中,池和
xa
键是 提示 (服务过滤器属性)用于选择其中一个注册的 org.ops4j.pax.jms.service.PooledConnectionFactoryonnectionFactoryy
服务。如果是 pooled-jms
库,这是:
karaf@root()> feature:install pax-jms-pool-pooledjms karaf@root()> bundle:services -p org.ops4j.pax.jms.pax-jms-pool-pooledjms OPS4J Pax JMS MessagingHub JMS Pool implementation (252) provides: ------------------------------------------------------------------ objectClass = [org.ops4j.pax.jms.service.PooledConnectionFactoryFactory] pool = pooledjms service.bundleid = 252 service.id = 331 service.scope = singleton xa = false ----- objectClass = [org.ops4j.pax.jms.service.PooledConnectionFactoryFactory] pool = pooledjms service.bundleid = 252 service.id = 335 service.scope = singleton xa = true
以下是创建和配置连接池的完整示例:
安装所需功能:
karaf@root()> feature:install -v pax-jms-pool-pooledjms pax-jms-artemis Adding features: pax-jms-pool-pooledjms/[1.0.0,1.0.0] ...
安装
jms
功能:karaf@root()> feature:install jms karaf@root()> service:list org.ops4j.pax.jms.service.ConnectionFactoryFactory [org.ops4j.pax.jms.service.ConnectionFactoryFactory] ---------------------------------------------------- service.bundleid = 249 service.id = 327 service.scope = singleton type = artemis Provided by : OPS4J Pax JMS Artemis Support (249) karaf@root()> service:list org.ops4j.pax.jms.service.PooledConnectionFactoryFactory [org.ops4j.pax.jms.service.PooledConnectionFactoryFactory] ---------------------------------------------------------- pool = pooledjms service.bundleid = 251 service.id = 328 service.scope = singleton xa = false Provided by : OPS4J Pax JMS MessagingHub JMS Pool implementation (251) [org.ops4j.pax.jms.service.PooledConnectionFactoryFactory] ---------------------------------------------------------- pool = pooledjms service.bundleid = 251 service.id = 333 service.scope = singleton xa = true Provided by : OPS4J Pax JMS MessagingHub JMS Pool implementation (251)
创建 工厂配置 :
karaf@root()> config:edit --factory --alias artemis org.ops4j.connectionfactory karaf@root()> config:property-set connectionFactoryType ConnectionFactory karaf@root()> config:property-set osgi.jndi.service.name jms/artemis karaf@root()> config:property-set type artemis karaf@root()> config:property-set protocol amqp # so we switch to org.apache.qpid.jms.JmsConnectionFactory karaf@root()> config:property-set jms.url amqp://localhost:61616 karaf@root()> config:property-set jms.username admin karaf@root()> config:property-set jms.password admin karaf@root()> config:property-set pool pooledjms karaf@root()> config:property-set xa false karaf@root()> config:property-set pool.idleTimeout 10 karaf@root()> config:property-set pool.maxConnections 123 karaf@root()> config:property-set pool.blockIfSessionPoolIsFull true karaf@root()> config:update
检查
pax-jms-config
是否处理配置为javax.jms.ConnectionFactory
服务:karaf@root()> service:list javax.jms.ConnectionFactory [javax.jms.ConnectionFactory] ----------------------------- connectionFactoryType = ConnectionFactory felix.fileinstall.filename = file:${karaf.etc}/org.ops4j.connectionfactory-artemis.cfg jms.password = admin jms.url = amqp://localhost:61616 jms.username = admin osgi.jndi.service.name = jms/artemis pax.jms.managed = true pool.blockIfSessionPoolIsFull = true pool.idleTimeout = 10 pool.maxConnections = 123 protocol = amqp service.bundleid = 250 service.factoryPid = org.ops4j.connectionfactory service.id = 347 service.pid = org.ops4j.connectionfactory.fc1b9e85-91b4-421b-aa16-1151b0f836f9 service.scope = singleton type = artemis Provided by : OPS4J Pax JMS Config (250)
使用连接工厂:
karaf@root()> jms:connectionfactories JMS Connection Factory ────────────────────── jms/artemis karaf@root()> jms:info -u admin -p admin jms/artemis Property │ Value ─────────┼──────────────── product │ QpidJMS version │ 0.30.0.redhat-1 karaf@root()> jms:send -u admin -p admin jms/artemis DEV.QUEUE.1 "Hello Artemis" karaf@root()> jms:browse -u admin -p admin jms/artemis DEV.QUEUE.1 Message ID │ Content │ Charset │ Type │ Correlation ID │ Delivery Mode │ Destination │ Expiration │ Priority │ Redelivered │ ReplyTo │ Timestamp ────────────────────────────────────────────────┼───────────────┼─────────┼──────┼────────────────┼───────────────┼─────────────┼────────────┼──────────┼─────────────┼─────────┼────────────────────────────── ID:64842f99-5cb2-4850-9e88-f50506d49d20:1:1:1-1 │ Hello Artemis │ UTF-8 │ │ │ Persistent │ DEV.QUEUE.1 │ Never │ 4 │ false │ │ Mon May 14 12:47:13 CEST 2018
7.5.3. 使用 pax-jms-pool-narayana 连接池模块
pax-jms-pool-narayna
模块几乎都有 pax-jms-pool-pooledjms
的所有内容。它安装特定于 pooled-jms 的 org.ops4j.pax.jms.service.PooledConnectionFactoryonnectionFactory
y,它们适用于 XA 和非 XA 场景。唯一的 区别是在 XA 方案中,有一个额外的集成点。org.jboss.tm.XAResourceRecovery
OSGi 服务由 com.arjuna.ats.arjuna.recoveryManager
获取。
7.5.4. 使用 pax-jms-pool-transx 连接池模块
pax-jms-pool-transx
模块提供了一个 org.ops4j.pax.jms.service.PooledConnectionFactoryonnectionFactoryy
服务(基于 pax-transx-jms
捆绑包)的实施。pax-transx-jms
捆绑包通过使用 org
池。这是 第 8.3 节 “关于 pax-transx 项目” 中讨论的 JCA(Java™ 连接器架构)解决方案。
.ops4j.pax.transx.ManagedConnectionFactory 功能来创建 java
x.jms.jms.ConnectionFactoryBuilder
7.6. 部署连接工厂作为工件
本主题讨论实际建议。
在 部署方法 中,javax.jms.ConnectionFactory
服务直接由应用代码注册。通常,这个代码位于 Blueprint 容器中。蓝图 XML 可能是普通 OSGi 捆绑包的一部分,可使用 mvn:
URI 进行安装,并存储在 Maven 存储库中(本地或远程)。与配置管理员配置相比,版本控制等捆绑包更容易。
pax-jms-config
版本 1.0.0 捆绑包为 连接工厂 配置添加部署方法。应用程序开发人员注册 javax.jms.(XA)ConnectionFactory
服务(通常使用 Bluerpint XML)并指定服务属性。然后,pax-jms-config
检测到注册的、代理特定连接工厂和(使用服务属性)将服务嵌套在通用、非代理特定的连接池中。
以下是使用 Blueprint XML 的三种 部署方法。
7.6.1. 手动部署连接因素
在此方法中,不需要 pax-jms-config
捆绑包。应用程序代码负责注册特定于代理和通用连接池。
<!-- Broker-specific, non-pooling, non-enlisting javax.jms.XAConnectionFactory --> <bean id="artemis" class="org.apache.activemq.artemis.jms.client.ActiveMQXAConnectionFactory"> <argument value="tcp://localhost:61616" /> <property name="callTimeout" value="2000" /> <property name="initialConnectAttempts" value="3" /> </bean> <!-- Fuse exports this service from fuse-pax-transx-tm-narayana bundle. --> <reference id="tm" interface="javax.transaction.TransactionManager" /> <!-- Non broker-specific, generic, pooling, enlisting javax.jms.ConnectionFactory --> <bean id="pool" class="org.messaginghub.pooled.jms.JmsPoolXAConnectionFactory"> <property name="connectionFactory" ref="artemis" /> <property name="transactionManager" ref="tm" /> <property name="maxConnections" value="10" /> <property name="idleTimeout" value="10000" /> </bean> <!-- Expose connection factory for use by application code (such as Camel, Spring, ...) --> <service interface="javax.jms.ConnectionFactory" ref="pool"> <service-properties> <!-- Giving connection factory a name using one of these properties makes identification easier in jms:connectionfactories: --> <entry key="osgi.jndi.service.name" value="jms/artemis" /> <!--<entry key="name" value="jms/artemis" />--> <!-- Without any of the above, name will fall back to "service.id" --> </service-properties> </service>
以下是显示如何使用它的 shell 命令:
karaf@root()> feature:install artemis-core-client artemis-jms-client karaf@root()> install -s mvn:org.apache.commons/commons-pool2/2.5.0 Bundle ID: 244 karaf@root()> install -s mvn:org.messaginghub/pooled-jms/0.3.0 Bundle ID: 245 karaf@root()> install -s blueprint:file://$PQ_HOME/message-brokers/blueprints/artemis-manual.xml Bundle ID: 246 karaf@root()> bundle:services -p 246 Bundle 246 provides: -------------------- objectClass = [javax.jms.ConnectionFactory] osgi.jndi.service.name = jms/artemis osgi.service.blueprint.compname = pool service.bundleid = 246 service.id = 340 service.scope = bundle ----- objectClass = [org.osgi.service.blueprint.container.BlueprintContainer] osgi.blueprint.container.symbolicname = artemis-manual.xml osgi.blueprint.container.version = 0.0.0 service.bundleid = 246 service.id = 341 service.scope = singleton karaf@root()> feature:install jms karaf@root()> jms:connectionfactories JMS Connection Factory ────────────────────── jms/artemis karaf@root()> jms:info -u admin -p admin jms/artemis Property │ Value ─────────┼────────────────────────── product │ ActiveMQ version │ 2.4.0.amq-711002-redhat-1
如上方列表所示,Bartner 捆绑包将导出 javax.jms.ConnectionFactory
服务,该服务是一个通用、非特定于代理的连接池。特定于代理的 javax.jms.XAConnectionFactory
没有 注册为 OSGi 服务,因为 Blueprint XML 没有明确的 < service ref="artemis">
声明。
7.6.2. 连接工厂部署
此方法以 规范 的方式显示使用 pax-jms-config
。这与 Fuse 6.x 推荐的方法稍有不同,因为要求将配置指定为服务属性。
下面是蓝图 XML 示例:
<!-- A broker-specific org.ops4j.pax.jms.service.ConnectionFactoryFactory that can create (XA)ConnectionFactory using properties. It is registered by pax-jms-* bundles --> <reference id="connectionFactoryFactory" interface="org.ops4j.pax.jms.service.ConnectionFactoryFactory" filter="(type=artemis)" /> <!-- Non broker-specific org.ops4j.pax.jms.service.PooledConnectionFactoryFactory that can create pooled connection factories with the help of org.ops4j.pax.jms.service.ConnectionFactoryFactory For example, pax-jms-pool-pooledjms bundle registers org.ops4j.pax.jms.service.PooledConnectionFactoryFactory with these properties: - pool = pooledjms - xa = true|false (both are registered) --> <reference id="pooledConnectionFactoryFactory" interface="org.ops4j.pax.jms.service.PooledConnectionFactoryFactory" filter="(&(pool=pooledjms)(xa=true))" /> <!-- When using XA connection factories, javax.transaction.TransactionManager service is not needed here. It is used internally by xa-aware pooledConnectionFactoryFactory. --> <!--<reference id="tm" interface="javax.transaction.TransactionManager" />--> <!-- Finally, use both factories to expose the pooled, xa-aware, connection factory. --> <bean id="pool" factory-ref="pooledConnectionFactoryFactory" factory-method="create"> <argument ref="connectionFactoryFactory" /> <argument> <props> <!-- Properties needed by artemis-specific org.ops4j.pax.jms.service.ConnectionFactoryFactory --> <prop key="jms.url" value="tcp://localhost:61616" /> <prop key="jms.callTimeout" value="2000" /> <prop key="jms.initialConnectAttempts" value="3" /> <!-- Properties needed by pooled-jms-specific org.ops4j.pax.jms.service.PooledConnectionFactoryFactory --> <prop key="pool.maxConnections" value="10" /> <prop key="pool.idleTimeout" value="10000" /> </props> </argument> </bean> <!-- Expose connection factory for use by application code (such as Camel, Spring, ...) --> <service interface="javax.jms.ConnectionFactory" ref="pool"> <service-properties> <!-- Giving connection factory a name using one of these properties makes identification easier in jms:connectionfactories: --> <entry key="osgi.jndi.service.name" value="jms/artemis" /> <!--<entry key="name" value="jms/artemis" />--> <!-- Without any of the above, name will fall back to "service.id" --> </service-properties> </service>
上例使用 factory an 使用 connection factory factories(…)创建连接工厂。不需要明确引用 javax.transaction.TransactionManager
服务,因为它由 XA 感知池ed ConnectionFactoryonnectionFactoryy
内部跟踪。
以下是它在 Fuse/Karaf shell 中查找的方式:
karaf@root()> feature:install jms pax-jms-artemis pax-jms-pool-pooledjms karaf@root()> install -s blueprint:file://$PQ_HOME/message-brokers/blueprints/artemis-pax-jms-factory-pooledjms.xml Bundle ID: 253 karaf@root()> bundle:services -p 253 Bundle 253 provides: -------------------- objectClass = [javax.jms.ConnectionFactory] osgi.jndi.service.name = jms/artemis osgi.service.blueprint.compname = pool service.bundleid = 253 service.id = 347 service.scope = bundle ----- objectClass = [org.osgi.service.blueprint.container.BlueprintContainer] osgi.blueprint.container.symbolicname = artemis-pax-jms-factory-pooledjms.xml osgi.blueprint.container.version = 0.0.0 service.bundleid = 253 service.id = 348 service.scope = singleton karaf@root()> jms:connectionfactories JMS Connection Factory ────────────────────── jms/artemis karaf@root()> jms:info -u admin -p admin jms/artemis Property │ Value ─────────┼────────────────────────── product │ ActiveMQ version │ 2.4.0.amq-711002-redhat-1
如上方列表所示,Bartner 捆绑包将导出 javax.jms.ConnectionFactory
服务,该服务是一个通用、非特定于代理的连接池。代理特定 javax.jms.XAConnectionFactory
没有 注册为 OSGi 服务,因为 Blueprint XML 没有明确的 < service ref="artemis">
声明。
7.6.3. 连接工厂的混合部署
pax-jms-config
1.0.0 bundle 使用服务属性添加了另一种 嵌套 代理特定连接工厂中的一种方式。此方法与在 Fuse 6.x 中工作的方式匹配。
下面是蓝图 XML 示例:
<!-- Broker-specific, non-pooling, non-enlisting javax.jms.XAConnectionFactory --> <bean id="artemis" class="org.apache.activemq.artemis.jms.client.ActiveMQXAConnectionFactory"> <argument value="tcp://localhost:61616" /> <property name="callTimeout" value="2000" /> <property name="initialConnectAttempts" value="3" /> </bean> <!-- Expose broker-specific connection factory with service properties. No need to expose pooling, enlisting, non broker-specific javax.jms.XAConnectionFactory. It will be registered automatically by pax-jms-config with the same properties as this <service>, but with a higher service.ranking --> <service id="pool" ref="artemis" interface="javax.jms.XAConnectionFactory"> <service-properties> <!-- "pool" key is needed for pax-jms-config to wrap broker-specific connection factory inside connection pool --> <entry key="pool" value="pooledjms" /> <!-- <service>/@id attribute does not propagate, but name of the connection factory is required using one of: --> <entry key="osgi.jndi.service.name" value="jms/artemis" /> <!-- or: --> <!--<entry key="name" value="jms/artemis" />--> <!-- Other properties, that normally by e.g., pax-jms-pool-pooledjms --> <entry key="pool.maxConnections" value="10" /> <entry key="pool.idleTimeout" value="10000" /> </service-properties> </service>
在上例中,您可以看到仅手动注册代理特定连接工厂。pool=pooledjms
服务属性是连接工厂跟踪器的提示,它由 pax-jms-config
捆绑包管理。带有这个 service 属性的连接工厂服务封装在池工厂内,在本示例中,pax-jms-pool-pooledjms
。
以下是它在 Fuse/Karaf shell 中查找的方式:
karaf@root()> feature:install jms pax-jms-config pax-jms-artemis pax-jms-pool-pooledjms karaf@root()> install -s blueprint:file://$PQ_HOME/message-brokers/blueprints/artemis-pax-jms-discovery.xml Bundle ID: 254 karaf@root()> bundle:services -p 254 Bundle 254 provides: -------------------- objectClass = [javax.jms.XAConnectionFactory] osgi.jndi.service.name = jms/artemis osgi.service.blueprint.compname = artemis pool = pooledjms pool.idleTimeout = 10000 pool.maxConnections = 10 service.bundleid = 254 service.id = 349 service.scope = bundle ----- objectClass = [org.osgi.service.blueprint.container.BlueprintContainer] osgi.blueprint.container.symbolicname = artemis-pax-jms-discovery.xml osgi.blueprint.container.version = 0.0.0 service.bundleid = 254 service.id = 351 service.scope = singleton karaf@root()> service:list javax.jms.XAConnectionFactory [javax.jms.XAConnectionFactory] ------------------------------- osgi.jndi.service.name = jms/artemis osgi.service.blueprint.compname = artemis pool = pooledjms pool.idleTimeout = 10000 pool.maxConnections = 10 service.bundleid = 254 service.id = 349 service.scope = bundle Provided by : Bundle 254 Used by: OPS4J Pax JMS Config (251) karaf@root()> service:list javax.jms.ConnectionFactory [javax.jms.ConnectionFactory] ----------------------------- osgi.jndi.service.name = jms/artemis osgi.service.blueprint.compname = artemis pax.jms.managed = true pax.jms.service.id.ref = 349 pool.idleTimeout = 10000 pool.maxConnections = 10 service.bundleid = 251 service.id = 350 service.ranking = 1000 service.scope = singleton Provided by : OPS4J Pax JMS Config (251) karaf@root()> jms:connectionfactories JMS Connection Factory ────────────────────── jms/artemis karaf@root()> jms:info -u admin -p admin jms/artemis Property │ Value ─────────┼────────────────────────── product │ ActiveMQ version │ 2.4.0.amq-711002-redhat-1
在上例中,jms:connectionfactories
仅显示一个服务,因为此命令会删除重复的名称。jdbc:ds-list
在数据源混合部署中介绍了两种服务。
javax.jms.XAConnectionFactory
从 Blueprint 捆绑包注册,它含有 pool = pooledjms
属性。
javax.jms.ConnectionFactory
从 pax-jms-config
捆绑包中注册,并且:
-
它没有
pool = pooledjms
属性。在注册打包程序连接工厂时会删除它。 -
它具有
service.ranking = 1000
属性,因此它始终是何时首选版本,例如查找连接工厂的名称。 -
它具有
pax.jms.managed = true
属性,因此它不会尝试再次换行。 -
它具有
pax.jms.service.id.ref = 349
属性,它指示在连接池中嵌套的原始连接工厂服务。
第 8 章 关于 Java 连接器架构
JCA 规格被创建为(另一方面)说出具有三个参与者的情况:
- 数据库等外部系统,或一般为 E IS 系统
- JavaEE 应用程序服务器
- 已部署的应用程序
8.1. 简单 JDBC 模拟
在最简单的场景中,只有一个应用程序和数据库,您有:
添加公开 javax.sql.DataSource
的应用服务器,您具有以下内容(不需要重新调用如 XA 之类的数据源的不同方面):
8.2. 使用 JCA 概述
JCA 通过添加 驱动程序与应用服务器之间的双向通信来规范数据库 驱动程序 的概念。该驱动程序成为由 javax. resource.spi.ResourceAdapter 代表的资源适配器
。
有两个重要的接口:
-
javax.resource.spi.ManagedConnectionFactory
由资源适配器实施。 -
javax.resource.spi.ConnectionManager
由应用服务器实施。
ManagedConnectionFactory
接口提供两个目的:
Object createConnectionFactory(ConnectionManager cxManager)
方法可用于为应用程序代码使用的给定 EIS(或数据库或数据库或消息代理)生成 连接工厂。返回的对象
可以是:-
通用
javax.resource.cci.ConnectionFactory
(此处未进一步介绍,请参阅 JCA 1.6,第 17 章: 通用客户端接口) -
EIS 特定连接工厂,如已知的
javax.sql.DataSource
或javax.jms.ConnectionFactory
。它是 pax-transx-jdbc 和
捆绑包使用的 连接工厂 类型。pax-transx
-jms
-
通用
-
javax.resource.spi.ManagedConnection ManagedConnectionFactory.createManagedConnection()
方法供 应用服务器 使用,创建到 EIS/database/broker 的实际物理连接。
ConnectionManager
由 应用服务器 实施,供 资源适配器 使用。它是首次执行 QoS 操作(池、安全、事务管理)的应用服务器,最终将 资源适配器的 managedConnectionFactory
委派给创建 ManagedConnection
实例。流如下:
-
应用代码使用从
ManagedConnectionFactory.createConnectionFactory()
返回的对象,由 应用服务器 创建并公开 连接工厂。它可能是通用 CCI 接口,如javax.sql.DataSource
。 -
这个 连接工厂 不会自行创建 连接,而是委派至
ConnectionManager.allocateConnection()
传递 资源适配器- 特定的ManagedConnectionFactory
-
应用程序服务器 实施的
ConnectionManager
创建支持 对象、管理事务、池等,并且最终从传递ManagedConnectionFactory
获取 物理(托管)连接。 - 应用程序代码获取 连接,通常是由 应用服务器 创建的 wrapper/proxy,后者最终委派给 资源适配器 的特定 物理连接。
下面是一个图,其中 应用服务器 创建了非CCI 连接工厂 (特定于 EIS)。只需 - 对 EIS 的访问(这里:数据库)是使用 javax.sql.DataSource
接口完成的,驱动程序的任务是提供 物理连接,而 应用服务器 会将其嵌套在(通常)执行池/enlist/安全性的代理中。
8.3. 关于 pax-transx 项目
pax-transx
项目为 OSGi 中的 JTA/JTS 事务管理提供支持,以及 JDBC 和 JMS 资源池。它关闭 pax-jdbc
和 pax-jms
间的差距。
-
pax-jdbc
添加了javax.sql.(XA)ConnectionFactory
服务的配置选项和发现,并附带一些 JDBC 池实施 -
pax-jms
执行与javax.jms.(XA)ConnectionFactory
服务相同的功能,并随附一些 JMS 池实施 -
pax-transx
添加了javax.transaction.TransactionManager
实施的配置选项和发现,并通过池ing 和 tranasction 支持提供基于 JCA 的 JDBC/JMS 连接管理。
关于 JDBC 连接池 和 JMS 连接池 的部分仍然有效。在注册 JDBC 数据源和 JMS 连接因素时,需要更改使用基于 JCA 的池的唯一更改是使用 pool=transx
属性。
-
pax-jdbc-pool-transx
使用org.ops4j.pax.transx.jdbc.ManagedDataSourceBuilder
frompax-transx-jdbc
-
pax-jms-pool-transx
使用org.ops4j.pax.transx.jms.ManagedConnectionFactoryBuilder
frompax-transx-jms
虽然池数据源/连接工厂以 构建器风格 (无 Java™ bean 属性)创建,但 JDBC 支持这些属性:
-
name
-
userName
-
password
-
commitBeforeAutocommit
-
preparedStatementCacheSize
-
transactionIsolationLevel
-
minIdle
-
maxPoolSize
-
aliveBypassWindow
-
houseKeepingPeriod
-
connectionTimeout
-
idleTimeout
-
maxLifetime
JMS 支持这些属性:
-
name
-
userName
-
password
-
clientID
-
minIdle
-
maxPoolSize
-
aliveBypassWindow
-
houseKeepingPeriod
-
connectionTimeout
-
idleTimeout
-
maxLifetime
XA 恢复工作需要 用户名和密码
属性(就像使用 Fuse 6. xa.
password 中的 ries.xa.password
属性相同)。
在 Blueprint 中使用此 JDBC 配置(mind pool=transx
):
<!-- Database-specific, non-pooling, non-enlisting javax.sql.XADataSource --> <bean id="postgresql" class="org.postgresql.xa.PGXADataSource"> <property name="url" value="jdbc:postgresql://localhost:5432/reportdb" /> <property name="user" value="fuse" /> <property name="password" value="fuse" /> <property name="currentSchema" value="report" /> <property name="connectTimeout" value="5" /> </bean> <!-- Expose database-specific data source with service properties No need to expose pooling, enlisting, non database-specific javax.sql.DataSource - it'll be registered automatically by pax-jdbc-config with the same properties as this <service>, but with higher service.ranking --> <service id="pool" ref="postgresql" interface="javax.sql.XADataSource"> <service-properties> <!-- "pool" key is needed for pax-jdbc-config to wrap database-specific data source inside connection pool --> <entry key="pool" value="transx" /> <!-- <service>/@id attribute doesn't propagate, but name of the datasource is required using one of: --> <entry key="osgi.jndi.service.name" value="jdbc/postgresql" /> <!-- or: --> <!--<entry key="dataSourceName" value="jdbc/postgresql" />--> <!-- Other properties, that normally are needed by e.g., pax-jdbc-pool-transx --> <entry key="pool.maxPoolSize" value="13" /> <entry key="pool.userName" value="fuse" /> <entry key="pool.password" value="fuse" /> </service-properties> </service>
并使用此 JMS 配置在 Blueprint 中(mind pool=transx
):
<!-- Broker-specific, non-pooling, non-enlisting javax.jms.XAConnectionFactory --> <bean id="artemis" class="org.apache.activemq.artemis.jms.client.ActiveMQXAConnectionFactory"> <argument index="0" value="tcp://localhost:61616" /> <!-- credentials needed for JCA-based XA-recovery --> <argument index="1" value="admin" /> <argument index="2" value="admin" /> <property name="callTimeout" value="2000" /> <property name="initialConnectAttempts" value="3" /> </bean> <!-- Expose broker-specific connection factory with service properties No need to expose pooling, enlisting, non broker-specific javax.jms.XAConnectionFactory - it'll be registered automatically by pax-jms-config with the same properties as this <service>, but with higher service.ranking --> <service id="pool" ref="artemis" interface="javax.jms.XAConnectionFactory"> <service-properties> <!-- "pool" key is needed for pax-jms-config to wrap broker-specific connection factory inside connection pool --> <entry key="pool" value="transx" /> <!-- <service>/@id attribute doesn't propagate, but name of the connection factory is required using one of: --> <entry key="osgi.jndi.service.name" value="jms/artemis" /> <!-- or: --> <!--<entry key="name" value="jms/artemis" />--> <!-- Other properties, that normally are needed e.g., pax-jms-pool-transx --> <entry key="pool.maxPoolSize" value="13" /> <entry key="pool.userName" value="admin" /> <entry key="pool.password" value="admin" /> </service-properties> </service>
您有一个 JDBC 数据源和 JMS 连接工厂注册,该注册了使用基于 JCA 的资源管理。基于 transx 的池将正确与 pax-transx-tm-narayana
整合到了 XA 恢复。
需要的功能有:
-
pax-jdbc-pool-tranx
-
pax-jms-pool-tranx
-
pax-transx-jdbc
-
pax-transx-jms
-
pax-jms-artemis
(在使用 A-MQ 7 时)
第 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");
9.2. 通过事务端点划分
如果路由开始时的消费者端点访问资源,则 transacted()
命令就不使用,因为它在轮询交换后发起事务。换句话说,交易开始过晚,将消费者端点包含在事务范围内。在这种情况下,正确的方法是使端点本身负责发起事务。能够管理事务的端点称为 事务端点。
事务端点划分有两种不同的模型,如下所示:
通常,事务端点会按以下方式分离事务:
- 当交换到达端点时,或者当端点成功轮询交换时,端点会调用其关联的事务管理器以开始事务。
- 端点将新事务附加到当前线程。
- 当交换达到路由结束时,事务端点会调用事务管理器提交当前的事务。
- 带有 InOut 交换的 JMS 端点 - 当 JMS 消费者端点接收 InOut 交换时,该交换将路由到另一个 JMS 端点,则必须被视为特殊情况。问题是,如果尝试在单个事务中包括整个请求/回复,则路由可能会死锁。
9.2.1. 带有 JMS 端点的路由示例
第 9.2 节 “通过事务端点划分” 显示了一个路由示例,它通过路由开头的事务端点(在 from()
命令中)存在事务端点进行事务。所有路由节点都包含在事务范围内。在本例中,路由中的所有端点都访问 JMS 资源。
9.2.2. Java DSL 中的路由定义
以下 Java DSL 示例演示了如何通过启动事务端点的路由来定义事务路由:
from("jmstx:queue:giro") .to("jmstx:queue:credits") .to("jmstx:queue:debits");
在前面的示例中,事务范围包含端点、jmstx:queue:giro
、jmstx:queue:credits
和 jmstx:queue:debits
。如果事务成功,则从 giro
队列永久移除交换,并推送到 信
队列和 debits
队列。如果事务失败,则交易不会转向贡献度和分 位
队列,并将交换重新推送到 giro
队列。默认情况下,JMS 会自动尝试重新传送消息。JMS 组件 bean 和
jmstx
必须明确配置为使用事务,如下所示:
<blueprint ...> <bean id="jmstx" class="org.apache.camel.component.jms.JmsComponent"> <property name="configuration" ref="jmsConfig" /> </bean> <bean id="jmsConfig" class="org.apache.camel.component.jms.JmsConfiguration"> <property name="connectionFactory" ref="jmsConnectionFactory" /> <property name="transactionManager" ref="jmsTransactionManager" /> <property name="transacted" value="true" /> </bean> ... </blueprint>
在上例中,transaction manager 实例( jmsTransactionManager
)与 JMS 组件关联,而 transacted
属性设为 true
,以启用对 InOnly 交换的事务解包。
9.2.3. Blueprint XML 中的路由定义
前面的路由可以等效在 Blueprint XML 中,如下所示:
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"> <camelContext xmlns="http://camel.apache.org/schema/blueprint"> <route> <from uri="jmstx:queue:giro" /> <to uri="jmstx:queue:credits" /> <to uri="jmstx:queue:debits" /> </route> </camelContext> </blueprint>
9.2.4. DSL 翻译()
命令不需要
在以事务性端点开头的路由中不需要 transacted()
DSL 命令。然而,假设默认事务策略是 PROPAGATION_REQUIRED
(请参阅 第 9.4 节 “事务传播策略”),通常会损害包含 transacted()
命令,如下例所示:
from("jmstx:queue:giro") .transacted() .to("jmstx:queue:credits") .to("jmstx:queue:debits");
但是,此路由可能会以意外的方式工作,例如,如果一个 TransactedPolicy
有一个有非默认传播策略在 Blueprint XML 中创建。请参阅 第 9.1.4 节 “默认事务管理器和转换策略”。因此,通常最好不要将 transacted()
DSL 命令包含在开头事务端点的路由中。
9.2.5. 路由开始时的事务端点
以下 Apache Camel 组件在路由开始时显示为事务端点(例如,如果它们出现在 from()
DSL 命令中)。也就是说,这些端点可以配置为作为事务客户端的行为,也可以访问事务的资源。
- ActiveMQ
- AMQP
- JavaSpace
- JMS
- JPA
9.3. 声明性事务划分
使用蓝图 XML 时,您还可以通过在 Blueprint XML 文件中声明事务来分离事务。例如,通过将适当的事务策略应用到 bean 或 bean 方法,您可以确保在调用特定 bean 或 bean 方法时启动事务。在 bean 方法的末尾,交易会被提交。这种方法和交易在企业 Java Bean 中处理的方式类似。
OSGi 声明事务允许您在蓝图中的以下范围定义事务策略:
另请参阅: 第 9.3.3 节 “tx:transaction
属性的描述”。
9.3.1. bean-level 声明
要在 bean 级别声明事务策略,插入 tx:transaction
元素作为 bean
元素的子项,如下所示:
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:tx="http://aries.apache.org/xmlns/transactions/v1.1.0"> <bean id="accountFoo" class="org.jboss.fuse.example.Account"> <tx:transaction method="*" value="Required" /> <property name="accountName" value="Foo" /> </bean> <bean id="accountBar" class="org.jboss.fuse.example.Account"> <tx:transaction method="*" value="Required" /> <property name="accountName" value="Bar" /> </bean> </blueprint>
在前面的示例中,所需的事务策略应用于 accountFoo
bean 和 accountBar
bean 的所有方法,其中 method 属性指定通配符 :*
以匹配所有 bean 方法。
9.3.2. 顶级声明
要在顶层声明事务策略,在 tx:transaction
元素中插入 tx:transaction 元素作为 蓝图
元素的子项,如下所示:
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:tx="http://aries.apache.org/xmlns/transactions/v1.1.0"> <tx:transaction bean="account*" value="Required" /> <bean id="accountFoo" class="org.jboss.fuse.example.Account"> <property name="accountName" value="Foo" /> </bean> <bean id="accountBar" class="org.jboss.fuse.example.Account"> <property name="accountName" value="Bar" /> </bean> </blueprint>
在前面的示例中,Required
事务策略应用于 ID
与模式匹配的所有 bean 的所有方法。
9.3.3. tx:transaction
属性的描述
tx:transaction
元素支持以下属性:
bean
(仅限顶级)指定事务策略应用到的 bean ID(组合或空格分隔)的列表。例如:
<blueprint ...> <tx:transaction bean="accountFoo,accountBar" value="..." /> </blueprint>
您还可以使用通配符字符
*
,每个列表条目中最多可能会显示一次。例如:<blueprint ...> <tx:transaction bean="account*,jms*" value="..." /> </blueprint>
如果省略了 bean 属性,则默认为
*
(与蓝图文件中所有非动态 Bean 匹配)。method
(顶级和 bean 级)指定事务策略应用到的方法名称(组合或空格分隔)的列表。例如:
<bean id="accountFoo" class="org.jboss.fuse.example.Account"> <tx:transaction method="debit,credit,transfer" value="Required" /> <property name="accountName" value="Foo" /> </bean>
您还可以使用通配符字符
*
,每个列表条目中最多可能会显示一次。如果省略了 method 属性,则默认为
*
(匹配适用 Bean 中的所有方法)。- value
(顶级和 bean 级)指定事务策略。策略值具有与 EJB 3.0 规范中定义的策略相同的语义,如下所示:
-
必需
- 支持当前的事务;如果不存在,则创建一个新事务。 -
强制
- 支持当前事务;如果不存在当前事务,则抛出异常。 -
RequiresNew
- 创建一个新事务,并暂停当前事务(如果存在)。 -
支持
- 支持当前的事务;如果不存在,则以非交易方式执行。 -
NotSupported
- 不支持当前的事务,而是始终以非事务方式执行。 -
Never
- 不支持当前的事务 ; 如果当前事务存在,则抛出异常。
-
9.4. 事务传播策略
如果要影响事务客户端创建新事务的方式,您可以使用 JmsTransactionManager
并为其指定事务策略。特别是 Spring transaction 策略,您可以指定事务的传播行为。例如,如果事务客户端要创建新事务,并且它检测到该事务已与当前线程关联,是否应该会失败并创建新的事务,并挂起旧的事务?或者说是否让现有事务接管?通过在事务策略中指定传播行为来监管这些行为。
事务策略在 Blueprint XML 中实例化。然后,您可以通过提供 bean ID
作为 transacted()
DSL 命令的参数来引用事务策略。例如,如果要启动受到行为的事务,则 PROPAGATION_REQUIRES_NEW
来说,您可以使用以下路由:
from("file:src/data?noop=true") .transacted("PROPAGATION_REQUIRES_NEW") .bean("accountService","credit") .bean("accountService","debit") .to("file:target/messages");
其中
参数指定使用 PROPAGATION_REQUIRES_NEW 行为的事务策略的 bean PROPAGATION_REQUIRES_NEW
ID
。请参阅 第 9.4.3 节 “在 Blueprint XML 中定义策略 Bean”。
9.4.1. 关于 Spring 事务策略
Apache Camel 可让您使用 org.apache.camel.spring.spi.spring.spi.SpringTransactionPolicy
类来定义 Spring 事务策略,它基本上是一个围绕原生 Spring 类的打包程序。SpringTransactionPolicy
类封装了两部分数据:
-
对
PlatformTransactionManager
类型的事务管理器的引用 - 传播行为
例如,您可以使用 PROPAGATION_MANDATORY
行为实例化 Spring transaction 策略,如下所示:
<blueprint ...> <bean id="PROPAGATION_MANDATORY "class="org.apache.camel.spring.spi.SpringTransactionPolicy"> <property name="transactionManager" ref="txManager" /> <property name="propagationBehaviorName" value="PROPAGATION_MANDATORY" /> </bean> ... </blueprint>
9.4.2. 传播行为的描述
Spring 支持以下传播行为。这些值最初基于 JavaeEE 支持的传播行为:
- PROPAGATION_MANDATORY
- 支持当前的事务。如果不存在当前事务,则抛出异常。
- PROPAGATION_NESTED
如果当前事务存在,则在嵌套的事务中执行,其他行为与
PROPAGATION_REQUIRED
类似。注意所有事务管理器不支持嵌套事务。
- PROPAGATION_NEVER
- 不支持当前的事务。如果当前事务存在,则抛出异常。
- PROPAGATION_NOT_SUPPORTED
不支持当前的事务。始终以非处理方式执行。
注意此策略要求当前事务被暂停,这是所有事务管理器不支持的功能。
- PROPAGATION_REQUIRED
- (默认)支持当前事务。如果不存在,创建一个新名称。
- PROPAGATION_REQUIRES_NEW
创建一个新事务,挂起当前事务(如果存在)。
注意所有事务管理器都不支持挂起事务。
- PROPAGATION_SUPPORTS
- 支持当前的事务。如果不存在,则以非事务方式执行。
9.4.3. 在 Blueprint XML 中定义策略 Bean
以下示例演示了如何为所有支持的传播行为定义事务策略 Bean。为方便起见,每个 bean ID 都与传播行为值的指定的值匹配,但在实践中,您可以使用您喜欢该 bean ID 的任何值。
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <bean id="PROPAGATION_MANDATORY " class="org.apache.camel.spring.spi.SpringTransactionPolicy"> <property name="transactionManager" ref="txManager" /> <property name="propagationBehaviorName" value="PROPAGATION_MANDATORY" /> </bean> <bean id="PROPAGATION_NESTED" class="org.apache.camel.spring.spi.SpringTransactionPolicy"> <property name="transactionManager" ref="txManager" /> <property name="propagationBehaviorName" value="PROPAGATION_NESTED" /> </bean> <bean id="PROPAGATION_NEVER" class="org.apache.camel.spring.spi.SpringTransactionPolicy"> <property name="transactionManager" ref="txManager" /> <property name="propagationBehaviorName" value="PROPAGATION_NEVER" /> </bean> <bean id="PROPAGATION_NOT_SUPPORTED" class="org.apache.camel.spring.spi.SpringTransactionPolicy"> <property name="transactionManager" ref="txManager" /> <property name="propagationBehaviorName" value="PROPAGATION_NOT_SUPPORTED" /> </bean> <!-- This is the default behavior. --> <bean id="PROPAGATION_REQUIRED" class="org.apache.camel.spring.spi.SpringTransactionPolicy"> <property name="transactionManager" ref="txManager" /> </bean> <bean id="PROPAGATION_REQUIRES_NEW" class="org.apache.camel.spring.spi.SpringTransactionPolicy"> <property name="transactionManager" ref="txManager" /> <property name="propagationBehaviorName" value="PROPAGATION_REQUIRES_NEW" /> </bean> <bean id="PROPAGATION_SUPPORTS" class="org.apache.camel.spring.spi.SpringTransactionPolicy"> <property name="transactionManager" ref="txManager" /> <property name="propagationBehaviorName" value="PROPAGATION_SUPPORTS" /> </bean> </blueprint>
如果要将任何 bean 定义粘贴到您自己的蓝图 XML 配置中,请记住对事务管理器的引用。也就是说,将对 txManager
的引用替换为您的事务管理器 bean 的实际 ID
。
9.4.4. Java DSL 中带有 PROPAGATION_NEVER
策略的示例
演示该事务策略对事务的影响的简单方法是将 PROPAGATION_NEVER
策略插入到现有事务中,如以下路由所示:
from("file:src/data?noop=true") .transacted() .bean("accountService","credit") .transacted("PROPAGATION_NEVER") .bean("accountService","debit");
使用这种方法时,PR OPAGATION_NEVER
策略不可避免地中止每个事务,从而导致事务回滚。您应该能轻松查看这对应用程序的影响。
请记住,传递到 transacted()
的字符串值是一个 bean ID
,而不是传播行为名称。在本例中,将选择 bean ID
与传播行为名称相同,但这不是必须的。例如,如果您的应用程序使用多个事务管理器,您可能会遇到多个具有特定传播行为的策略。在这种情况下,您不能在传播行为后直接命名 Bean。
9.4.5. 蓝图 XML 中的 PROPAGATION_N
EVER 策略示例
以上路由可以在 Blueprint 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" /> <transacted ref="PROPAGATION_NEVER" /> <bean ref="accountService" method="debit" /> </route> </camelContext> </blueprint>
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()
子句不会重新定义异常,因此文件端点不会进行任何重试,避免无限循环。