轻便的数据传输方式一直是面向服务构架(SOA)所关注的焦点,对于SOA来说,曾经发展出了很多方便的数据传输模式。但自从XML流行后,这些传输模式都逐渐消失了,因此,现在的SOA中的基本的数据传输模式都是基于XML的。虽然XML可以用于象服务发送请求、从服务器获得响应或传递数据表之类的操作,但这些操作都太简单,而有时我们需要对XML数据进行复杂的操作,如果要象SQL查询数据表一样来查询XML数据。这就要用到本文所讨论的XQuery技术。
本文主要讨论XQuery以及相关的其他技术,如XQJ(基于XQuery的Java API)。本文首先介绍了什么是XQuery和XQJ,并使读者可以了解使用这些技术的基本方法。然后通过具体的实例来使读者真正掌握XQuery和XQJ技术。
一、什么是XQuery
XQuery是一种查询XML数据的技术。就象用SQL来查询关系数据一样。在几年前,W3C查询语言工作组开发出了XQuery1.0。在以前我们想操作XML数据必须要掌握SAX或DOM中的一种,而在本文中我们将有幸了解到更容易使用的XQJ技术。XQJ更符合XQuery规范,在XQJ语言中,我们将定义很多接口和类,而提交XQJ查询和获得查询结果的任务就是由这些类完成的。同时,XQJ还支持最新的XPath2.0技术。由于XQuery是为查询XML的专用语言,因此,使用一行XQuery语句所达到的效果需要象Java或C#这样的语言上百行才能达到。
XPath是一种用来获得XML文档的部分内容的技术。因此,如果我们只想得到一个XML文件中的一部分内容,就可以使用XPath来完成。但XPath也有局限性,如它不能获得一个节点(Node)的一部分,也不能创建新的内容。这一点XSLT就强一些,XSLT是XPath的超集,除了包含XPath的全部功能外,还包含了很多其他的特性。如可以在XSLT中使用变量、命名空间,并可以创建新文档。
二、XQuery API for Java (XQJ)简介
XQJ其实就是用Java实现的一个包,其中包含了很多的接口和类。使用XQJ可以查询单独的XML文档或XML文档集合。而XQuery提供了更灵活的机制:可以根据需要选择实现这些特性的方法。这就意味着可以使用XQJ来区分处理中间结果和最终结果。现在实现XQJ主要有两个框架,它们是Qexo和Saxon。其中Saxon可以同时在Java和.NET中使用,分别对应了两个包:Saxon-B和Saxon-SA。其中Saxon-B是以开源许可证形式发布的,而Saxon-SA则是收费的。
XQJ可以从JNDI中获得数据,也可以使用其他的方法获得数据。如XQDataSource可以作为创建XQuery连接对象、序列对象和项目对象的工厂。XQDataSource方法有三个重载的getConnection方法,方法的定义如下:
public XQConnection getConnection() throws
XQException
public XQConnection getConnection (String username, String passwd)
throws
XQException
public XQConnection getConnection(Connection con) throws XQException |
在最后一个重载形式中可以使用已经存在的JDBC连接(这个连接的数据源是XML)来创建XQConnection对象。除非这个XQJ实现不支持这个重载形式,否则XQConnection和JDBC将使用同一个事务上下文。一但获得一个XQConnection对象,我们就可以调用prepareExpression方法来提交一个查询了。XQPreparedExpression类有一个executeQuery方法,通过这个方法,可以返回一个XQSequence对象。这个XQSequence对象包含了对于操作返回结果记录的游标。在移动到相应的位置后,可以使用getItem方法得到当前位置的数据。getItem方法返回了一个XQItem对象,通过XQItem类的相应方法,可以将返回数据转换为相应的Java数据类型。
Saxon是XQJ的主要实现,在使用Saxon时,必须直接创建SaxonXQDataSource对象。而不是用工厂类来创建对象。因此,应用程序不能在动态创建XQDataSource时在编译时引用Saxon
XQJ实现。如下面是创建SaxonXQDataSoruce的代码:
String content = null;
XQDataSource ds = new SaxonXQDataSource();
XQConnection conn = ds.getConnection();
XQPreparedExpression exp =
conn.prepareExpression("doc(\"books.xml\")/BOOKLIST/BOOKS/ITEM/TITLE");
XQResultSequence result = exp.executeQuery();
while (result.next())
...{
content = result.getItemAsString();
} |
三、XQJ的使用实例
在本文中我们不准备更深入地讨论XQuery技术。也不会用尝试解决复杂的技术问题。而只是以一个简单的例子来介绍如何使用XQJ来查询数据。现在先让我们来看一看一些表达式,并理解我们将从中得到什么:
/booklist/books/item/title:得到图书列表中所有书的标题。
/booklist/books/item/title[2]:得到图书列表的第二本书。
/booklist/books/item[title="计算机原理"]/author: 得到书名为"计算机原理"的作者
/booklist/books/item/@cat:得到图书列表中所有的种类
/booklist/books/item[2]/*: 获得图书列表中第二项的所有的元素节点
/booklist/books/item/*/@*:获得图书列表中的任何元素中的任何节点
1. Saxon SQL 扩展
我们有很多方法可以进行XML-关系映射。但在这一节中我们介绍一下如何使用Saxon SQL扩展来进行XML-关系映射。使用Saxon
SQL 扩展可以增强CPU访问SQL数据库的能力。第一步是在xsl:stylesheet元素的扩展元素前缀属性中定义一个命名空间前缀(如sql),然后将这个前缀映射到net.sf.saxon.sql.SQLElementFactory。下面是7种操作数据库的stylesheet类型:
sql:connect
sql:query
sql:insert
sql:update
sql:delete
sql:column
sql:close
sql:connect元素将一个数据库连接作为一个值返回。这个值的类型是java.sql.Connection。下面是一段实例代码:
<xsl:param name="driver" select="'oracle.jdbc.driver.OracleDriver'"/>
<xsl:param name="database" select="'jdbc:oracle:thin:@127.0.0.1:1521:orcl'"/>
<xsl:param name="user">user1</xsl:param>
<xsl:param name="password">mypw</xsl:param>
<xsl:variable name="connection" as="java:java.sql.Connection"
xmlns:java="http://saxon.sf.net/java-type">
<sql:connect driver="{$driver}" database="{$database}"
user="{$user}" password="{$password}"
xsl:extension-element-prefixes="sql"/>
</xsl:variable> |
一但通过上述的方法获得连接,就可以对数据库进行GRUD操作了。
2. 对数据表进行CRUD操作
为了使读者清楚地知道Saxon SQL扩展的使用,本文将使用非常基本的CRUD操作。一但读者掌握这些基本的操作,就完全可以利用本文所讲的XSLT来执行复杂的SQL操作。我们的数据模型是非常简单的,如图1所示:
(1) 插入记录
下面让我们先来看看如何来向表中插入记录。我们将使用customer_insert.xml来描述插入操作。代码如下:
import net.sf.saxon.Transform;
public class CustomerOrder...{
public void insert()...{
Transform transformData = new Transform();
String[] args = ...{"customer_insert.xml", "customer_insertupdate.xsl"};
transformData.doTransform(args, null);
}
} |
下面是customer_insertupdate..xls的代码。在这里,我们首先核对一下客户记录在数据库中是否存在,如果不存在,则插入记录。
<xsl:variable name="customerid"
select="CUSTOMERID"/>
<xsl:variable name="customer-table"> <sql:query
connection="$connection" table="customer"
where="CUSTOMERID='{$customerid}'" column="*"
row-tag="CUSTOMERORDER" column-tag="col"/>
</xsl:variable>
<xsl:if test="count($customer-table//CUSTOMERORDER)
= 0"> <sql:insert table="customer"
connection="$connection"> <sql:column name="CUSTOMERID"
select="CUSTOMERID"/> <sql:column name="CUSTOMERLASTNAME"
select="CUSTOMERLASTNAME"/> <sql:column
name="CUSTOMERFIRSTNAME" select="CUSTOMERFIRSTNAME"/>
<sql:column name="CUSTOMEREMAIL" select="CUSTOMEREMAIL"/>
</sql:insert> </xsl:if> |
(2) 读取记录
在插入记录后,在这一部分我们将使用sql:query来读取记录。在这里我们使用customer_query.xml向customer_query.xsl传递参数。
<?xml version="1.0"?>
<CUSTOMERORDERS> <CUSTOMERORDER> <CUSTORDER>
<ORDERID>123</ORDERID> </CUSTORDER>
</CUSTOMERORDER>
</CUSTOMERORDERS> |
上面的xml表示将得到所有ID是123的记录。显然如果想采用这些技术,就必须在程序中动态产生XML文档。customer_query.xsl的内容如下:
<xsl:template match="CUSTOMERORDERS">
<xsl:message>customer_query.xsl :
Connecting to <xsl:value-of select="$database"/>...</xsl:message>
<xsl:message>customer_query.xsl : query records....</xsl:message>
<xsl:apply-templates select="CUSTOMERORDER" mode="Query"/>
<sql:close connection="$connection"/>
</xsl:template>
<xsl:template match="CUSTOMERORDER" mode="Query"> <xsl:variable name="orderid"
select="CUSTORDER/ORDERID"/> <xsl:variable
name="orderitem-table"> <sql:query connection="$connection"
table="ORDERITEM" where="ORDERID=
'{$orderid}'" column="*" row-tag="ORDERITEM"
column-tag="col"/> </xsl:variable>
<xsl:message>There are now <xsl:value-of
select="count($orderitem-table//ORDERITEM)"/> orderitems.</xsl:message>
<ORDER> <xsl:copy-of select="$orderitem-table"/>
</ORDER> <sql:close connection="$connection"/>
</xsl:template> |
(1) 修改记录
在本节我们将使用customer_update.xml来更新CUSTOMEREMAIL字段。首先我们会检查客户记录是否存在,如果存在,将使用sql:update来更新记录。代码如下:
<xsl:if test="count($customer-table//CUSTOMERORDER)
> 0"> <sql:update table="customer"
connection="$connection" where="CUSTOMERID='{$customerid}'">
<sql:column name="CUSTOMERLASTNAME" select="CUSTOMERLASTNAME"/>
<sql:column name="CUSTOMERFIRSTNAME" select="CUSTOMERFIRSTNAME"/>
<sql:column name="CUSTOMEREMAIL" select="CUSTOMEREMAIL"/>
</sql:update>
</xsl:if> |
(2) 删除记录
删除是CRUD最后一项。在这一节我们将使用sql:delete删除表中的记录。对于本文例子,我们将传递一个DELETE节点为空的customer_delete.xml文件,这个文件的内容如下:
<?xml version="1.0"?>
<CUSTOMERORDERS> <DELETE></DELETE>
</CUSTOMERORDERS> |
customer_delete.xsl中包含了删除address、orderitem、custorder和customer的sql:delete命令,文件的内容如下:
<xsl:template match="CUSTOMERORDERS">
<xsl:apply-templates select="DELETE" />
</xsl:template>
<xsl:template match="DELETE">
<sql:delete table="address" connection="$connection"
/> <sql:delete table="orderitem" connection="$connection"
/> <sql:delete table="custorder" connection="$connection"
/> <sql:delete table="customer" connection="$connection"
/>
</xsl:template> |
四、总结
从上面的案例中我们学习到了如何使用XQJ对数据库进行CRUD操作。使用XQuery的目的就是为了尽可能地少写Java代码。当然,在这个例子中有很多XSLT代码。但这些代码是为了保证程序的灵活性而存在的。如果我们要改变程序的功能,只需要改变这些XSLT代码就可以了。除了本文所讨论的构架,还有很多其他的开源或商业的XML产品。通过这些产品,可以在XML和关系数据之间架起一座桥梁。在以上的案例中实际上是直接基于XQuery的,而如果完全使用XQJ,我们将会获得更为强大的功能。一些新一代的构架还允许对XML数据库进行ACID操作。这些事务甚至可以作为一个子事务存在。如果使用这些强大的构架,开发人员将会如虎添翼。所写的代码将会大大减少,如果是这样,将会大大减少程序的bug。从而使程序更加健壮。
|