Menu Close
Settings Close

Language and Page Formatting Options

Apache Karaf 事务指南

Red Hat Fuse 7.11

为 Apache Karaf 容器编写事务应用程序

摘要

为 Fuse 开发事务感知应用程序

前言

本指南为实施 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 子句读取一系列行的 SQL SELECT 语句。如果另一个事务在第一个事务运行时添加一个行到这个范围,如果第一个事务正在运行,则第一个事务可以看到这个新行,如果它重复 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 分发的位置。构建此项目:

  1. 调用 Maven 来构建项目:

    $ cd quickstarts
    
    $ mvn clean install -f camel/camel-jms/
  2. 创建 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/
  3. 验证公布的连接工厂:

    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
  4. 安装捆绑包:

    karaf@root()> install -s mvn:org.jboss.fuse.quickstarts/camel-jms/7.0.0.redhat-SNAPSHOT
    Bundle ID: 256
  5. 确认它正在运行:

    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
  6. Camel 路由启动后,您可以在 Fuse 安装中看到一个目录 work/jms/input。将您在此 Quickstart 的 src/main/data 目录中找到的文件复制到新创建的 work/jms/input 目录中。
  7. 稍等片刻,您将在 work/jms/output 目录下找到由国家组织相同的文件:

    • work/jms/output/others中,order1.xmlorder2.xmlorder4.xml
    • work/jms/output/us中的 order3.xmlorder5.xml
    • work/jms/output/fr中的 order6.xml
  8. 查看日志以查看业务日志:

    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
  9. 查看队列是否动态创建:

    karaf@root()> jms:queues -u admin -p admin artemis
    JMS Queues
    ────────────────────────────────────
    DLQ
    17767323-937f-4bad-a403-07cd63311f4e
    ExpiryQueue
    incomingOrders
  10. 检查 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 BEGINCOMMITROLLBACK 语句,或者使用原生 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 应用。

JavaEE 事务 api

图显示 Camel 代码和应用程序代码都可以访问:

  • javax.transaction.UserTransaction 实例,使用 Spring TransactionTemplate 类直接从应用程序或内部通过事务感知型 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 应用程序服务器,通常是 WAREAR 存档。通过 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 APISpring 工具程序配置使用

JDBC

org.springframework.jdbc.core.JdbcTemplate

javax.sql.DataSource

JMS

org.springframework.jms.core.JmsTemplate

javax.jms.ConnectionFactory

JTA

org.springframework.transaction.support.TransactionTemplate

org.springframework.transaction.PlatformTransactionManager

JdbcTemplateJmsTemplate 分别使用 javax.sql.DataSourcejavax.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 中,您通常使用事务管理器,如下所示:

  1. 创建事务管理器实例。Spring 中有几种不同的实现,请参阅 第 3.4 节 “使用 Spring Boot 事务客户端”
  2. 将事务管理器实例传递到 Apache Camel 组件或路由中的 transacted() DSL 命令。然后,事务组件或 transacted() 命令负责分离事务。详情请查看 第 9 章 编写使用事务的 Camel 应用程序

3.4.3. 关于 Spring PlatformTransactionManager 实现

本节概述了 Spring Framework 提供的事务管理器实现。该实施分为两类:本地事务管理器和全球交易经理。

从 Camel 开始:

  • camel-jms 组件使用的 org.apache.camel.component.JmsConfiguration 对象需要 org.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 的实际实现。

