IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Information Management | XML | SOA and Web services | Architecture  >

DB2 XML 编程,第 2 部分: 在应用程序体系结构中使用 XML 数据库支持

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 中级

Hardeep Singh (hardeep@us.ibm.com), 高级技术架构师, IBM 

2008 年 1 月 10 日

了解 IBM® DB2® 9 for Linux®, UNIX®, and Windows® 新的 XML 存储和查询环境如何处理本系列 第 1 部分 中描述的 XML 数据模型。第 2 部分主要关注如何在应用程序体系结构中使用新的 XML 数据库支持。

简介

XML 在数据库中的地位在过去两年中已经发生了变化,从 “临时工” 变成了重要成员。它不再需要改变本身来适应关系环境。它可以保持其层次化性质,同时利用关系数据库环境的功能和稳定性。实际上,一些关系性元素已经采用某些技术让它们看起来像 XML,以便利用层次化 XML 模型丰富的功能。

本文讨论新的 XML 存储和查询环境如何处理本系列 第 1 部分 中的 XML 数据模型。还要说明,在采用新的基于 XML 的应用程序开发体系结构之后,数据库模式会变得更简单更自然。还演示如何按照在应用程序中查询数据的相同方式查询数据库中的 XML 数据。最后,讨论如何结合关系数据和 XML 数据,从而同时获得这两个环境的优势。





回页首


XML 数据库基础

尽管大多数主流关系数据库都有某种 XML 支持,但是 DB2 的 pureXML™ 支持要健壮和高效得多,这使它成为试验 XML 编程模型的理想数据库。本文主要关注如何在应用程序体系结构中使用新的 XML 数据库支持。

DB2 允许存储、查询、操作和发布:

  • 关系数据 — SQL
  • 采用 XML 形式的关系数据 — SQL/XML
  • XML 数据 — XQuery
  • 混合型数据(关系数据和 XML 数据) — SQL/XML 和 XQuery

图 1. DB2 混合型存储
DB2 混合型存储

在数据库中存储 XML

关系数据库中的 XML 支持的主要好处是,可以在同一个表中同时存储关系数据和 XML 数据。另外,尽管 XML 在内部存储为层次化(树)格式,但是它看起来像是存储在数据库表的单一列中(就像 CLOB 或 BLOB)。

从第 1 部分中的数据对象可以看出,有两个表,每个表至少有两列。


清单 1. 表
                			
CREATE TABLE CUSTOMER_TABLE (
CUSTOMERID CHARACTER (12) NOT NULL,
CUSTXML XML NOT NULL ,
CONSTRAINT CC1183665042494 PRIMARY KEY ( CUSTOMERID) )

CREATE TABLE PURCHASE_TABLE (
CUSTOMERID CHARACTER (12) NOT NULL ,
ITEMXML XML NOT NULL ,
CONSTRAINT CC1183665244645 FOREIGN KEY
(CUSTOMERID) REFERENCES CUSTOMER_TABLE (CUSTOMERID)
ON DELETE CASCADE ON UPDATE NO ACTION
ENFORCED ENABLE QUERY OPTIMIZATION )

显然,通过将应用程序的数据对象存储为 XML,关系模式大大简化了。另外,基础结构仍然是关系型的,这使 XML 数据能够利用关系数据库的实用功能,比如触发器、约束和外键关系。

因为从逻辑上看 XML 列与 VARCHAR、CLOB 或 BLOB 列相似,所以 INSERT 语句也是相似的。

insert into CUSTOMER_TABLE values('hardeep',
'<Customer customerid="hardeep" firstname="hardeep" lastname="singh"/>')

在 Java™ 程序中执行插入的代码也是相似的:


清单 2. 在 Java 程序中执行插入
                
String insertsql= "insert into PURCHASE_TABLE values(?,?)";
PreparedStatement iStmt=connection.prepareStatement(insertsql);
File inputfile= new File(filename); //filename is the path of the XML file
long filesize=inputfile.length();
BufferedReader in = new BufferedReader(new FileReader(inputfile));
iStmt.setCharacterStream(1,in,(int)filesize);
int rc= iStmt.executeUpdate();

