了解如何通过 JDBC 代码使用 XQuery 访问非 JDBC 源中的数据。
2011 年 3 月发布
Oracle Database 10g 第 2 版发布时,XQuery 首次出现在 Oracle XML DB 中,从那时起,就可以通过 JDBC 代码使用 XQuery 这一查询和编程语言来操作 XML 数据。然而,使用 Oracle Database 11g 第 2 版中首次推出的 XQuery 和 XML DB 特性(如扩展表达式杂注以及建立非结构化 XML 内容索引)现在可以显著提高执行 XQuery 查询的 JDBC 代码的效率。
本文提供了一些通过 JDBC 使用 XQuery 访问非 JDBC 源中数据的示例。本文还介绍 XQuery API for Java(XQJ;JSR 225),它可以取代 JDBC,提供与 XQuery 引擎交互的标准 Java API。
为了执行本文中的示例,您需要访问 Oracle 11g 第 2 版数据库。此版本的 Oracle 数据库附带了与 JDK 1.5 兼容的 JDBC 驱动程序。此外,将在示例中使用的 JDBC 瘦驱动程序以及 JDBC OCI 驱动程序均支持 JDK 1.6(尽管在此不需要该版本)。因此,您需要做的是将以下 jar 文件添加到 CLASSPATH 环境变量中:
ORACLE_HOME/jdbc/lib/ojdbc5.jar
ORACLE_HOME/jlib/orai18n.jar
下一步是检查您的系统中是否有 Java 编译器和 Java 解释器,以确保可以编译和运行您将使用的代码。需要注意的是,Oracle Database 附带了 Java 编译器和 Java 解释器。因此,确保已将 ORACLE_HOME/jdk/bin 添加到路径环境变量中。然后,可以从操作系统提示符处发出带有版本选项的 Javac 和 Java 命令,以确保使用正确的软件。
注意,在 Oracle Database 11g 第 1 版及更高版本中,可以使用访问控制列表 (ACL) 特性配置对外部网络资源的细粒度访问控制。默认情况下,Oracle 数据库拒绝对外部网络资源的访问。由于本文使用的示例说明如何使用 XQuery 查询通过 URL 访问网络资源,因此必须创建一个 ACL,从而允许数据库用户连接到要使用的这些资源。例如,如果要使用连接到位于 localhost 上的资源的 HR 演示模式,可以使用 DBMS_NETWORK_ACL_ADMIN 提供的 PL/SQL 程序包的 CREATE_ACL 和 ASSIGN_ACL 过程执行以下 PL/SQL 块:
CONN /AS SYSDBA BEGIN DBMS_NETWORK_ACL_ADMIN.CREATE_ACL( acl => 'localhost.xml', description => 'localhost ACL', principal => 'HR', is_grant => true, privilege => 'connect'); DBMS_NETWORK_ACL_ADMIN.ASSIGN_ACL( acl => 'localhost.xml', host => 'localhost'); END; / COMMIT;
一旦上述代码执行完,就会将 localhost 的连接权限授予 HR — 这意味着您现在可以使用该模式通过 UTL_TCP、UTL_HTTP、UTL_SMTP 以及 UTL_MAIL PL/SQL 程序包访问 localhost 资源。需要强调的是,XQuery 通过 HTTP 协议查询网络资源时,将在后台使用 UTL_HTTP。
示意图如下所示:
当然,求助于 XQuery 的原因多种多样,需要使用 URL 查询 Web 文档之类的非 JDBC 源只是其中一种情况。要了解 Oracle XQuery 所展现的所有激动人心的可能性,您可以参考 Oracle XML DB 开发人员指南 中的“结合使用 XQuery 和 Oracle XML DB”一章。大体概括一下:您可以使用 XQuery 高效地查询、构造和转换可用 XML 表示的任何数据,包括关系数据。例如,您可以对一个关系源执行 XQuery 语句以将关系数据转换为 XML。
但是,本文中的示例将说明如何在查询 XML 和 HTML 之类的 Web 文档(决不代表是可通过 JDBC 访问的数据源)时利用新的 Oracle XQuery 特性。
此时,您可能想知道:如果此类文档不是可通过 JDBC 访问的文档,那么如何通过 JDBC 代码访问它们?为了消除此处可能出现的误解,必须了解,尽管您使用 XQuery 查询的文档可能不是可通过 JDBC 访问的文档,但所使用的 XQuery 引擎位于 Oracle 数据库中,该数据库实际上代表了可通过 JDBC 访问的源。
换言之,JDBC 代码对它所查询的源一无所知,只是将 XQuery 语句传递给 XQuery 引擎,此引擎对该语句进行求值并访问该语句中指定的源。而 XQuery 引擎对 JDBC 代码来说是已知的,因为它是 Oracle 数据库的一个集成部分,而 Oracle 数据库实际上是可通过 JDBC 访问的。因此,从这种角度说,可将数据库视作 JDBC 代码和该代码通过 XQuery 访问的源之间的中介。
如果采用示意图表示,该架构如下所示:
无论您通过 JDBC 使用 Oracle XQuery 查询何种数据源,都必须先获得一个到所用 Oracle 数据库的 JDBC 连接,以处理发出的 XQuery 语句。
通过示例可以更好地理解这一点。假设您要查询以下可通过 http://localhost/bonuses.xml 访问的 XML 文档:
<?xml version="1.0" ?> <employees> <employee> <empno>100</empno> <ename>Steven King</ename> <email>SKING</email> <bonus>3000</bonus> </employee> <employee> <empno>171</empno> <ename>William Smith</ename> <email>WSMITH</email> <bonus>2000</bonus> </employee> </employees>
然后,您可以使用以下 JDBC 代码访问上述文档:
import java.sql.*; import oracle.jdbc.*; import oracle.jdbc.pool.OracleDataSource; class JDBCXQuery { public static void main (String args[]) throws SQLException { OracleDataSource ods = new OracleDataSource(); ods.setURL("jdbc:oracle:thin:hr/hr@localhost:1521/orcl11g"); Connection conn = ods.getConnection(); Statement stmt = conn.createStatement(); String qry = "SELECT * FROM XMLTable("+ "'for $i in $h/employees/employee " + "return $i '" + "PASSING xmlparse(document " + "httpuritype('http://localhost/bonuses.xml').getCLOB()) as \"h\" " + "COLUMNS ename VARCHAR2(45) PATH '/employee/ename', " + " bonus NUMBER(10,2) PATH '/employee/bonus' )"; ResultSet rs = stmt.executeQuery(qry); System.out.println("Bonuses:"); while(rs.next()) System.out.println(rs.getString(1) + ": $" + rs.getFloat(2)); } }
上述代码将生成以下输出:
Bonuses: Steven King: $3000.0 William Smith: $2000.0
前面的示例说明了如何使用 XMLTable SQL 函数将 XQuery 查询的结果分解为常规 SQL 行,这样您就可以使用 ResultSet 实例的 next() 方法向下浮动这些行。但是,Oracle XQuery 的灵活性远不止如此。
例如,获得 XMLTable 生成的虚拟表之后,您可以在联接查询中使用该表,将该表的行链接到从常规关系表中获得的行。示例如下:
import java.sql.*; import oracle.jdbc.*; import oracle.jdbc.pool.OracleDataSource; class MultitargetXQuery { public static void main (String args[]) throws SQLException { OracleDataSource ods = new OracleDataSource(); ods.setURL("jdbc:oracle:thin:hr/hr@localhost:1521/orcl11g"); Connection conn = ods.getConnection(); Statement stmt = conn.createStatement(); String qry = "SELECT d.department_name, b.ename, b.bonus FROM XMLTable("+ "'for $i in $h/employees/employee " + "return $i '" + "PASSING xmlparse(document " + "httpuritype('http://localhost/bonuses.xml').getCLOB()) as \"h\" " + "COLUMNS empno NUMBER(6) PATH '/employee/empno', " + " ename VARCHAR2(45) PATH '/employee/ename', " + " bonus NUMBER(10,2) PATH '/employee/bonus' ) b," + "employees e," + "departments d " + "WHERE (e.employee_id = b.empno) AND (d.department_id=e.department_id)"; ResultSet rs = stmt.executeQuery(qry); System.out.println("Bonuses:"); while(rs.next()) System.out.println("Department: " + rs.getString(1) +": " + rs.getString(2) + ": $" + rs.getFloat(3)); } }
正如您在前面示例中所见,XMLTable 生成的虚拟表与 HR 演示模式中的 employees 和 departments 关系表一起参与联接。联接条件以粗体突出显示。但是,检查该联接查询的选择列表时,您可能注意到它不包括 employees 表的任何列。确实如此:在这个特定示例中,该表充当 XMLTable 生成的行和 departments 的行之间的中介。该程序的输出如下所示:
Bonuses: Department: Executive: Steven King: $3000.0 Department: Sales: William Smith: $2000.0
到目前为止,我们所讨论的示例应有助于您了解如何通过 JDBC 使用 XQuery。但是并未介绍新的 Oracle XQuery 特性,这些特性在 Oracle Database 11g 第 2 版中首次推出,可帮助您提高 JDBC 代码的效率。
其中一个特性就是扩展表达式杂注 (pragma),该特性还能够处理对基于 XML 模式的数据操作无效的 XPath 表达式,好像该表达式的目标节点不存在,不会引发无效 XPath 异常,因此没有必要在 JDBC 代码中捕获该异常。本节中讨论的示例将说明如何利用这一新特性。
通过上一个示例,您了解了如何在一条 SQL 语句中混合作为 XQuery 求值结果生成的行与从关系表中选择的行。在下面的示例中,您将了解如何混合两个不同的 XQuery 查询生成的行,其中每个查询由一个单独的 XMLTable 求值。第一个 XQuery 查询的目标是前面示例中使用的 bonus.xml 文档,第二个查询的目标是 OE 演示模式中基于 XML 模式的 XMLType 表 purchaseorder。因此,您首先需要将 localhost 的连接权限授予 OE:
BEGIN DBMS_NETWORK_ACL_ADMIN.ADD_PRIVILEGE( acl => 'localhost.xml', principal => 'OE', is_grant => true, privilege => 'connect'); END; / COMMIT;
现在,您可以编译并运行以下代码,在其中一个 XQuery 表达式中,有一个 XPath 表达式的目标是 purchaseorder 文档中不存在的一个节点:
import java.sql.*; import oracle.jdbc.*; import oracle.jdbc.pool.OracleDataSource; class XQueryPragmas { public static void main (String args[]) throws SQLException { OracleDataSource ods = new OracleDataSource(); ods.setURL("jdbc:oracle:thin:oe/oe@localhost:1521/orcl11g"); Connection conn = ods.getConnection(); Statement stmt = conn.createStatement(); String qry = "SELECT e.ename, po.reference FROM XMLTable("+ "'for $i in $h/employees/employee " + "return $i '" + "PASSING xmlparse(document " + "httpuritype('http://localhost/bonuses.xml').getCLOB()) as \"h\" " + "COLUMNS ename VARCHAR2(25) PATH '/employee/ename', " + " email VARCHAR2(25) PATH '/employee/email') e, " + "purchaseorder p, " + "XMLTable(" + "'for $i in $po/PurchaseOrder " + "return $i ' " + "PASSING OBJECT_VALUE as \"po\" " + "COLUMNS reference VARCHAR2(30) PATH '/PurchaseOrder/Reference', " + " usr VARCHAR2(25) PATH '/PurchaseOrder/User', " + " orddt VARCHAR2(25) PATH '/PurchaseOrder/Date') po " + "WHERE po.usr = e.email " + "ORDER BY po.orddt"; ResultSet rs = stmt.executeQuery(qry); while(rs.next()) System.out.println("User: " + rs.getString(1) + "PORef" + rs.getString(2)); } }
这不足为奇,因为 /PurchaseOrder/Date 节点不符合与 purchaseorder XML 文档关联的 XML 模式,因此可能在该文档中不存在,运行上述代码后将显示以下错误:
ORA-19276: XPST0005 – XPath step specifies an invalid element/attribute name: (Date)
当然,为了避免在失败时无所适从,可以在程序中创建一个异常块来捕获此类错误。但是,Oracle XQuery 针对该问题提供了另一个解决办法,JDBC 代码不必生成任何异常。具体来说,您可以在有问题的 XPath 表达式前面放置杂注 #ora:invalid_path empty #。因此,更新后的查询字符串将如下所示:
... String qry = "SELECT e.ename, po.reference FROM XMLTable("+ "'for $i in $h/employees/employee " + "return $i '" + "PASSING xmlparse(document " + "httpuritype('http://localhost/bonuses.xml').getCLOB()) as \"h\" " + "COLUMNS ename VARCHAR2(25) PATH '/employee/ename', " + " email VARCHAR2(25) PATH '/employee/email') e, " + "purchaseorder p, " + "XMLTable(" + "'for $i in $po/PurchaseOrder " + "return $i ' " + "PASSING OBJECT_VALUE as \"po\" " + "COLUMNS reference VARCHAR2(30) PATH '/PurchaseOrder/Reference', " + " usr VARCHAR2(25) PATH '/PurchaseOrder/User', " + " orddt VARCHAR2(25) PATH '(#ora:invalid_path empty #) {/PurchaseOrder/Date}') po " + "WHERE po.usr = e.email " + "ORDER BY po.orddt"; ...
现在,如果您重新编译并再次运行 XQueryPragmas,将生成以下输出:
User: Steven King PORef: SKING-20021009123337503PDT User: William Smith PORef: WSMITH-20021009123335450PDT User: Steven King PORef: SKING-20021009123337703PDT User: Steven King PORef: SKING-20021009123338294PDT User: Steven King PORef: SKING-20021009123337974PDT User: Steven King PORef: SKING-20021009123336392PDT User: Steven King PORef: SKING-20021009123336131PDT User: Steven King PORef: SKING-20021009123336822PDT User: Steven King PORef: SKING-20021009123336622PDT User: Steven King PORef: SKING-20021009123336321PDT User: Steven King PORef: SKING-20021009123335560PDT User: Steven King PORef: SKING-20021009123336952PDT User: Steven King PORef: SKING-20021009123337153PDT User: William Smith PORef: WSMITH-20021009123337924PDT User: William Smith PORef: WSMITH-20021009123338154PDT User: William Smith PORef: WSMITH-20021009123336412PDT User: William Smith PORef: WSMITH-20021009123335751PDT User: William Smith PORef: WSMITH-20021009123335741PDT User: William Smith PORef: WSMITH-20021009123335650PDT User: Steven King PORef: SKING-20021009123337383PDT
在此要注意的重要一点是,Oracle XQuery 扩展表达式杂注(在该示例中具体讨论了 #ora:invalid_path empty #)通常并不绑定到特定函数(如 XMLTable),可以与其他执行 XQuery 表达式求值的函数结合使用。例如,可以用另一种方式重写此处讨论的 SELECT 语句:删掉第二次出现的 XMLTable(其 XQuery 表达式参数的目标为 purchaseorder 文档)以支持一系列 XMLCast(XMLQuery(…)) 组合。但您仍能够使用杂注。基于 XMLCast(XMLQuery(…)) 实施查询的 ORDER BY 子句可能如下所示:
ORDER BY XMLCast(XMLQuery('(#ora:invalid_path empty #){$p/PurchaseOrder/Date}' PASSING OBJECT_VALUE AS "p" RETURNING CONTENT) AS VARCHAR2(30))
XMLIndex 在 Oracle Database 11g 第 1 版中首次引入并在第 2 版中进行了增强,现在支持结构化内容和非结构化内容。将 XMLIndex 用于非结构化内容对于建立 Web 文档索引尤其有用,因为在这种情况下,具有小结构的信息域非常庞大。另一方面,当您遇到以文档为中心并且包含结构化孤岛的数据时,结构化内容非常有用,能够以关系格式组织此类孤岛。例如,一个表示文章的 Web 文档可能包含 title、author、pubDate 之类的元数据,可以将这些元数据视作此文档中的关系孤岛,因此可采用关系格式进行有益的组织。
为了了解 XMLIndex 的作用方式,我们来执行一个简单示例。首先创建一个新的数据库模式并授予其所需的所有权限:
CONN /as sysdba CREATE USER usr IDENTIFIED BY usr; GRANT connect, resource TO usr; BEGIN DBMS_NETWORK_ACL_ADMIN.ADD_PRIVILEGE( acl => 'localhost.xml', principal => 'USR', is_grant => true, privilege => 'connect'); END; / COMMIT;
下一步是在新建的模式中创建一个要建立索引的表:
CONN usr/usr CREATE TABLE otn_xml( link VARCHAR2(200) PRIMARY KEY, item XMLType);
正如您所看到的,此处创建的 otn_xml 表包含一个 XMLType 列,您可以使用 XMLIndex 为该列创建索引。在执行此操作之前,您可能有兴趣看看针对表中存储的未建立索引的 XML 数据进行的查询的执行统计信息,这样您就能够将其与数据建立索引后针对同样的查询生成的统计信息进行比较。因此,您必须先用数据填充 otn_xml 表。可使用以下 JDBC 代码完成此操作:
import java.sql.*;
import oracle.jdbc.*;
import oracle.jdbc.pool.OracleDataSource;
class XMLIndexXQuery
{
public static void main (String args[]) throws SQLException
{
OracleDataSource ods = new OracleDataSource();
ods.setURL("jdbc:oracle:thin:usr/usr@localhost:1521/orcl11g");
Connection conn = ods.getConnection();
Statement stmt = conn.createStatement();
String qry =
"INSERT INTO otn_xml(item, link) "+
"SELECT * FROM XMLTable(" +
"'for $i in $h/rss/channel/item "+
"return- {$i/title, $i/link, $i/pubDate, $i/description}
' " +
"PASSING xmlparse (document httpuritype
('http://feeds.delicious.com/v2/rss/OracleTechnologyNetwork/otntecharticle').getCLOB()) as \"h\" "+
"COLUMNS item XMLType PATH '/', "+
"link VARCHAR2(200) PATH '/item/link') "+
"WHERE link NOT IN (SELECT link FROM otn_xml)";
int rows = stmt.executeUpdate(qry);
conn.commit();
System.out.println("Number of rows inserted now is: "+ rows);
}
}
由于上面的 JDBC 程序访问互联网资源(OTN — 新文章 RSS 文档),因此您必须在程序执行期间连接到互联网。该程序从 RSS 文档中提取有关最新 OTN 文章的关键信息,并将其放入前面讨论的已创建的 otn_xml 表中。您可以安全地运行该程序任意次 — 它不会将有关同一文章的信息保存两次,也不会引发主键违规错误。这是因为在 INSERT 语句的子查询 WHERE 子句中指定的条件仅允许 otn_xml 表中不包含的行。
现在表已填充好,您可以查询该表了。要执行查询,请回到 SQL 提示工具并以 usr/usr 身份进行连接。假设您使用 SQL*Plus,将 AUTOTRACE 变量设为 on explain,以查看所执行语句的执行统计信息,如下所示:
SET AUTOTRACE ON EXPLAIN
然后,执行目标为 otn_xml 表中单条记录的查询,搜索从 item XMLType 列中存储的 XML 数据中提取的特定文章标题。此类查询可能如下所示:
SELECT XMLCast(XMLQuery('$t/item/title' PASSING o.ITEM AS "t" RETURNING CONTENT) AS VARCHAR2(200)) FROM otn_xml o
WHERE XMLExists('$t/item[title="Enhancing Oracle Database Performance with Flash Storage"]' PASSING o.item AS "t");
下面是包括执行计划的输出:
XMLCAST(XMLQUERY('$T/ITEM/TITLE'PASSINGO.ITEMAS"T"RETURNINGCONTENT)ASVARCHAR2(20
--------------------------------------------------------------------------------
Enhancing Oracle Database Performance with Flash Storage
Execution Plan
----------------------------------------------------------
Plan hash value: 4149380736
--------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 15 | 30030 | 9 (12)| 00:00:01 |
|* 1 | FILTER | | | | | |
| 2 | TABLE ACCESS FULL | OTN_XML | 15 | 30030 | 3 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 2 | 8 | 6 (17)| 00:00:01 |
| 4 | COLLECTION ITERATOR PICKLER FETCH| XMLSEQUENCEFROMXMLTYPE | 1 | 2 | 2 (0)| 00:00:01 |
|* 5 | COLLECTION ITERATOR PICKLER FETCH| XMLSEQUENCEFROMXMLTYPE | 2 | 4 | 4 (25)| 00:00:01 |
--------------------------------------------------------------------------------------------------------------
现在,让我们在 otn_xml 表的 item XMLType 列上创建 XMLIndex。
CREATE INDEX otn_idx ON otn_xml(item) INDEXTYPE IS XDB.XMLIndex;
重新运行这个查询,您应该会看到以下执行计划:
Execution Plan
----------------------------------------------------------
Plan hash value: 3507075359
-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 3034 | 4 (25)| 00:00:01 |
| 1 | SORT GROUP BY | | 1 | 3524 | | |
|* 2 | TABLE ACCESS BY INDEX ROWID | SYS74798_OTN_IDX_PATH_TABLE | 1 | 3524 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | SYS74798_OTN_IDX_PIKEY_IX | 1 | | 1 (0)| 00:00:01 |
| 4 | NESTED LOOPS | | 1 | 3034 | 4 (25)| 00:00:01 |
| 5 | SORT UNIQUE | | 1 | 3022 | 2 (0)| 00:00:01 |
|* 6 | TABLE ACCESS BY INDEX ROWID| SYS74798_OTN_IDX_PATH_TABLE | 1 | 3022 | 2 (0)| 00:00:01 |
|* 7 | INDEX RANGE SCAN | SYS74798_OTN_IDX_VALUE_IX | 1 | | 1 (0)| 00:00:01 |
| 8 | TABLE ACCESS BY USER ROWID | OTN_XML | 1 | 12 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------------
注意,与先前显示的针对访问未建立索引数据的同一查询生成的执行计划相比,上述执行计划几乎没有任何改进。实际上,默认情况下,XMLIndex 使用非结构化内容创建;必须显式包括结构化内容。您可以将结构化内容添加到现有的 XMLIndex 中:注册一个表示该结构化内容的新参数,然后更改索引,添加注册的参数。然而在我们的示例中,我们将重新创建索引,这样它仅包括结构化内容:
DROP INDEX otn_idx;
CREATE INDEX otn_idx ON otn_xml(item) INDEXTYPE IS XDB.XMLIndex
PARAMETERS (
'XMLTable otn_ptab ''/item''
COLUMNS
title VARCHAR2(200) PATH ''title'',
pubDate DATE PATH ''pubDate''');
您可能已经猜到,上面索引的目标是 item 根节点下的 title 和 pubDate 节点,这将加快对这些节点内容的访问速度。
现在,如果您重新运行本节前面部分介绍的查询,应该会看到所生成的执行计划有了重大改进:
Execution Plan
----------------------------------------------------------
Plan hash value: 296194532
-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3 | 378 | 2 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 114 | | |
| 2 | TABLE ACCESS BY INDEX ROWID| OTN_PTAB | 1 | 114 | 1 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | SYS74802_74803_RID_IDX | 1 | | 1 (0)| 00:00:01 |
| 4 | NESTED LOOPS SEMI | | 3 | 378 | 2 (0)| 00:00:01 |
| 5 | INDEX FAST FULL SCAN | SYS_C0011149 | 15 | 180 | 2 (0)| 00:00:01 |
|* 6 | TABLE ACCESS BY INDEX ROWID| OTN_PTAB | 1 | 114 | 0 (0)| 00:00:01 |
|* 7 | INDEX RANGE SCAN | SYS74802_74803_RID_IDX | 1 | | 0 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------
以下查询在 WHERE 子句中使用 LIKE 条件在 /item/description 节点中执行一个模式匹配测试:
SELECT x.title, x.description FROM otn_xml o, XMLTable(
'/item'
PASSING o.item
COLUMNS title VARCHAR2(200) PATH 'title',
description VARCHAR2(2000) PATH 'description') x
WHERE x.description LIKE '%Architect%';
生成的输出可能如下所示:
TITLE
--------------------------------------------------------------------------------
DESCRIPTION
--------------------------------------------------------------------------------
Agile Enterprise Architecture
Can enterprise architecture and agile development play in the same sandbox?
Voices from the community weigh in in the Architect Community column in the Nov-Dec 2010 issue of Oracle Magazine.
Execution Plan
----------------------------------------------------------
Plan hash value: 3907039892
-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 6126 | 11M| 722 (44)| 00:00:09 |
| 1 | NESTED LOOPS | | 6126 | 11M| 722 (44)| 00:00:09 |
| 2 | TABLE ACCESS FULL | OTN_XML | 15 | 30030 | 3 (0)| 00:00:01 |
|* 3 | COLLECTION ITERATOR PICKLER FETCH| XMLSEQUENCEFROMXMLTYPE | 408 | 816 | 48 (44)| 00:00:01 |
-------------------------------------------------------------------------------------------------------------
检查上述执行计划,您可能猜到所获得的 XMLIndex 在此没有任何帮助。这完全在预料之中,因为该索引现在不包括对此类搜索尤为有效的非结构化内容。所以,让我们向索引中添加非结构化内容:
ALTER INDEX otn_idx PARAMETERS('PATH TABLE path_tab');
然后,重新运行查询以查看是否有任何改进。下面是您现在应该会看到的执行计划:
Execution Plan
----------------------------------------------------------
Plan hash value: 445613365
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1534 | 6 (0)| 00:00:01 |
|* 1 | FILTER | | | | | |
|* 2 | TABLE ACCESS BY INDEX ROWID | PATH_TAB | 1 | 3524 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | SYS74802_OTN_IDX_PIKEY_IX | 1 | | 1 (0)| 00:00:01 |
|* 4 | FILTER | | | | | |
|* 5 | TABLE ACCESS BY INDEX ROWID | PATH_TAB | 1 | 3524 | 2 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | SYS74802_OTN_IDX_PIKEY_IX | 1 | | 1 (0)| 00:00:01 |
|* 7 | FILTER | | | | | |
| 8 | NESTED LOOPS | | 1 | 1534 | 4 (0)| 00:00:01 |
|* 9 | TABLE ACCESS FULL | PATH_TAB | 1 | 1522 | 3 (0)| 00:00:01 |
| 10 | TABLE ACCESS BY USER ROWID | OTN_XML | 1 | 12 | 1 (0)| 00:00:01 |
|* 11 | FILTER | | | | | |
|* 12 | TABLE ACCESS BY INDEX ROWID| PATH_TAB | 1 | 3524 | 2 (0)| 00:00:01 |
|* 13 | INDEX RANGE SCAN | SYS74802_OTN_IDX_PIKEY_IX | 1 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------
从上面的结果可以看到,性能改进显而易见。
XQJ
XQJ 是用于实现 XQuery 查询的 Java API。XQJ 允许您执行 XQuery 查询,将数据绑定到 XQuery 查询,然后处理查询结果。XQJ 在许多方面都与 JDBC 类似,但仅用于处理 XQuery 查询。Oracle XML Developer's Kit (Oracle XDK) 支持 XQJ,为您提供用于构建 XQJ 程序的程序包。这些程序包可在以下 Oracle XDK 文件中找到:xdk\lib\xqjapi.jar、xdk\lib\xqjori.jar。因此,编译 XQJ 程序时必须将这些文件包括在类路径中。然而,执行期间可能还需要其他的 JAR。
考虑下面这个示例:使用 XQJ API 连接到 OTN — 新文章 RSS 文档(上个示例中使用过此文档),然后提取那些包括“Oracle Database”子字符串的文章标题。
import javax.xml.xquery.*; import javax.xml.stream.*; import javax.xml.namespace.QName;
class XQJExtSource { public static void main (String args[]) throws Exception { XQDataSource xds = new oracle.xquery.xqj.OXQDataSource(); XQConnection conn = xds.getConnection();
XQPreparedExpression pEx = conn.prepareExpression( "declare variable $doc external;"+ "for $c in $doc//item "+ "where fn:contains($c/title,'Oracle Database') "+ "return $c/title" );
java.net.URL doc = new java.net.URL("http://feeds.delicious.com/v2/rss/OracleTechnologyNetwork/otntecharticle"); java.io.InputStream inpt=doc.openStream();;
pEx.bindDocument(new javax.xml.namespace.QName("doc"), inpt, null, null);
XQResultSequence rslt = pEx.executeQuery(); while (rslt.next()){ System.out.println(rslt.getAtomicValue()); } conn.close(); inpt.close(); } }
使用类路径中包括的 xdk\lib\xqjapi.jar、xdk\lib\xqjori.jar 文件对上述程序进行编译时仍然可能出错,因为这些文件都不包含编译过程在执行 XQPreparedExpression.bindDocument() 方法时所需的 javax.xml.stream.XMLStreamReader 类(至少在 Oracle Database 11.2.0.1.0 引入 XDK 之前)。尽管这个特定示例中使用的 bindDocument() 方法采用 java.io.InputStream 值作为第二个参数,但缺少 javax.xml.stream.XMLStreamReader 会使编译器生成错误。如果您数据库附带的 JDK 版本早于 6.0,那么您不会在 JDK jar 中找到此类。实际上,XMLStreamReader 类属于 Streaming API for XML (StAX),作为 Java 标准版 6 运行时的一部分提供,而不是作为早期版本的一部分提供。因此,您的系统上必须安装了 JDK 6,至少要安装 JRE 6。您可以从 Java SE 下载页面 http://www.oracle.com/technetwork/cn/java/javase/downloads/index.html 下载。您将在 jre\lib\rt.jar 中找到 javax.xml.stream.XMLStreamReader。
运行时,上述程序还必须访问以下 Oracle XDK 文件:xquery.jar 和 xmlparserv2.jar。此外,还必须包括 ORACLE_HOME\jlib 目录中的 orai18n-collation.jar。
上述程序生成的输出可能如下所示(当然,具体取决于 RSS 页面的实际内容):
Enhancing Oracle Database Performance with Flash Storage Enhancing Oracle Database Performance with Sun Flash Storage
需要重点强调的是,上述示例说明了如何通过 URL 访问 Web 文档。与此相反,访问本地文件系统或 Oracle XML DB 信息库中存储的 XML 文档不需要将该文档声明为外部文档,然后使用 XQPreparedExpression.bindDocument() 方法将其绑定到查询。您可以仅在查询内使用 fn:doc XQuery 函数。
因此,重写上述程序以访问 OTN — 新文章 RSS 文档(已下载到本地文件系统上的 /home/myfiles 目录并保存为 otntecharticle.xml)时,您可能生成以下代码:
import javax.xml.xquery.*;
class XQJFileSource { public static void main (String args[]) throws Exception { XQDataSource xds = new oracle.xquery.xqj.OXQDataSource(); XQConnection conn = xds.getConnection();
XQPreparedExpression pEx = conn.prepareExpression( "for $c in fn:doc('/home/myfiles/otntecharticle.xml')//item "+ "where fn:contains($c/title,'Oracle Database') "+ "return $c/title" );
XQResultSequence rslt = pEx.executeQuery(); while (rslt.next()){ System.out.println(rslt.getAtomicValue()); } conn.close(); } }
正如您在本文中所了解的,通过 JDBC 使用 XQuery 的优点在于能够访问各种不同的源,包括那些通常被视为非 JDBC 源的源。因此,只需获得 URL,即可执行 XQuery 查询来访问 Web 上的相应 HTML、XML、RSS 或任何其他文档。
此外,您还了解到 SQL\JDBC 并不是在 Java 中实现 XQuery 查询的唯一方法。您还可以使用 XQJ,此 API 可以直接执行 XQuery 查询而无需 SQL 包装器。
Yuli Vasiliev 是一名软件开发人员、自由撰稿人和顾问,目前专攻开源开发、Java 技术、业务智能 (BI)、数据库和面向服务的架构 (SOA)。他是《Oracle Business Intelligence:The Condensed Guide to Analysis and Reporting》(Packt,2010 年)的作者,也是一系列有关 Oracle 技术的其他图书的作者。