跨多个层进行 XML 编程,第 2 部分: 编写采用 XML 数据库服务器的高效 Java EE 应用程序

利用 JDBC 4.0、SQLXML、WebSphere XML Feature Pack 和 DB2 pureXML 优化纯 XML 解决方案

本系列第 1 部分介绍了一种声明性编程方法,用于跨应用程序服务器层和数据库服务器层处理瞬时和永久 XML 数据。本文将更加深入地探究在服务器端 Java™ 应用程序中处理瞬时和永久 XML 数据。通过使用实际的示例和样例代码,您会看到,数据库管理系统中的 XML 索引和查询过滤功能将如何为处理大量 XML 数据的 Java EE 应用程序带来重大的性能优势。还将复习如何联接瞬时和永久 XML 数据。

Andrew Spyker, 高级软件工程师, IBM

Photo of Andrew SpykerAndrew Spyker 是 WebSphere 开发组织内的 WebSphere Application Server Performance and Benchmarking 组织的团队主管。目前他主要研究编程模型(包括 J2EE 和 SDO)、Web 服务(包括 WS-Security 和其他不断发展的标准),以及这些领域如何适应面向服务的体系结构。除了主要研究 SOA 之外,他还致力于了解与所有 WebSphere 产品有关的重要性能,包括(但不限于)WebSphere Business Integration Server 和 WebSphere Portal Server。他的性能研究工作包括启用性能更高且可扩展的应用程序服务器功能,帮助客户设计他们当前的和未来的应用程序以获得最好的性能。Andrew 有两年半的性能研究经验和七年多的 Java 编程经验。他获得了宾夕法尼亚州立大学 (Pennsylvania State University) 计算机工程学士学位。



Cynthia M. Saracco, 高级解决方案架构师

照片:Cynthia SaraccoCynthia M. Saracco 是 IBM 硅谷实验室的高级解决方案架构师,擅长新兴技术和数据库管理主题。她有 23 年软件行业从业经验,曾经撰写了三本书和 60 多篇技术文章,拥有七项专利。


developerWorks 专家作者

Bert Van Der Linden, DB2 XML 架构师, IBM

Bert Van Der Linden 的照片Bert Van Der Linden 于 2001 年加入 IBM,担任 DB2 中的 pureXML 架构设计工作,这项工作以 2006 年 DB2 9 的发布圆满结束。与此同时,他与许多客户和合作伙伴合作,致力于在数据库中推广 XML 的使用。加入 IBM 之前,Bert 在一个创业公司 Propel 任职,主持设计和实现了一个分布式的容错中间件(其中宿主着一个可伸缩的电子商务应用程序)。此前,Bert 在 Tandem Computers 公司从事了多年的 NonStop SQL 工作,NonStop SQL 是一个数据库,用于支持许多关键的金融行业应用程序的运行。



Guogen Zhang, 杰出工程师, IBM

Guogen Zhang 的照片Guogen Zhang 是 IBM 硅谷实验室的一名 DB2 for z/OS 开发杰出工程师。他是首席架构师和开发主管,负责用 DB2 for z/OS 交付 pureXML,他还是高级 SQL 技术的领头人,用 DB2 for z/OS 交付了很多重要功能,包括 pureXML、 Materialized Query Tables (MQT)、XML 发布功能、星型联接和 union in view,等等。他经常在很多会议(比如 IOD 和 IDUG)中讲话,旨在为客户提供 pureXML 解决方案。



2010 年 11 月 08 日

概述

XML 的广泛应用促使程序员去寻找处理瞬时 XML 消息和永久 XML 数据的有效方式。Web 服务和其他软件组件经常会产生包含重要业务数据的瞬时 XML 消息。日益地,应用程序越来越需要处理这些消息以及存储在原生 XML 数据库管理系统中的永久业务工件。

常用缩写词

  • API:应用程序编程接口
  • BLOB:二进制大对象
  • CLOB:字符大对象
  • DBMS:数据库管理系统
  • DOM:文档对象模型
  • JDBC:Java 数据库连接
  • OEM:原始设备制造商
  • SAX:XML 的简单 API
  • SOA:面向服务架构
  • SQL:结构化查询语言
  • URI:统一资源标识符
  • URL:统一资源定位符
  • W3C:万维网联盟
  • XHTML:可扩展超文本标记语言
  • XML:可扩展标记语言
  • XSLT:可扩展样式表语言转换

尽管完全可以将瞬时和永久 XML 数据都映射到 Java 对象,但是这样做的缺点是会导致高度嵌套的复杂 XML 结构。另外,如果您的 XML 数据的模式可能随着时间而变化,那么这会向您的应用程序代码引入额外的复杂性。本系列的第一篇文章介绍了一种声明性编程方法,用于跨应用程序服务器层和数据库服务器层处理瞬时和永久 XML 数据,以降低通常与命令式编程方法相关联的复杂性。