为了更好地了解混合型存储,我们来看看 XML 数据的逻辑视图,体会 XML 数据如何看起来像是存储在关系数据库表中。

注意:尽管不同关系数据库厂商的 XML 物理存储技术可能不一样,但是逻辑视图是相似的。


图 2. DB2 混合型存储逻辑视图
DB2 混合型存储逻辑视图

查询 XML

在展开数据库模式模型时,可以看到关系表和列。如果展开 XML 列,模式会从关系模型变成 XML 层次化模型。现在,您应该意识到其实有两个模式(一个关系模式和一个 XML 模式),但是把它们当作一个整体;理解了这一点,就能够以更自然的方式在这个统一的模式中进行导航和查询。

对于清单 1 所示的统一模式,如果希望获得 CUSTOMER_TABLE 中 CUSTXML 列的数据,那么可以在查询中指定 CUSTXML 列的路径作为目标。

SELECT CUSTXML FROM CUSTOMER_TABLE where customerid='hardeep';

这会返回 hardeep 的 CUSTXML 列中的客户数据。

现在,考虑如何获得其 lastname 为 singh 的客户数据。在这种情况下,需要指定每个 XML 文档的 lastname 属性的路径(CUSTOMER_TABLE.CUSTXML/Customer/@lastname)并检查它是否是 singh。

在完美的环境中,查询应该是 Select * from CUSTOMER_TABLE where CUSTXML/Customer/@lastname='singh'。但是在真实环境中,需要用数据库查询引擎能够理解的一种语法编写查询。数据库领域已经引入了一种称为 XQuery 的新语言,可以用它查询 XML 文档。SQL 添加了可以理解这种语言的新函数,从而将关系和 XML 环境联系起来了。所以搜索姓氏为 singh 的客户的查询如下:

select CUSTXML from CUSTOMER_TABLE
where xmlexists ('$cust/Customer[@lastname= "singh" ]' passing CUSTXML AS "cust" )

还可以在 Java 程序中通过参数化查询执行这个调用:

select CUSTXML from CUSTOMER_TABLE
where xmlexists ('$cust/Customer[@lastname= $lname ]'
passing CUSTXML AS "cust" , cast(? as VARCHAR(12)) as "lname")

掌握了向 SQL/XML 函数传递参数的语法之后,您会发现,在针对关系数据和 XML 数据的基本混合型查询中,XML 查询通常包含 XPath 表达式。这与在应用程序层中操作 XML 数据模型的方法非常相似(见第 1 部分),在那里许多代码通过对 Document Object Model(DOM)包装器进行 XPath 调用来查询和操作 XML 数据。

注意:在 Viper 2 中,对传递给一些 SQL/XML 函数的参数做了简化。例如,在前面的查询中,XMLExists 的 passing 子句不需要指定 CUSTXML 列。

select CUSTXML from CUSTOMER_TABLE
where xmlexists ('$CUSTXML/Customer[@lastname= $lname ]'
passing cast(? as VARCHAR(12)) as "lname")





回页首


将应用程序逻辑放在数据库中

XQuery 提供了大多数高级语言的所有基本功能(if-then-else、for、变量、函数和算术操作符)。因此,可以将业务逻辑嵌入查询中。另外,它还提供许多常用的 XSLT 映射,所以它不但能够执行查询,还能够在数据库中转换 XML 输出。

我们仍然以第 1 部分中 Customer 示例的 XML 数据模型为例。

<Customer customerid ="" firstname="" lastname="" >
<Items><Item ID="" description="" purchaseDate="" price="" /></Items>
</Customer>

用 DB2 查询替代应用程序代码