因此,您可以在可以访问的环境中运行 PlatformTransactionManagerJtaTransactionManager 实施(通过 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 对象(UserTransactionTransactionManager)的实际实例来自 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 获取事务管理器、数据源和连接因素的引用。

OSGi 事务架构

应用程序(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.0.fuse-7_11_0-00036-redhat-00001 发行版使这些接口从一开始可用。

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.0.fuse-7_11_0-00036-redhat-00001/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.0.fuse-7_11_0-00036-redhat-00001/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.0.fuse-7_11_0-00036-redhat-00001/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.0.fuse-7_11_0-00036-redhat-00001/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属性前缀

com.arjuna.ats.arjuna.common.CoordinatorEnvironmentBean

com.arjuna.ats.arjuna.coordinator

com.arjuna.ats.arjuna.common.CoreEnvironmentBean

com.arjuna.ats.arjuna

com.arjuna.ats.internal.arjuna.objectstore.hornetq.HornetqJournalEnvironmentBean

com.arjuna.ats.arjuna.hornetqjournal

com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean

com.arjuna.ats.arjuna.objectstore

com.arjuna.ats.arjuna.common.RecoveryEnvironmentBean

com.arjuna.ats.arjuna.recovery

com.arjuna.ats.jdbc.common.JDBCEnvironmentBean

com.arjuna.ats.jdbc

com.arjuna.ats.jta.common.JTAEnvironmentBean

com.arjuna.ats.jta

com.arjuna.ats.txoj.common.TxojEnvironmentBean

com.arjuna.ats.txoj.lockstore

前缀 可以简化配置。但是,您通常应该使用以下格式之一:

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 访问的通用模式是:

  1. 应用代码要求 javax.sql.DataSource 用于 JDBC 访问,而用于 JMS 的 javax.jms.ConnectionFactory 以获取 JDBC 或 JMS 连接。
  2. 在应用程序/OSGi 服务器中,注册这些接口的数据库或代理特定实现。
  3. 应用程序/OSGi 服务器将特定于数据库/代理的因素嵌套成通用池,以及列出因素。

这样,应用程序代码仍然使用 javax.sql.DataSourcejavax.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.ConnectionPoolDataSource

javax.sql.PooledConnection

javax.sql.XADataSource

javax.sql.XAConnection

以上每个 数据源 之间最重要的区别如下:

  • javax.sql.DataSource 最重要的是一个类似于 java.sql.Connection 实例的 工厂。大多数 javax.sql.DataSource 实现通常执行 连接池 不应更改图片的事实。这是应用程序代码应使用的唯一接口。您正在实施以下哪些方法无关紧要:

    • 直接 JDBC 访问
    • JPA 持久性单元配置(&lt; jta-data-source > 或 &lt ;non-jta-data-source>
    • 常见库,如 Apache Camel 或 Spring Framework
  • javax.sql.ConnectionPoolDataSource 最重要的是通用(特定于数据库)连接池/数据源之间的 桥接。它可以被视为 SPI 接口。应用程序代码通常处理从 JNDI 获取的通用 javax.sql.DataSource 对象,并由应用服务器实施(可能使用 commons-dbcp2等库)。在另一个结束时,应用程序代码没有直接与 javax.sql.ConnectionPoolDataSource 的接口。它用于应用服务器和特定于数据库的驱动程序。以下序列图显示了这一点:

    Application -> "App Server": javax.sql.DataSource.getConnection()
    "App Server" -[#0000FF]> "Database Driver": javax.sql.ConnectionPoolDataSource.getPooledConnection()
    "Database Driver" -> "App Server": javax.sql.PooledConnection
    "App Server" -> Application: javax.sql.PooledConnection.getConnection()
  • javax.sql.XADataSource 是一种获取 javax.sql.XAConnectionjavax.transaction.xAResource 的方法。与 javax.sql.ConnectionPoolDataSource 相同,它在应用服务器和特定于数据库的驱动程序之间使用。以下是带有不同执行器的稍有修改的图表,其中包括 JTA 事务管理器:

    Application -> "App Server": UserTransaction.begin()
    "App Server" -> "Transaction Manager": TransactionManager.getTransaction()
    create Transaction
    "Transaction Manager" -> Transaction: new
    "App Server" <- Transaction: javax.transaction.Transaction
    
    Application -> "App Server": DataSource.getConnection()
    "App Server" -[#0000FF]> "Database Driver": javax.sql.XADataSource.getXAConnection()
    create XAConnection
    "Database Driver" -> XAConnection: new
    XAConnection -> "App Server": javax.sql.XAConnection
    
    "App Server" -> XAConnection: javax.sql.XAConnection.getXAResource()
    XAConnection -> "App Server": javax.transaction.xa.XAResource
    "App Server" -> Transaction: Transaction.enlistResource(XAResource)
    
    "App Server" -> Application: XAConnection.getConnection()

如上图中所示,您与 App Server 交互,这是可在其中配置 javax.sql.DataSourcejavax.transaction.UserTransaction 实例的一般实体。这些实例可以通过 JNDI 进行访问,也可以使用 CDI 或其他依赖项机制注入。

重要

重要的一点是,即使应用使用 XA 事务和/或连接池,应用也会与 javax.sql.DataSource 进行交互,而不是其他 JDBC 数据源接口。

6.2.1. 特定于数据库及通用数据源

JDBC 数据源实施分为两个类别:

  • 通用 javax.sql.DataSource 实现,例如:

  • javax.sql.DataSourcejavax.sql.XADataSourcejavax.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.ConnectionPoolDataSourcejavax.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.ConnectionPoolDataSourcejavax.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 属性 配置基本的 数据源,如 maxTotalminIdle,用于配置 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.ConnectionPoolDataSourcejavax.sql.XADataSource Factory,由一个非数据库特定的 pax-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.ConnectionPoolDataSourcejavax.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 项目为几个数据库产品和配置方法提供了功能。

  1. 使用 /META-INF/services/java.sql.Driver 安装 JDBC 驱动程序:

    karaf@root()> install -s mvn:mysql/mysql-connector-java/5.1.34
    Bundle ID: 223
  2. 安装 OSGi JDBC 服务捆绑包和 pax-jdbc-mysql 捆绑包,该捆绑包注册 intermediary 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)
  3. 安装 pax-jdbc 捆绑包和 pax-jdbc-config 捆绑包,该捆绑包跟踪 org.osgi.service.jdbc.DataSourceonnectionFactoryy 服务和 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
  4. 创建 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
  5. 检查 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.0.fuse-7_11_0-00036-redhat-00001/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 > 是来自数据源配置 工厂 PIDdecryptor 属性的值。

6.7. 使用 JDBC 连接池

本节提供使用 JDBC 连接池的介绍,然后显示如何使用这些连接池模块:

重要

本章介绍数据源管理内部的信息。虽然提供了 DBCP2 连接池功能的信息,但请记住,这个连接池提供正确的 JTA enlist 功能,但不提供 XA 恢复

为确保 XA 恢复 就位,使用 pax-jdbc-pool-transxpax-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.DataSourcecom.mysql.jdbc.jdbc2.optional.MysqlDataSource
  • javax.sql.ConnectionPoolDataSourcecom.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource
  • javax.sql.XADataSourcecom.mysql.jdbc.jdbc2.optional.MysqlXADataSource
  • java.sql.Drivercom.mysql.jdbc.Driver

PostgreSQL 驱动程序本身实施 OSGi JDBC 服务并生成:

  • javax.sql.DataSourceorg.postgresql.jdbc2.optional.PoolingDataSource (如果指定了池相关的属性)或 org.postgresql.jdbc2.optional.SimpleDataSource
  • javax.sql.ConnectionPoolDataSourceorg.postgresql.jdbc2.optional.ConnectionPool
  • javax.sql.XADataSourceorg.postgresql.xa.PGXADataSource
  • java.sql.Driverorg.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 服务可以防止应用程序为 javax.sql.DataSource Source Source 对象配置池,因此无法为 javax.sql.DataSource 对象返回池。

下表显示了哪些捆绑包注册了池化数据源因素。在表格中,o.o.p.j.p 的实例代表 org.ops4j.pax.jdbc.pool

捆绑包(Bundle)PooledDataSourceFactory池密钥

pax-jdbc-pool-narayana

o.o.p.j.p.narayana.impl.Dbcp(XA)PooledDataSourceFactory

narayana

pax-jdbc-pool-dbcp2

o.o.p.j.p.dbcp2.impl.Dbcp(XA)PooledDataSourceFactory

dbcp2

pax-jdbc-pool-transx

o.o.p.j.p.transx.impl.Transx(Xa)PooledDataSourceFactory

transx

以上捆绑包只安装数据源因子,而不是数据源本身。应用程序需要调用 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 的通用配置属性。

参数默认描述

username

 

要传递给 JDBC 驱动程序的连接用户名,以建立连接。

password

 

要传递给 JDBC 驱动程序的连接密码,以建立连接。

url

 

要传递给 JDBC 驱动程序的连接 URL 来建立连接。

driverClassName

 

要使用的 JDBC 驱动程序的完全限定 Java 类名称。

initialSize

0

池启动时创建的初始连接数。

maxTotal

8

可同时从这个池中分配的最大活跃连接数,或对没有限制的负数。

maxIdle

8

池中可以保持闲置的最大连接数,没有释放的额外连接数,或对没有限制的负数。

minIdle

0

池中可以保持闲置的最少连接数,如果没有创建额外数量,或零可用于创建 none。

maxWaitMillis

无限期

在引发异常前,池要等待的最大毫秒数(当没有可用的连接时),然后再放弃异常或 -1 以无限期地等待连接。

validationQuery

 

在将连接返回给调用者之前,将用于验证来自这个池的连接的 SQL 查询。如果指定,此查询 MUST 是返回至少一个行的 SQL SELECT 语句。如果没有指定,则连接将通过调用 isValid()方法进行验证。

validationQueryTimeout

没有超时

连接验证查询失败前的超时时间(以秒为单位)。如果设置为正值,则该值将通过执行验证查询的声明的 setQueryTimeout 方法传递到驱动程序。

testOnCreate

false

创建后验证对象是否会被验证。如果对象无法验证,则触发对象的创建尝试将失败。

testOnBorrow

true

在从池中分离前验证对象是否会被验证。如果对象无法验证,它将从池中丢弃,我们将尝试再浏览。

testOnReturn

false

表示在返回到池之前验证对象是否会被验证。

testWhileIdle

false

表明对象是否由空闲对象驱除器(若有)验证。如果对象无法验证,它将从池中丢弃。

timeBetweenEvictionRunsMillis

-1

空闲对象驱除器线程之间休眠的毫秒数。当非正向时,将不会运行闲置对象驱除或线程。

numTestsPerEvictionRun

3

每次运行闲置对象驱除器线程时要检查的对象数量(若有)。

minEvictableIdleTimeMillis

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 安装开始。

  1. 安装 JDBC 驱动程序:

    karaf@root()> install -s mvn:mysql/mysql-connector-java/5.1.34
    Bundle ID: 223
  2. 安装 jdbcpax-jdbc-mysqlpax-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)
  3. 创建 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
  4. 检查 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)
  5. 使用数据源:

    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&currentSchema=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="(&amp;(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&currentSchema=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&currentSchema=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&currentSchema=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.DataSourcepax-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.DataSourcejavax.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 JDBC org.osgi.service.jdbc.DataSourceFactory相同
  • 专有 org.ops4j.pax.jms.service.PooledConnectionFactoryonnectionFactory y 与 proprietary pax-jdbc org.ops4j.pax.jdbc.pool.common.PooledDataSourceonnectionFactoryy 相同

对于专用于代理的专用、特定于代理的、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.ConnectionFactoryjavax.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.ConnectionFactoryjavax.jms.XAConnectionFactory 服务,将它们嵌套在 JMS 连接工厂内。

详情在以下主题中:

7.2.1. 为 AMQ 7.1 创建连接工厂

下面是一本例行的、规范、分步指南,用于创建一个对 runc 代理的连接因子。

  1. 使用 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
  2. 创建 工厂配置

    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
  3. 检查 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 属性。

  4. 测试连接。

现在,您有特定于代理(没有池)连接工厂,您可以在需要时注入。例如,您可以使用 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 驱动程序。

  1. 进入 https://developer.ibm.com/messaging/mq-downloads/
  2. 登录。
  3. 点击要安装的版本,例如,点 IBM MQ 8.0 ClientIBM MQ 9.0 Client
  4. 在底部页面的下载版本表中,点击您想要的版本。
  5. 在下一页中,选择后缀 IBM-MQ-Install-Java-All 的最新版本。例如,下载 8.0.0.10-WS-MQ-Install-Java-All9.0.0.4-IBM-MQ-Install-Java-All
  6. 提取下载的 JAR 文件的内容。
  7. 执行 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`.
  8. 创建连接工厂,如下所示:

    1. 安装 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
    2. 创建 工厂配置

      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
    3. 检查 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.0.fuse-7_11_0-00036-redhat-00001/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)
    4. 测试连接:

      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. 流程

  1. 进入 快速入门/camel/camel-jms/src/main/resources/OSGI-INF/blueprint/ 目录。
  2. 将以下 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 中启用(或者您可以在此处自定义这些设置以匹配代理配置)。

  3. 保存 camel-context.xml 文件。
  4. 构建 camel-jms Quickstart:

    $ cd quickstarts/camel/camel-jms
    $ mvn install
  5. 成功安装 Quickstart 后,导航到 $FUSE_HOME/ 目录,再运行以下命令在 Apache Karaf 服务器上启动 Fuse:

    $ ./bin/fuse
  6. 在 Apache 手册的 Fuse 上安装 activemq-client 功能及 camel-jms 功能:

    karaf@root()> feature:install activemq-client
    karaf@root()> feature:install camel-jms
  7. 安装 camel-jms Quickstart 捆绑包:

    karaf@root()> install -s mvn:org.jboss.fuse.quickstarts/camel-jms/{$fuseversion}

    其中,将 {$fuseversion} 替换为您刚才构建的 Maven 工件的实际版本(使用 camel-jms quickstart README 文件)。

  8. 启动 JBoss A-MQ 6.3 代理(需要安装 JBoss A-MQ 6.3)。打开另一个终端窗口,进入 JBOSS_AMQ_63_INSTALLDIR 目录:

    $ cd JBOSS_AMQ_63_INSTALLDIR
    $ ./bin/amq
  9. Camel 路由启动后,您可以在 Fuse 安装中看到一个目录 work/jms/input。将您在此 Quickstart 的 src/main/data 目录中找到的 文件复制到新创建的 work/jms/input 目录中。
  10. 稍等片刻,您将在 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
  11. 使用 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.MQConnectionFactorycom.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-transxpax-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-pooledjms

o.o.p.j.p.pooledjms.PooledJms(XA)PooledConnectionFactoryFactory

pooledjms

pax-jms-pool-narayana

o.o.p.j.p.narayana.PooledJms(XA)PooledConnectionFactoryFactory

narayana

pax-jms-pool-transx

o.o.p.j.p.transx.Transx(XA)PooledConnectionFactoryFactory

transx

注意

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

以下是创建和配置连接池的完整示例:

  1. 安装所需功能:

    karaf@root()> feature:install -v pax-jms-pool-pooledjms pax-jms-artemis
    Adding features: pax-jms-pool-pooledjms/[1.0.0,1.0.0]
    ...
  2. 安装 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)
  3. 创建 工厂配置

    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
  4. 检查 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)
  5. 使用连接工厂:

    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 .ops4j.pax.transx.ManagedConnectionFactory 功能来创建 java x.jms.jms.ConnectionFactoryBuilder 池。这是 第 8.3 节 “关于 pax-transx 项目” 中讨论的 JCA(Java™ 连接器架构)解决方案。

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="(&amp;(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.ConnectionFactorypax-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 模拟

在最简单的场景中,只有一个应用程序和数据库,您有:

Application -> "Database (driver)": java.sql.Driver.connect()
"Database (driver)" -> Application: java.sql.Connection

添加公开 javax.sql.DataSource 的应用服务器,您具有以下内容(不需要重新调用如 XA 之类的数据源的不同方面):

Application -> "App Server": javax.sql.DataSource.getConnection()
"App Server" -> "Database (driver)": javax.sql.ConnectionPoolDataSource.getPooledConnection()
"Database (driver)" -> "App Server": javax.sql.PooledConnection
"App Server" -> Application: javax.sql.PooledConnection.getConnection()

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.DataSourcejavax.jms.ConnectionFactory。它是 pax-transx- jdbc 和 pax-transx -jms 捆绑包使用的 连接工厂 类型。
  • javax.resource.spi.ManagedConnection ManagedConnectionFactory.createManagedConnection() 方法供 应用服务器 使用,创建到 EIS/database/broker 的实际物理连接。

ConnectionManager应用服务器 实施,供 资源适配器 使用。它是首次执行 QoS 操作(池、安全、事务管理)的应用服务器,最终将 资源适配器的 managedConnectionFactory 委派给创建 ManagedConnection 实例。流如下:

  1. 应用代码使用从 ManagedConnectionFactory.createConnectionFactory() 返回的对象,由 应用服务器 创建并公开 连接工厂。它可能是通用 CCI 接口,如 javax.sql.DataSource
  2. 这个 连接工厂 不会自行创建 连接,而是委派至 ConnectionManager.allocateConnection() 传递 资源适配器- 特定的 ManagedConnectionFactory
  3. 应用程序服务器 实施的 ConnectionManager 创建支持 对象、管理事务、池等,并且最终从传递 ManagedConnectionFactory 获取 物理(托管)连接
  4. 应用程序代码获取 连接,通常是由 应用服务器 创建的 wrapper/proxy,后者最终委派给 资源适配器 的特定 物理连接

下面是一个图,其中 应用服务器 创建了非CCI 连接工厂 (特定于 EIS)。只需 - 对 EIS 的访问(这里:数据库)是使用 javax.sql.DataSource 接口完成的,驱动程序的任务是提供 物理连接,而 应用服务器 会将其嵌套在(通常)执行池/enlist/安全性的代理中。

participant Application
participant "App Server"

create "Connection Manager"
"App Server" -> "Connection Manager": new

"App Server" -> "Resource Adapter": ManagedConnectionFactory.createConnectionFactory(connection manager)

create "Connection Factory"
"Resource Adapter" -> "Connection Factory": new

Application -> "Connection Factory": javax.sql.DataSource.getConnection()
"Connection Factory" -> "Connection Manager": ConnectionManager.allocateConnection()
"Connection Manager" -> "App Server": configure pooling/tx/security
"Connection Manager" -> "Resource Adapter": ManagedConnectionFactory.createManagedConnection()
"Connection Manager" <- "Resource Adapter": javax.resource.spi.ManagedConnection
"Connection Manager" -> "Connection Factory": pooled/enlisted/secured connection
Application <- "Connection Factory": pooled/enlisted/secured connection

8.3. 关于 pax-transx 项目

pax-transx 项目为 OSGi 中的 JTA/JTS 事务管理提供支持,以及 JDBC 和 JMS 资源池。它关闭 pax-jdbcpax-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 from pax-transx-jdbc
  • pax-jms-pool-transx 使用 org.ops4j.pax.transx.jms.ManagedConnectionFactoryBuilder from pax-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 相关的概念,如 SpringTransactionPolicySpring XML DSLBlueprint XML DSL 之间存在明确区分,它们是定义 Camel 上下文的 XML 语言。Spring XML DSL 现在在 Fuse 中弃用。但是,Camel 事务机制仍然在内部使用 Spring 库。

这里的大部分信息都不依赖于所使用的平台 过渡 者类型。如果 PlatformTransactionManager 是 Narayana 事务管理器,则会使用完整的 JTA 事务。如果 platformTransactionManager 定义为本地 Blueprint < bean&gt;,例如,org.springframework.jms.connection.JmsTransactionManager,则会使用本地事务。

事务分离指的是启动、提交和回滚事务的步骤。本节介绍可用于控制事务分离的机制,它们均按编程以及配置进行。

9.1. 通过标记路由来分离事务

Apache Camel 提供了在路由中启动事务的简单机制。在 Java DSL 中插入 transacted() 命令,或者在 XML DSL 中插入 & lt;trans acted/> 标签。

图 9.1. Marking the Route 来分离

txn demarcation 01

翻译的处理器分离事务,如下所示:

  1. 当交换进入转换处理器时,转换的处理器调用默认事务管理器以启动事务并将事务附加到当前线程。
  2. 当交换达到其余路由结束时,转换处理器会调用事务管理器以提交当前的事务。

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 节 “事务传播策略” )。以下规则用于选择默认事务管理器或事务策略:

  1. 如果只有 org.apache.camel.spi.TransactedPolicy 类型有一个 bean。

    注意

    TransactedPolicy 类型是 SpringTransactionPolicy 类型的基础类型,如 第 9.4 节 “事务传播策略” 所述。因此,这里提到的信息可能是 SpringTransactionPolicy bean。

  2. 如果存在 type,则 org.apache.camel.spi.TransactedPolicy 为,其 IDPROPAGATION_REQUIRED,请使用此 bean。
  3. 如果只有一个 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() 命令就不使用,因为它在轮询交换后发起事务。换句话说,交易开始过晚,将消费者端点包含在事务范围内。在这种情况下,正确的方法是使端点本身负责发起事务。能够管理事务的端点称为 事务端点

Camel 路由 tx 端点

事务端点划分有两种不同的模型,如下所示:

  • 通常,事务端点会按以下方式分离事务:

    1. 当交换到达端点时,或者当端点成功轮询交换时,端点会调用其关联的事务管理器以开始事务。
    2. 端点将新事务附加到当前线程。
    3. 当交换达到路由结束时,事务端点会调用事务管理器提交当前的事务。
  • 带有 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:girojmstx:queue:creditsjmstx: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 参数指定使用 PROPAGATION_REQUIRES_NEW 行为的事务策略的 bean 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 错误和例外。

Camel 例外
注意

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() 子句不会重新定义异常,因此文件端点不会进行任何重试,避免无限循环。