本文更加深入地探究在服务器端 Java 应用程序中处理瞬时和永久 XML 数据。通过回顾常见的应用程序开发任务和样例代码,您将:

  1. 明白如何从需要高效集成瞬时和永久 XML 数据的 Java EE 应用程序采用原生 XML 数据库服务器技术。
  2. 学习如何利用数据库管理系统中的 XML 索引和查询过滤功能来确保强健的运行时性能。
  3. 复习如何联接瞬时和永久 XML 数据,以及如何更新永久 XML 文档的特定部分。

前提条件和产品

在阅读本文之前,您应该熟悉 XML、XPath 和 XQuery。参见 参考资料,了解关于这些技术的信息。此外,由于本文中的示例基于 WebSphere Application Server V7.0 Feature Pack for XML 1.0,所以您还应该阅读本系列的第 1 部分。对 XML 数据库管理技术的了解也是有帮助的。 在本文的示例中,我们使用 DB2® 作为 XML 数据库服务器。

我们的应用程序环境和样例数据库

为了探究服务器端 Java 程序员可以如何高效地处理瞬时和永久 XML 数据,我们在单个 Windows® 系统上安装了以下软件:

  • Rational Application Developer for WebSphere Software V7.5.5 及 WebSphere Application Server 7.0 测试环境
  • WebSphere Application Server Feature Pack for XML 1.0 及 Fix Pack 1.0.0.7。(这既可以与单独版本的 WebSphere Application Server 一起使用,也可以与内置在 Rational Application Developer 中的 WebSphere 测试环境一起使用。)
  • DB2 9.7 Enterprise Server Edition

我们的 Rational Application Developer 开发项目的构建路径为 DB2 中的 JDBC 4.0 驱动程序包含了适当的 .jar 文件,即 db2jcc4.jardb2_license_cu.jar。要在您的项目中使用 XML Feature Pack,请确保您通过右键单击项目、选择 Project Facets 并启用 facet 而为您的项目启用了 XML Transformation and Query Project Facet。另外,您也可以向构建路径添加 XML Feature Pack 瘦客户端 jar(com.ibm.xml.thinclient_1.0.0.jar)。有关 DB2 中 JDBC 4.0 驱动程序或 XML Feature Pack 的详细信息,请参见 参考资料

我们的示例应用程序的数据库中包含,基于 International Swaps and Derivatives Association (ISDA) 提供的样例 XML 数据的衍生品柜台交易(OTC derivatives trade)记录。此数据遵循 Financial Products Markup Language (FpML) 规范,此规范对于很多进行柜台交易的公司很流行。

DB2 提供大量免费的、行业特定的软件下载,帮助公司快速创建和填充一个遵循某种行业标准 XML 格式的测试数据库。有这样一个针对 FpML 的软件包可以下载。我们定制了这个软件包,以帮助我们演示本文中的某些观点;您可以使用 下载 中的链接下载这个版本。如果您想要在 Windows 系统上设置样例数据库,那么打开 DB2 命令窗口并发出 start.bat。这将创建必要的数据库对象并加载样例 FpML 数据。

我们的示例使用 FPMLADMIN.FPML43 表,其中包含两个关系列(ID 和 COMMENT)和一个 XML 列(DOCUMENT)。XML 列存储所有的 FpML 记录或各种类型的柜台交易。公司在单个表中既存储关系数据也存储 XML 数据是很常见的情况,我们的示例引用了这两种类型的列。图 1 展示了这个表的结构。

图 1. FPMLADMIN.FPML43 表带有两个关系列(ID 和 COMMENT)和一个 XML 列(DOCUMENT)
样例 FPMLADMIN.FPML43 表带有两个项(ID 和简要注释)和 FpML 文档

FpML 记录包含高度嵌套的结构,以及根据交易类型而变化的元素和属性。图 2 展示了信用违约掉期交易的一个样例 FpML 记录的一小部分。

图 2. 信用违约掉期交易的不完整 FpML 记录
信用违约掉期交易的不完整 FpML 记录的屏幕截图

我们的样例表包含大小从 2KB 到超过 125KB 的 FpML 文档。此外,表中同一 XML 列中存储基于 FpML 模式三种不同版本的交易文档(具体来说,存储基于 FpML 4.2、4.3 和 4.7 的交易文档)。XML Schemas 趋向于随着时间而演进,以适应不断变化的业务需求,所以在单个 XML 列中存储遵循不同模式的文档是一个常见的业务需求。


查询永久 XML 数据

Java EE 程序员经常需要检索存储在数据库层的永久 XML 数据,并在应用程序服务器层操纵这些数据。为了进行演示,我们将使用一个示例,它利用 XML Feature Pack API 和内置的 DB2 支持来查询 XML 数据。两者都支持跟查询 XML 数据有关的行业标准,包括 XQuery 和 XPath 表达式。

由于 DB2 认可并支持 XML 数据为一流的数据类型,所以程序员可以直接处理原生格式的 XML 数据,而不是当作大对象 (LOB) 来处理。跨多个软件层使用 XML 可以简化编程逻辑,降低开发成本。此外,DB2 中的原生 XML 支持提供对文档特定部分(XML 节点)的高效访问;大对象不可以。

查询场景