我们不必在应用程序层中合并来自两个表的 XML 数据,可以在数据库中用一个 SQL/XML 查询实现相同的效果。这需要一个 CUSTOMER_TABLE.CUSTXML/Customer/@customeridPURCHASE_TABLE.ITEMXML/Customer/@customerid 的一对多联结。


图 3. 联结两个 XML 列
联结两个 XML 列


清单 3. 查询两个 XML 列

values(xmlquery('

for $Customer in db2-fn:xmlcolumn( "CUSTOMER_TABLE.CUSTXML")/Customer

where $Customer/@customerid= $customerid

return
<Customer customerid ="{$Customer/@customerid}"
firstname ="{$Customer/@firstname}" lastname ="{$Customer/@lastname}" >{

for $Customer0 in db2-fn:xmlcolumn("PURCHASE_TABLE.ITEMXML")/Customer
where $Customer0/@customerid= $Customer/@customerid
return $Customer0/Item

}</Customer>

' passing cast( ? AS varchar(255) ) as "customerid" ))

对于客户 hardeep 购买的所有商品,产生的 XML 如下:


图 4. 查询结果
查询结果

在上面的查询中,必须构造外层的 Customer 元素并添加来自 CUSTXML 列数据的属性。DB2 Viper 2(beta)支持 XQuery 更新表达式,可以修改 XML 文档,所以不需要构造外层的 Customer 元素。可以使用来自客户表的一个 XML 文档,并将来自购买表的商品数据作为子元素插入其中。


清单 4. 针对两个 XML 列的 Viper 2 查询
values(xmlquery('
				
for $Customer in db2-fn:xmlcolumn( "CUSTOMER_TABLE.CUSTXML")/Customer

let $items:=(<Items>{

for $Customer0 in db2-fn:xmlcolumn("PURCHASE_TABLE.ITEMXML")/Customer
where $Customer0/@customerid= $Customer/@customerid
return $Customer0/Item

}</Items>)

where $Customer/@customerid= $customerid

return
transform
copy $cust:=$Customer
modify(
do insert $items as last into $cust)
return $cust

' passing hardeep as "customerid" ))

在上面的查询中,不但搜索、获取和合并了数据库中存储的 XML 文档部分,还在产生的 XML 中添加了新元素。另外,hardeep 被隐式转换为 XML 类型(xs:string)。

对比数据库查询和 Java 应用程序代码

如果将上面的查询与第 1 部分中的 Java 代码(清单 6. 重写应用程序来使用 XML 模型)做一下对比,就会发现它们的逻辑非常相似。

  1. 从 CUSTOMER_TABLE 中选择 Customer 信息。
  2. 构造一个 Items 元素并在 PURCHASE_TABLE 中搜索这位客户购买的所有商品。
  3. 循环遍历列表中的每个商品并将它们插入 Items 元素。
  4. 将 Items 元素插入 Customer 元素。

创建存储过程

为了将数据库中的业务逻辑与应用程序代码隔离开,一种好方法是为这个查询创建一个存储过程。


清单 5. 创建存储过程
                
CREATE PROCEDURE customerItems(IN custid varchar(12))
DYNAMIC RESULT SETS 1
LANGUAGE SQL
BEGIN
DECLARE c_cur CURSOR WITH RETURN FOR

values(xmlquery('

for $Customer in db2-fn:xmlcolumn( "CUSTOMER_TABLE.CUSTXML")/Customer

let $items:=(<Items>{

for $Customer0 in db2-fn:xmlcolumn("PURCHASE_TABLE.ITEMXML")/Customer
where $Customer0/@customerid= $Customer/@customerid
return $Customer0/Item

}</Items>)

where $Customer/@customerid= $customerid

return
transform
copy $cust:=$Customer
modify(
do insert $items as last into $cust)
return $cust

' passing custid as "customerid" ))
OPEN c_cur;
END

用存储过程调用替代应用程序代码

应用程序代码现在可以对 DB2 进行存储过程调用并将 XML 传递给 DOM 包装器。第 1 部分中的 XML 数据模型的应用程序代码(清单 6. 重写应用程序来使用 XML 模型,2-8 行)可以简化为:

2. ResultSet dbResult = dbstmt.executeQuery("call customerItems ("+custid+")"
3. XMLParse customerXML = new XMLParse(dbResult. getString(1));





回页首


一个更精细的示例

现在考虑一个比较精细的场景,这个场景还要计算每个商品的保险费。为了让这个场景有点儿难度,保险费不但每天变动,而且随价格变化。这意味着,不但要向查询传递 customerid,还要传递保险费率。现在,假设您每天在保险公司提供的一个 Web 服务中查询最新的保险费率。保险费率信息采用 XML 文档的形式。

<insurance>
<rate price="100" currency="$" rate=".02"/>
<rate price="500" currency="$" rate=".018"/>
<rate price="" currency="$" rate=".015"/>
</insurance>

可以修改前面的存储过程来计算保险费。


清单 6. 计算每个商品的保险费的存储过程
                
CREATE PROCEDURE customerItemsWithInsurance(IN custid varchar(12), rate XML)
DYNAMIC RESULT SETS 1
LANGUAGE SQL
BEGIN
DECLARE c_cur CURSOR WITH RETURN FOR

values(xmlquery('

for $Customer in db2-fn:xmlcolumn( "CUSTOMER_TABLE.CUSTXML")/Customer

let $items:=(
<Items>{
for $Customer0 in db2-fn:xmlcolumn("PURCHASE_TABLE.ITEMXML")/Customer

let $insurance:=<insurance currency="{($rate//rate[@price=""]/@currency)}">
{(
if($Customer0/Item/@price > 500) then (
$Customer0/Item/@price * $rate//rate[@price=""]/@rate
)
else (

if($Customer0/Item/@price > 100) then (
$Customer0/Item/@price * $rate//rate[@price="500"]/@rate
)
else (
$Customer0/Item/@price * $rate//rate[@price="100"]/@rate
)

)
)}</insurance>

where $Customer0/@customerid= $Customer/@customerid

return
transform
copy $item:=$Customer0/Item
modify( do insert $insurance as last into $item)
return $item
}</Items>
)

where $Customer/@customerid= $customerid

return
transform
copy $cust:=$Customer
modify(do insert $items as last into $cust)
return $cust

' passing custid as "customerid", rate as "rate" ));

OPEN c_cur;
END

对这个存储过程的调用接受两个运行时参数,customerid 和保险 XML。

call customerItemsWithInsurance(?,?)

显然,在上面的示例中,如果要操作的数据库数据是 XML 格式的,那么可以使用 XQuery 实现比只使用 SQL 时更多的业务逻辑。另外,查询中使用的 XML 甚至不需要在数据库中存在。因此,SQL/XML 查询中涉及的 XML 数据可以按照层次化形式存储在数据库中,可以由 SQL/XML 函数生成,甚至可以作为运行时参数传递给查询。数据库和应用服务器之间的差异逐渐变得模糊了。





回页首


优点和缺点

所有新技术在刚刚出现时都会有一些问题。一些问题是由于实现还不成熟造成的,其他问题是由于开发人员还不适应变化。

  1. 尽管性能有所改进,但是仍然比不上关系数据。
  2. XQuery 是一种新语言,而且一些 SQL/XML 函数的语法需要花时间适应。
  3. 有许多遗留数据采用关系格式。
  4. 最重要的是,这是一种创建业务应用程序和数据模式的新方式,它与当前的面向对象应用程序和规范化关系模式很不一样。
  5. 能够对这类查询进行调试和优化的工具还不多。

尽管存在这些缺点,但是新的模型管理数据的方式更加自然。在应用程序层和数据库层中都按原样维护和操作业务数据信息,而且您将第 3 部分中看到,甚至在客户机层中也是如此。

  • 尽管外围语言可能不一样(Java、XQuery、JavaScript、PHP),但是在所有层中用来在 XML 文档中移动的语言都是相同的(XPath)。
  • 即使遗留数据是关系型的,但是通过使用 Viper 2 中引入的一些 SQL/XML 函数,可以轻松地查询这些数据并将它们转换为 XML。看一下第 1 部分中的示例 “第二种情况 —— 所有数据在数据库中存储为关系形式”。可以使用 Viper 2 中引入的 XMLROW 函数简化这个查询。

    Select XMLROW (customerid, firstname, lastname OPTION as attributes ROW Customer)
    from customer_table where customerid=?
    

    还可以在关系数据和 XML 数据之间创建联结。在这个示例场景中,如果有第三个表,其中包含购买商品的产品说明,而且这是一个关系表,那么可以使用商品 ID 执行联结,从而获得购买的每种商品的产品说明。



    图 5. 联结关系列和 XML 列
    联结关系列和 XML 列

    Select details, weight from SQLPRODUCT, ITEM_TABLE
    where xmlexists ('$itemxml/item[@itemid=$pid]'
    passing ITEM_TABLE.ITEMXML AS "itemxml", SQLPRODUCT.PID AS "pid" )
    

    在 DB2 9 中,可以使用 passing 子句向 SQL 语句中嵌入的 XQuery 传递运行时参数,但是不能向 XQuery 中嵌入的 SQL 传递参数。在 Viper 2 中,这个限制已经消除了,现在可以向 XQuery 中嵌入的关系查询传递运行时变量。



    清单 7. 向 XQuery 中嵌入的 SQL 传递运行时变量
                            			
    values(xmlquery('
    
    for $Customer0 in db2-fn:xmlcolumn("PURCHASE_TABLE.ITEMXML")/Customer
    where $Customer0/@customerid= $custid
    return (
    $Customer0/Item,
    db2-fn:sqlquery(
    ''select xmlrow(details, description, weight option ROW "description")
    from sqlproduct where pid= parameter(1)'', $Customer0/Item/@ID))
    
    ' passing cast( ? AS varchar(255) ) as "custid" ))
    

    因此,即使一部分数据位于关系表中,一些数据是 XML,现在都可以在 SQL 查询、XQuery 或这两者中在 XML 数据和关系数据之间进行动态联结。

  • 在某些情况下,性能可能不是大问题,因为:
    • 能够为数据库中存储的 XML 文档创建基于 XPath 表达式的索引。

      create index custfname on customer_table(info) generate key
      using xmlpattern '/Customer/@firstname' as sql varchar(64)
      

    • 因为数据库模式更简单,所以减少了所需的联结数量。
    • 因为现在可以在查询中缩减数据,然后再把数据发送给应用程序,所以可以减少 I/O。
    • 随时可以使用 SQL/XML 函数(比如 XMLTable)将 XML 文档中的关键信息提取到关系列中,并为它们创建关系索引。
    • 可以为 XML 文档创建文本搜索索引。




回页首


结束语

XML 已经取得了稳固的地位。大多数行业和政府组织都对他们的 XML 模式做了标准化,并要求电子文档必须符合这些模式。既然通过线路交换的 B2B 数据已经采用了 XML 格式,为什么不在数据库中按原样(pureXML)存储这些数据呢?将数据存储为 XML 之后,可以使用 XQuery 和标准的 SQL/XML 对它进行编制索引、查询、检验、操作、转换和更新。随着越来越多的应用程序逻辑被放在查询中,数据库可以以 Web 服务和 feed 的形式公开它的存储过程,从而成为面向服务体系结构(SOA)环境中活跃的参与者。

“旧的秩序已经改变了,新的秩序正在形成。”(The old order changeth, yielding place to new.) Morte d'Arthur



参考资料

学习

获得产品和技术

讨论


关于作者

Hardeep Singh 是高级技术小组的成员。他是 DB2 XML 工具技术和 XML 迁移的架构师。他拥有超过 23 年的行业经验。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款