考虑这样一个场景,即一个 Java EE 应用程序需要访问一项特定交易的信用违约掉期数据。如果交易数据被作为瞬时 XML 消息传递到应用程序中,那么应用程序可以简单地使用 XML Feature Pack 提供的服务来针对该消息执行一个 XPath 表达式。清单 1 定义了这样一个 XPath 表达式。

清单 1. 使用 XML Feature Pack API 来处理瞬时 XML
// This XPath expression obtains the credit default swap data for a given trade.  
// For simplicity, we omitted declaring a specific namespace here.
  
String myXpath = "*:FpML/*:trade/*:creditDefaultSwap;
. . .

并不是所有应用程序都依赖于瞬时 XML 消息。对于一个设计来处理适当投资组合以分析投资策略、管理风险或者执行一些其他业务功能的应用程序来说,则可能需要从永久 XML 仓库获得所需的交易数据。

返回一段永久 XML 交易数据的样例查询

清单 2 演示了如何使用 XPath 表达式从数据库(本例中是 DB2)获得一家公司的信用违约掉期数据。

清单 2. 带有关系和 XML 谓词的 SQL/XML 查询
SELECT XMLQUERY('declare default element namespace 
   "http://www.fpml.org/2009/FpML-4-7";
   $fpml/FpML/trade/creditDefaultSwap' passing document as "fpml")
FROM fpmladmin.fpml43 
WHERE comment LIKE ‘cd%’ 
AND 
XMLEXISTS('declare default element namespace "http://www.fpml.org/2009/FpML-4-7";
   $fpml/FpML/trade/creditDefaultSwap/generalTerms/referenceInformation
   /referenceEntity[entityName="Agrium Inc."]' passing document as "fpml")

该查询可以通过 DB2 命令行界面或图形查询工具以交互方式执行,是一个标准的 SQL 语句,调用了两个 XML 特定的函数。第一个函数 XMLQUERY() 识别我们想要返回的数据(creditDefaultSwap 节点中的数据)。第二个函数 XMLEXISTS() 将结果限制为引用特定实体(Agrium Inc.)的 FpML 4.7 文档。

跟大多数 SQL 语句一样,本例遵循 SELECT column(s) . . . FROM table(s) . . . WHERE condition(s) 形式的标准 SQL 语法。我们下面仔细地来看看这个查询,然后再回顾一个 Java 编码例子,例子展示了我们如何在一个也使用 XML Feature Pack 的应用程序中调用该查询。

查询的第一行发出一个 SQL SELECT 语句,其中调用了一个行业标准的 SQL 函数 (XMLQUERY)。调用 XMLQUERY() 时,我们首先声明所关注的目标名称空间 (http://www.fpml.org/2009/FpML-4-7)。正如我们前面所讨论过的,我们的样例 DB2 表在单个列中包含 FpML 记录的不同版本,但是我们只关心特定的 FpML 4.7 记录。

在为 XMLQuery 函数声明默认的名称空间之后,我们提供一个 XPath 表达式 ($fpml/FpML/trade/creditDefaultSwap),用于指定我们想要检索的数据。XPath 表达式后面的 passing 子句指定 $fpml 变量代表表中的 DOCUMENT 列。因此,清单 2 的前三行表明,我们只想检索存储在 DOCUMENT 列中的 FpML 4.7 交易数据的一个子集。具体来说,我们只想要信用违约掉期数据,FpML 将这些数据表示为一个包含多个子节点的 XML 节点(参见 图 2)。SQL FROM 子句将 FPMLADMIN.FPML43 识别为所关注的表。

SQL WHERE 子句表明,我们只想要与信用违约交易相关的结果。在我们的样例表中,COMMENT 列(一个关系列)中的值可以用于分隔记录在 DOCUMENT 列(一个 XML 列)中的交易类型。以 cd 打头的 COMMENT 值代表信用违约交易。

剩余的行调用行业标准 XMLEXISTS() 函数,它将查询结果限制为涉及一个名为 Agrium Inc 的特定公司实体的掉期。再好好看看 XMLEXISTS 函数,您会发现它声明了一个适当的名称空间并包含一个 XPath 表达式(数据库服务器将针对表中 DOCUMENT 列包含的数据执行该表达式)。

为了达到最佳运行时性能,Java EE 程序员将查询编写为尽可能是选择性的。我们的样例查询只检索我们需要的部分 XML 数据(具体来说,XMLQUERY 函数只获得交易文档的一个特定 XML 节点)。此外,我们的样例查询通过在 WHERE 子句中指定关系谓词和 XML 谓词,限定了所关注的行。以这种方式编写我们的查询可以最小化数据库和应用程序服务器层之间传输的数据总量。(我们的例子检索存储在 DB2 中的一个 XML 交易记录的子集,而一个更加一般的、涉及所有信用违约掉期交易的查询,将会返回所有 40 个交易记录。)最后,在 DB2 中定义适当的 XML 索引可以提高运行时查询性能,下面马上会进行讨论。

针对我们的查询场景的样例 Java EE 代码

在 Java EE 应用程序中包含这个数据库查询并不困难。下面我们详细地来看一个例子,它从数据库检索所需的 XML 数据并在应用程序服务器中操纵这些数据。

清单 3 展示了一个 servlet 的摘录,此 servlet 定义将由适当 XML Feature Pack 集合解析器执行的数据库查询。

清单 3. Java EE 应用程序,它向读入内存的 DB2 pureXML® 数据应用一个 XQuery 程序
// Excerpt from our sample servlet. 
//  
// First, the servlet sets up the database query. 
// Here, a String named "getCreditDefaultSwapsByEntityName" will be mapped to our query, 
// which extracts the creditDefaultSwap node from qualifying trades.

dbStatements = new HashMap<String, String>();
dbStatements.put("getCreditDefaultSwapsByEntityName",
 "select " +
   "xmlquery("'declare default element namespace " + 
     "\"http://www.fpml.org/2009/FpML-4-7\"; " + 
     "$DOCUMENT/FpML/trade/creditDefaultSwap' ) " +
  "from fpmladmin.fpml43 " + 
  "where comment like ? and " +
  "xmlexists("'declare default element namespace " + 
    "\"http://www.fpml.org/2009/FpML-4-7\"; " +
    "$fpml/FpML/trade/creditDefaultSwap/generalTerms/" + 
    "referenceInformation/referenceEntity[entityName=$name]' " +
    "passing document as \"fpml\", " +
    "cast (? as varchar(100)) as \"name\")"
);
...

// Next, another method in this servlet creates the XQuery executable.     
// This method then uses our JDBC resolver to execute it, 
// providing an appropriate value for the entityName variable. 

Source source = 
   new Source(FpMLServlet.class.getResource("/getCreditDefaultSwaps.xq").toString());
XQueryExecutable getCreditDefaultSwapsXQ = factory.prepareXQuery(source, staticContext);
...
JDBCCollectionResolver inputResolver =
  new JDBCCollectionResolver(getConnection(), dbStatements);
dynamicContext.setCollectionResolver(inputResolver);
dynamicContext.bind(new QName("http://com.ibm.xml.samples",entityName"), name);
XSequenceCursor output = getCreditDefaultSwapsXQ.execute(dynamicContext);

清单 3 第一部分中的查询几乎跟 清单 2 中的交互式版本一模一样。不同之处有:

  • 参数标记(由问号字符 (?) 表示)取代硬编码的谓词值,以带来更大的灵活性。
  • 双引号被适当转义。

正确定义好一个已命名的查询之后,应用程序就可以执行该查询并向返回的结果应用业务逻辑。为简单起见,本例中展示的业务逻辑只获得与符合条件的交易相关的票面息率和其他公司债券信息。生产应用程序应该包含更复杂的业务逻辑。

清单 3 中代码的第二部分包含用于从数据库服务器获得适当 XML 数据并在应用程序服务器中对数据进行处理的逻辑。此编程模式采用与本系列第 1 部分作为例子介绍的相同方法(参见 参考资料)。首先,应用程序创建一个 XQuery 可执行对象,XML Feature Pack 将针对 DB2 返回的部分 XML 交易记录执行此对象。接下来,应用程序指定一个适当的集合解析器。解析器将连接到 DB2 数据库并执行查询,在此过程中,会为查询的参数标记传入一个适当的数据值(即交易中所引用实体的名称)。

我们的解析器实现跟本系列第 1 部分清单 8中描述的解析器相同,所以这里不再详细复习它。该解析器处理以一个 jdbc:// URI 方案打头的集合。它使用已命名的查询来解析 URI 的其余部分并执行适当的逻辑。此解析器跟本文中讨论的其他样例代码一样,可以下载得到。参见 下载 部分的链接。

注意,这里使用的解析器设计基于与 XML Feature Pack 一起发布的一个例子,并且代表惟一的访问原生 XML 数据库(比如 DB2 pureXML)的方式。如果想要在生产应用程序中使用此代码,您应该根据需要修改或扩展此样例。此外,本系列第 1 部分简要讨论了其他可能的设计解析器的方法;例如,请参见那篇文章中的清单 14(参见 参考资料)。

一旦从数据库检索到了适当的信用违约掉期数据,在需要时应用程序就可以进一步操纵此数据。清单 4 展示了一个在应用程序服务器上执行的 XQuery 函数,它提取从数据库返回的每个交易中引用的公司债券的 instrument ID、票面息率和到期日。

清单 4. 摘自 getCreditDefaultSwaps.xq,这是一个用于从交易获得债券信息的 XML Feature Pack XQuery 程序
declare variable $my:entityName as xs:string external;

declare variable $databaseURI := 
   concat('jdbc://getCreditDefaultSwapsByEntityName?cd%&', $my:entityName); 
declare variable $creditDefaultSwaps := collection($databaseURI);

for $bond in $creditDefaultSwaps//fpml:bond
     return
<tr>
     <td>{ $bond/fpml:instrumentId }</td>
     <td>{ $bond/fpml:couponRate }</td>
     <td>{ $bond/fpml:maturity }</td>
</tr>

创建索引以改善运行时性能

既然理解了我们从数据库检索适当 XML 数据并在中间层对数据进行处理时采用的逻辑,现在就来讨论另一个主题:性能。为了确保我们的查询可以在数据库层高效地执行,我们在 FPMADMIN.FPML43 表上创建了两个索引:

  • FPMLADMIN.COMMENTX,它索引关系 COMMENT 列。
  • FPMLADMIN.ENTITYNAME,它索引 XML DOCUMENT 列中的一个特定节点。

正如您马上就会看到的,DB2 可以使用这两个索引来为我们的查询产生一个高效的访问路径。清单 5 展示了如何创建这些索引。第一个语句定义关系索引,第二个语句定义 XML 索引。

清单 5. 创建关系索引和 XML 索引
create index fpmladmin.commentx on fpmladmin.fpml43(comment)

create index fpmladmin.entityname on fpmadmin.fpml43(document) 
generate key using xmlpattern  
  'declare default element namespace "http://www.fpml.org/2009/FpML-4-7"; 
  /FpML/trade/creditDefaultSwap/generalTerms/referenceInformation
       /referenceEntity/entityName' 
as sql varchar(1000)

我们创建了这些索引,因为样例数据库中的 FPMLADMIN.FPML43 表有 4MB,包含近 900 行。只有少量的数据与将我们的目标公司 (Agrium Inc.) 引用为命名实体的信用违约掉期相关。DB2 可以使用关系索引和 XML 索引来快速检索所关注的数据,避免了盲目地扫描表中的所有行。

图 3 演示了 DB2 为我们的查询选择的访问计划(针对我们使用 DB2 中的内置 RUNSTATS 工具收集的适当统计数据)。从下往上读,您会注意到,DB2 使用了这两个索引来快速地检索我们的样例应用程序所请求的数据。

图 3. DB2 使用关系索引和 XML 索引进行高效的数据访问
DB2 使用 FPMLADMIN.COMMENTX 和 FPMLADMIN.ENTITYNAME 索引的屏幕截图

关于如何查看和解释 DB2 数据访问计划的更多信息,请参见 参考资料


查询临时和永久 XML 数据

XML Feature Pack 也允许您编写 XQuery 表达式,用于将临时 XML 数据 — 也许由 web 服务或 Java 应用程序产生— 与永久 XML 数据联接起来。正如您可能想到的,有各种方式可以做到这一点。这里我们将探索其中的一种方法。

联接场景

考虑一个这样的应用程序,它需要分析金融衍生品中的投资。该应用程序的一个方面可能需要获得关于信用违约掉期交易中引用的公司的当前市场数据。此类市场数据的一个简单例子可能包含关于当前股价的信息。假设我们将衍生品交易记录存储在 DB2 中,并可以从 web 服务获得股票信息,那么我们的应用程序就需要联接永久和临时 XML 数据。

用于我们的联接场景的样例 Java EE 代码

清单 6 演示了一种使用 XML Feature Pack 执行必要工作的方式。我们使用与 清单 3 中相同的查询来从 DB2 获得信用违约掉期,所以这里不再重复它。我们也使用了一个适当的解析器来执行查询,就跟我们在前一场景中所做的一样。

清单 6 中的新内容就是定义了一个不同的 XQuery 可执行程序 — 具体来说,是一个将在 XML Feature Pack 中处理联接的 XQuery 函数。另外,清单 6 创建一个 StreamSource 对象来给出将与存储在 DB2 中的适当交易数据联接的临时 XML 数据。为简单起见,我们的 StreamSource 用名为 assets.xml 的文件中数据来填充,我们使用该文件来给出对投资组合分析有用的临时市场数据。(但是在生产应用程序中,此 XML 数据更可能来自 web 服务或消息队列。)

清单 6. 联接临时和永久 XML 数据的 Java EE 应用程序
// Create the XQuery executable.   
Source source = 
    new Source(FpMLServlet.class.getResource("/joinCreditDefaultSwap.xq").toString());
XQueryExecutable joinCreditDefaultSwapsXQ = factory.prepareXQuery(source, staticContext);
...

// Declare the resolver and execute the join. 
// The resolver will issue the DB2 query, and WebSphere software will join its output 
// with XML data in the StreamSource object. 
JDBCCollectionResolver inputResolver = 
    new JDBCCollectionResolver(getConnection(), dbStatements);
dynamicContext.setCollectionResolver(inputResolver);
StreamSource source = 
    new StreamSource(FpMLServlet.class.getResourceAsStream("/assets.xml"));
dynamicContext.bind(new QName("http://com.ibm.xml.samples", "entityName"), name);
XSequenceCursor output = joinCreditDefaultSwapsXQ.execute(source, dynamicContext);

清单 7 展示了 assets.xml 文件的内容。

清单 7. assets.xml 的内容,它给出我们示例中的临时 XML 数据
<?xml version="1.0" encoding="UTF-8"?>
<assets>
     <equity>
          <symbol>AGU</symbol>
          <name>Agrium Inc.</name>
          <currency>USD</currency>
          <high>64.06</high>
          <low>62.79</low>
     </equity>
     <equity>
          <symbol>STM-FP</symbol>
          <name>STMicroelectronics N.V.</name>
          <currency>EUR</currency>
          <high>6.92</high>
          <low>7.2</low>
     </equity>
</assets>

清单 8 包含联接临时和永久 XML 数据的 XQuery 函数。

清单 8. 摘自 joinCreditDefaultSwaps.xq,这是执行联接的 XML Feature Pack XQuery 程序
declare variable $my:entityName as xs:string external;

declare variable $databaseURI := 
    concat('jdbc://getCreditDefaultSwapsByEntityName?cd%&', $my:entityName); 
declare variable $creditDefaultSwaps := collection($databaseURI);

declare function local:equityRows($root) {
     for $equity in $root//equity
     let $referenceEntity := $creditDefaultSwaps//fpml:referenceEntity
     where $equity/name = $referenceEntity/fpml:entityName
     return
          <tr xmlns="http://www.w3.org/1999/xhtml">
               <td>{ $equity/*:symbol/text() }</td>
               <td>{ $equity/*:name/text() }</td>
               <td>{ $equity/*:high/text() }</td>
               <td>{ $equity/*:currency/text() }</td>
          </tr>
};

<table border="1">
<tr>
     <th>Ticker Symbol</th>
     <th>Company Name</th>
     <th>High</th>
     <th>Currency</th>
</tr>
{ local:equityRows(/) }
</table>

该函数的 FOR 子句循环通过包含在临时 XML 数据中的实体节点。LET 子句从执行 清单 6 中定义的已命名查询之后 DB2 返回的信用违约掉期数据集合中摘取所引用的实体信息。WHERE 子句基于资产名称联接临时和永久 XML 数据。函数以 XHTML 格式返回关于从 DB2 返回的信用违约掉期中引用的所有公司的股票代号、公司名称、最高售价和币种的信息。

联接临时和永久 XML 数据时,一定要考虑联接应该发生在哪里。如果一个软件层包含大量数据需要与另一软件层上的少量数据联接,那么通常最高效的做法是在具有大量数据的层执行联接。


更新永久 XML 数据

基于 XML 的 Java EE 应用程序的另一个常见需求涉及到更新永久 XML 数据。本系列的第 1 部分包含一个代码样例,展示了如何用内存中的另一个文档替换永久 XML 文档。完整的文更新在某些情况下肯定是有用的。但是,很多应用程序只需要更新 XML 文档的一部分。下面我们来探究如何做到这一点。

DB2 支持 XQuery Update Facility,这是 XQuery 的一个标准化的扩展,允许程序员以各种方式更新特定的 XML 节点。例如,程序员可以添加新节点、删除节点、更新元素或属性的值,以及进行其他类型的更新。子文档更新通常有助于改善运行时性能。程序员只需简单地指定他们想要对 XML 文档的适当部分做出的更改。DB2 直接在服务器上执行这些更新,最小化了应用程序编程逻辑和数据传输。而对于依赖于 CLOB 或 BLOB 管理 XML 的 DBMS 来说,则要求应用程序从数据库检索 XML 文档,解析这些文档,根据需要进行更新,然后再将这些文档写回数据库中。如果只是一个大 XML 文档的一小部分需要更改,那么此方法带来的性能损害是比较可观的。

更新场景

考虑这样一个场景,即一个应用程序需要更改一个 XML 节点的值。例如,信用违约掉期交易中的两方可能会重新协商跟交易预定终止日期相关的条款(被表示为 FpML 交易记录中的一个节点)。您需要更新存储在数据库中的 FpML 交易记录,以反映这些新的条款,这些新条款会被作为临时 XML 消息传递给 Java EE 应用程序。

出于顺应性考虑,参与衍生品交易的公司经常会给出修改过的交易记录作为其数据库中的新记录。对于演示目的,我们将展示如何直接修改初始的 FpML 记录。

永久 XML 数据的样例子文档更新

清单 9 包含一个交互式 SQL 语句,用于更新 FPMLADMIN.FPML43 表的 DOCUMENT 列中的 XML 数据。

清单 9. 更新存储在 DB2 中的一个文档的 XML 节点
update FPMLADMIN.FPML43 
set document = 
  xmlquery ('declare default element namespace "http://www.fpml.org/2009/FpML-4-7";
    transform copy $new := $x 
    modify do replace 
      $new/FpML/trade/creditDefaultSwap/generalTerms/scheduledTerminationDate 
    with 
      <scheduledTerminationDate xmlns="http://www.fpml.org/2009/FpML-4-7">
         <adjustableDate>
            <unadjustedDate>2011-05-05</unadjustedDate>
            <dateAdjustments>
               <businessDayConvention>FOLLOWING</businessDayConvention>
            </dateAdjustments>
         </adjustableDate>
         <comment>This is new.</comment>
      </scheduledTerminationDate>
    return $new' passing document as "x")
where comment like 'cd-ex10-long-us-corp-fixreg-47%'and
  xmlexists('declare default element namespace "http://www.fpml.org/2009/FpML-4-7";
    $fpml/FpML/trade/creditDefaultSwap/generalTerms
    /referenceInformation/referenceEntity[entityName="Agrium Inc."]' 
    passing document as "fpml")

WHERE 子句将更新限制到一个涉及 Agrium Inc. 的特定信用违约掉期交易。由于该子句的逻辑跟前一场景中给出的逻辑非常类似,我们这里就不再复习它了。

该查询的有趣部分在 XMLQuery() 函数调用中包含的表达式中。在声明了一个适当的默认名称空间之后,该表达式将初始 XML 文档值(参见 图 2)复制到 $new 变量中。MODIFY 子句用一个新节点替换预定终止日期的节点。这个新节点以 4 种方式高效地更改初始节点:

  1. 未调整日期(一个子节点)的值被更改为一个新日期(2011 年 5 月 5 日)。
  2. 营业日惯例(一个子节点)的值被修改为 "FOLLOWING"。
  3. 业务中心节点被删除。(因此,业务中心节点的子节点也被删除。)
  4. 为注释添加了一个新的子节点。

最后,RETURN 子句返回代表预定终止日期的新节点,所以 DB2 将用修改后的数据更新 DOCUMENT 列中的数据。

更新场景的样例 Java EE 代码

下面我们来看这个更新操作在 Java EE 应用程序中是如何实现的。正如您可能想到的,在将该查询合并到应用程序中去时,我们需要使用参数标记和转义字符。清单 10 的第一部分演示了,我们如何将交互式 UPDATE 语句(在 清单 9 中)转换成 Java EE 应用程序中的已命名查询。跟我们的前一场景一样,本例也依赖于解析器来执行数据库操作。

清单 10. 使用 DB2 pureXML 替换 XML 元素
// Define the database query. 
// In this case, the named query will update part of an FpML trade record. 
dbStatements = new HashMap<String, String>();
dbStatements.put(
  "updateScheduledTerminationDateByEnityName",
  "update fpmladmin.fpml43 set document = " + 
  "xmlquery('" +
    "declare default element namespace " + 
       "\"http://www.fpml.org/2009/FpML-4-7\"; " +
     "transform copy $new := $x " +
     "modify do replace " +
       "$new/FpML/trade/creditDefaultSwap/generalTerms/scheduledTerminationDate with $d "+
     "return $new' " +
     "passing cast (? as xml) " +
     "as \"d\", " +
     "document as \"x\"" +
  ") " +			
  "where comment like ? and " +
    "xmlexists(" +
        "'declare default element namespace " + 
            "\"http://www.fpml.org/2009/FpML-4-7\"; " +
        "$fpml/FpML/trade/creditDefaultSwap/generalTerms" +
            "/referenceInformation/referenceEntity[entityName=$name]'" +
        "passing document as \"fpml\", cast (? as varchar(100)) as \"name\"" +
     ")"
);
...

// Create an XSLT executable and execute it with the JDBC resolver 
Source source = 
  new Source(FpMLServlet.class.getResource("/updateCreditDefaultSwap.xsl").toString());
XSLTExecutable updateCreditDefaultSwapXSL = 
  factory.prepareXSLT(source, staticContext);
...
JDBCResultsResolver resultsResolver = 
  new JDBCResultsResolver(getConnection(), dbStatements);
dynamicContext.setResultResolver(resultsResolver);
dynamicContext.bind(new QName("http://com.ibm.xml.samples","entityName"),"Agrium Inc.");
dynamicContext.bind(new QName("http://com.ibm.xml.samples","tradeType"),"cd-ex10-long%");
dynamicContext.bind(new QName("http://com.ibm.xml.samples","updateOrRestore"),"update");
...

// "newDate" is XML and comes from a non-DB2 XML data source
XItemView newDate = getUpdatedTerminationDate();
dynamicContext.bind(new QName("http://com.ibm.xml.samples",
   "updatedScheduledTermination"), newDate);
...
StreamResult result = new StreamResult(servletResponse.getOutputStream());
updateCreditDefaultSwapXSL.execute((Source)null, dynamicContext, result);

本例中我们不是使用 XQuery 程序(跟前一场景中不一样),而是使用 XSLT 2.0 样式表。清单 11 演示了我们的 XSLT 代码的一部分。

清单 11. 用于更新永久 XML 文档一部分的 XSLT
<xsl:param name="my:entityName" as="xs:string" />
<xsl:param name="my:tradeType" required="yes" as="xs:string" />
<xsl:param name="my:updateOrRestore" required="yes" as="xs:string" />
<xsl:param name="my:updatedScheduledTermination" as="node()" />

<xsl:variable name="updateCreditDefaultSwapURL"
    select="concat('jdbc://updateScheduledTerminationDateByEnityName?--XML--&', 
       $my:tradeType, '&', $my:entityName)" />

<xsl:when test="$my:updateOrRestore eq 'update'">
    <xsl:result-document href="{$updateCreditDefaultSwapURL}" method="xml" indent="yes">
    <xsl:copy-of select="$my:updatedScheduledTermination" />
  </xsl:result-document>
</xsl:when>

在新的 XML 文档中组合 XML 和关系数据

想要从数据库层获得关系和 XML 数据的 XML 表示的 Java EE 程序员可以使用 DB2 的子文档更新支持来帮助他们达到这一目标。例如,程序员可以编写单个语句,用从存储在 DB2、Oracle 或一些其他 DBMS 中的表的关系列摘取的信息来丰富 XML 数据。该功能在多种情况下有用,因为它使得程序员可以跨多个系统组合 XML 和关系数据。即使该场景没有包含可供本文下载的样例代码,但是执行此类查询的方法与我们在前面例子中讨论的方法是相同的。

为了理解如何以这种方式使用 DB2 的子文档更新支持,可以假设我们的 FpML 数据库包含一个传统的关系表,用于跟踪衍生品交易中经常涉及的公司的联系信息。参见 清单 12,了解该表的定义。

清单 12. 创建 FPMLADMIN.PARTYCONTACTINFO 表
CREATE TABLE FPMLADMIN.PARTYCONTACTINFO (
          PARTYID VARCHAR(40) PRIMARY KEY NOT NULL,
          PARTYNAME VARCHAR(100),
          PHONENO BIGINT,
          EMAILID VARCHAR(100),
          ADDRESS1 VARCHAR(100),
          ADDRESS2 VARCHAR(100),
          CITY VARCHAR(100),
          ZIPCODE BIGINT,
          STATE VARCHAR(100),
          COUNTRY VARCHAR(100)
     )

FpML 交易记录通常只包含有限的关于衍生品交易中涉及到的公司的信息,而交易确认(合同)通常需要更详细的信息。如 清单 13 所示,单个 DB2 语句可以轻松地将关系数据转换成 XML 元素,将这些元素插入 XML 文档的适当节点,并返回一个新的 XML 文档以备处理。

清单 13. 用关系数据丰富 FpML 交易记录
select xmlquery ('declare default element namespace "http://www.fpml.org/2009/FpML-4-7"; 
  transform 
  copy $new := $message 
  modify for $i in $new/FpML/party
  return
    do insert 
      db2-fn:sqlquery("select 
         XMLELEMENT(NAME ""ContactInfo"", 
         XMLELEMENT(NAME ""Address1"", p.ADDRESS1), 
         XMLELEMENT(NAME ""Address2"", p.ADDRESS2), 
         XMLELEMENT(NAME ""CITY"", p.CITY), 
         XMLELEMENT(NAME ""STATE"", p.STATE), 
         XMLELEMENT(NAME ""COUNTRY"", p.COUNTRY),
         XMLELEMENT(NAME ""PHONE"", p.PHONENO)
         ) 
         from FPMLADMIN.PARTYCONTACTINFO p 
         where partyId=parameter(1)", $i/partyId/text()) 
         as last into $i
         return <newroot>{$new}</newroot>'
  passing F.DOCUMENT as "message") 
FROM FPMLADMIN.FPML43 f  
where id=47022

如果关系数据驻留在远程 DB2 或 OEM 数据库中,那么我们将简单地为我们数据库中的这个远程表创建一个别名。别名将远程数据库对象呈现给本地 DB2 服务器,并允许程序员把这些远程对象当作本地 DB2 表一样看待。因此,清单 13 中的查询保持不变,即使底层的数据是存储在远程 DB2、Oracle 或其他数据库中。

此外,我们还可以很容易修改该查询,以将这些变丰富了的交易数据插入到我们的 DB2 数据库的一个 XML 列中。我们所需要做的就是在 清单 13SELECT 子句的前面包含一个 INSERT INTO . . . 子句(如果我们想要保留初始的交易记录并将修改过的交易作为一个新文档插入数据库中,那么我们也可以利用 清单 9 中的查询而采用一种类似的方法。)


结束语

在本系列第 2 部分中,我们演示了 Java EE 程序员如何在应用程序服务器层和数据库服务器层原生地处理 XML。通过几个样例应用程序,我们在一个服务器端 Java 应用程序中处理了大量临时和永久 XML 数据,对于 XML 和关系数据都使用了索引和查询过滤功能。我们的示例也展示了如何处理永久 XML 数据的子集 — 具体来说就是如何只从数据库摘取所关注的 XML 节点,以及如何只更新文档中的特定 XML 节点。处理永久 XML 文档片段避免了应用程序服务器层与数据库服务器层之间不必要的数据传输,也帮助减少了否则需要在应用程序服务器上发生的一些处理。最后,我们也演示了程序员如何联接临时和永久 XML 数据 — 一种日益常见的编程需求,因为当今 XML 跨很多软件已经很流行。

致谢

感谢 Lee Ackerman 和 Matthias Nicola 对本文的审阅。也感谢 Susan Malaika 和她的同事帮助开发了可供本文下载的 DB2 脚本。


下载

描述名字大小
样例 FpML 数据和 DB2 脚本XMLWASDB2.zip2MB
使用 XML 特性包的样例 Java 代码FpML-Sample-WASXMLFEP-DB2pureXML.zip5MB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Information Management, WebSphere
ArticleID=576833
ArticleTitle=跨多个层进行 XML 编程,第 2 部分: 编写采用 XML 数据库服务器的高效 Java EE 应用程序
publish-date=11082010