为了能处理 XML 文档,大多数 XML 工具使用 SAX 与 DOM 编程接口。本文中,我们将看到一种数据库通用编程接口的实现方法,它使得 XML 工具能象处理 XML 文档一样处理数据库。通过这种方法,我们可以避免对数据库的 XML 物理转化。
我们会看到一种用于数据库的 SAX 编程接口实现,它可通过任何 JDBC 引擎实现对数据库的操作。然后,我们会看到一种用于数据库的 DOM 编程接口实现,它是通过 SAX 编程接口间接实现的。为了演示这种用于数据库的 SAX 编程接口,我们会看到将其与& nbspXT(一种 XSLT 处理器)的集成。我们同样会看到有关这种集成一个范例,它演示了通过将 XSLT 样式表如何直接作用于数据库来建立一个 HTML 页面,及如何将一个数据库转化为一个 XML 文档。最后,我们会看到如何将用于数据库的 DOM 编程接口与一个 XQL 处理器相结合。
本文中,作者利用已有的工具而不是建立一个新的工具来阐明用于数据库的 SAX 及 DOM 应用,并显示如何支持众多的& nbspXML 工具对数据库进行操作。所有本文中提及的 XML 工具都是免费的(自由软件或非商业用途免费),当然,即使如此,你仍然应该好好看一下有关版权的说明。
SAX 与 DOM 编程接口的概况
SAX 是一种基于事件的 XML 编程接口。通过它,SAX 解析器可搜索一个 XML 文档,并告诉应用程序如某元素的开始与结束等事件。由于解析器是通过查看 XML 文档的不同部分来产生事件的,因此不需要建立任何内部的结构。这大大减少了对系统资源的需求,并且对那些较大的 XML 文档的解析非常合适。对于那些以接收数据流形式处理的 XML 文档,基于事件的& nbspXML 编程接口是唯一选择。
另一方面,DOM 编程接口采用的是一种树型结构。元素之间具有亲子关系,通过& nbspDOM 编程接口,解析器基于 XML 文档建立一个内部结构,从而使应用可以通过树型模式对其进行操作。DOM 允许一个应用随意访问树型结构文档,代价是增加了内存的负荷。
面向数据库 XML 编程接口:基本内容
由于数据库具有高度规范的数据存储结构,因此我们可以将其映射为以数据为中心的 XML 文档。例如,我们可以通过如下的 DTD 范例转化一个数据库为XML文档:
....
换句话说,通过一个 XML 数据库编程接口,我们可以使数据库看起来像一个 XML 文档:即使用& nbspAPI 将数据库封装为一个虚拟的 XML 文档。这里我们使用了面向对象设计的基本概念:即我们提供的是一个接口,而不是方法的实现。从应用的角度,使用这种 XML 数据库编程接口的工具并不关心它们处理的实际是 XML 文档或是一个数据库表。
面向数据库的 SAX 编程接口实现
为了实现数据库用的 SAX 编程接口,我们需要实现一个基于 JDBC 的解析器,遍历数据源的每一行与列,并产生适当的 SAX 事件。SAX 规范提供了 org.xml.sax.InputSource 类,它可以将一个数据源以一个 URL 或一个数据字节流的方式引用。我们可以使用 JDBCInputSource,它扩展了& nbsporg.xml.sax.InputSource 类,以下是 JDBCInputSource 的详细内容:
// JDBCInputSource.java
package dbxml.sax;
import java.sql.*;
import org.xml.sax.InputSource;
public class JDBCInputSource extends InputSource {
private String _connectionURL;
private String _userName;
private String _passwd;
private String _tableName;
public JDBCInputSource(String connectionURL, String userName,
String passwd, String tableName) {
super(connectionURL);
_connectionURL = connectionURL;
_userName = userName;
_passwd = passwd;
_tableName = tableName;
}
public String getTableName() {
return _tableName;
}
public Connection getConnection() throws SQLException {
return DriverManager.getConnection(_connectionURL, _userName, _passwd);
}
}
在上述的代码中,构造函数使用了数据库连接所需的信息及将被解析的数据库表名。方法getConnection() 连接数据库并返回一个连接对象。
下一步,我们需要通过 JDBCInputSource 实现 SAX 解析器,并遍历数据库表的行与列,产生 SAX 事件。为了简
代码,我们创建了一个抽象的 ParserBase 类,它实现了 org.xml.sax.Parser 类并负责管理不同的句柄。然后我们建立一个基于 JDBC 的 SAX 解析器 JDBCSAXParser,它扩展了 ParserBase 类:
(To view the code for ParserBase.java, click here.)
// JDBCSAXParser.java
package dbxml.sax;
import java.io.IOException;
import java.sql.*;
import org.xml.sax.*;
import org.xml.sax.helpers.AttributeListImpl;
public class JDBCSAXParser extends ParserBase {
private static final AttributeList _stockEmptyAttributeList
= new AttributeListImpl();
//------------------------------------------------------------------
// Methods from the Parser interface
//------------------------------------------------------------------
public void parse (InputSource source) throws SAXException, IOException {
if (! (source instanceof JDBCInputSource)) {
throw new SAXException("JDBCSAXParser can work only with source "
+ "of JDBCInputSource type");
}
parse((JDBCInputSource)source);
}
public void parse (String systemId) throws SAXException, IOException {
throw new SAXException("JDBCSAXParser needs more information to "
+ "connect to database");
}
//------------------------------------------------------------------
// Additional methods
//------------------------------------------------------------------
public void parse(JDBCInputSource source)
throws SAXException, IOException {
try {
Connection connection = source.getConnection();
if (connection == null) {
throw new SAXException("Could not establish connection with "
+ "database");
}
String sqlQuery = getSelectorSQLStatement(source.getTableName());
PreparedStatement pstmt = connection.prepareStatement(sqlQuery);
ResultSet rs = pstmt.executeQuery();
parse(rs, source.getTableName());
rs.close();
connection.close();
} catch (SQLException ex) {
throw new SAXException(ex);
}
}
public void parse(ResultSet rs, String tableName)
throws SAXException, SQLException, IOException {
if (_documentHandler == null) {
return; // nobody is interested& nbspin me, no need to sweat!
}
ResultSetMetaData rsmd = rs.getMetaData();
int numCols = rsmd.getColumnCount();
String tableMarker = getTableMarker(tableName);
String rowMarker = getRowMarker();
_documentHandler.startdocument();
_documentHandler.startElement(tableMarker, _stockEmptyAttributeList);
while(rs.next()) {
_documentHandler.startElement(rowMarker,
_stockEmptyAttributeList);
for (int i = 1; i <= numCols; i++) {
generateSAXEventForColumn(rsmd, rs, i);
}
_documentHandler.endElement(rowMarker);
}
_documentHandler.endElement(tableMarker);
_documentHandler.enddocument();
}
public void parse(String connectionURL,& nbspString userName, String passwd,
String tableName) throws SAXException, IOException {
parse(new JDBCInputSource(connectionURL, userName, passwd, tableName));
}
//------------------------------------------------------------------
// Protected methods that derived classes could override to
// customize the parsing.
//------------------------------------------------------------------
protected void generateSAXEventForColumn(ResultSetMetaData rsmd,
ResultSet rs,
int columnIndex)
throws SAXException, SQLException {
String columnvalue = rs.getString(columnIndex);
if (columnvalue == null) {
return;
}
String columnMarker = getColumnMarker(rsmd.getColumnLabel(columnIndex));
char[] columnvalueChars = columnvalue.toCharArray();
_documentHandler.startElement(columnMarker,
_stockEmptyAttributeList);
_documentHandler.characters(columnvalueChars,0, columnvalueChars.length);
_documentHandler.endElement(columnMarker);
}
protected String getTableMarker(String tableName) {
return tableName;
}
protected String getRowMarker() {
return "row";
}
protected String getColumnMarker(String columnName) {
return columnName;
}
protected String getSelectorSQLStatement(String tableName) {
return "select * from " + tableName;
}
}
让我们来看看上述代码的详细内容。JDBCSAXParser 包括了几个重载的 parse() 方法。在下表中 org.xml.sax.Parser 接口需要实现 parse(InputSource) 与 parse (String) 方法。其它 parse() 方法简化了代码并允许通过派生类重载以改变其解析方法。
如果参数为 JDBCInputSource 类型,Parse(InputSource) 方法调用 parse (JDBCInputSource) 方法,否则,产生一个 SAXException 事件表示无法处理数据源。当所提供的信息不足以访问数据库时 parse(String) 方法产生一个 SAXException 事件。
Parse(JDBCInputSource) 方法对输入源建立一个连接对象,并执行一个查询以获得一个 ResultSet 对象。然后对该对象可调用 parse(ResultSet) 方法。
Parse(ResultSet,String) 方法执行解析的核心逻辑。它遍历结果集中的每一行与字段。在对每一行循环时调用方法& nbspStartElement() 与 endElement() (将数据库表标识作为元素名参数)。同样的,在对每一行记录的每个字段循环时会调用方法 StartElement() 与 endElement()(将行标识作为元素名参数)。在上述两种情况中一个空的属性表作为第二参数传递给 startElement()。在访问记录的每个字段时,方法 generateSAXEventForColumn() 被调用(使用字段名与字段值为参数)。通过对结果集对象使用& nbspgetString90 方法可获得单个字段的值,同样我们需要用一个字符串表征字段数据,并在 characters() 事件中使用。
方法 parse(String, String, String, String) 通过传递给它的参数简单有效地建立了一个JDBCInputSource 对象,然后可对该对象使用 parse (JDBCInputSource) 方法。
方法 JDBCSAXParser(protected 方式)通过过载提供了一些专门的特性:
方法 generateSAXEventForColumn() 为字段数据产生事件。一个数据库中的字段空值(null)与一个字段零值(empty)有着不同的含义。我们通过过滤那些具有 null 值的字段来捕获这种差别。另一种表现数据库中null值的方法是使用一个二进制属性(如 isNull)。通过该选项,一个真值(true)被认为是 null 值,否则就不是。
GetTableMarker(),getRowMarker(),及 getClumnMarker() 方法可分别返回合适的表、行、字段默认值。派生出来的类可重载这些方法以提供特定的标识。
方法 GetTableMarker() 返回一个“select * from< tableName>”字符串。派生出来的类可通过重载该方法以提供一个不同的 select query 字符串,并实现数据库级的过滤。
类 JDBCSAXUtils 提供两种方法来建立一个& nbspJDBCInputSource 对象:通过属性(property)文件或一个Property 对象。它不需要通过& nbspSAX 或 DOM 编程接口提供一个有关数据库的参数表给应用程序。它希望用户来提供一个包含完整数据库& nbspURL 入口的属性文件,一个可连接到数据库的用户名及密码,一个用于建立连接JDBC 数据库引擎,及数据库表名。以下是一个典型的属性文件:
# portfolio.prop
# JDBCSAXSource property file
URL=jdbc:odbc:db1
user=jw
password=jw-passwd
table=portfolio
driver=sun.jdbc.odbc.JdbcOdbcDriver
我们现在有了一个简单的解析器,它能对数据库表产生适当的SAX事件。它能区分 null 值并提供一些专用的标识。这些功能对于一些应用已经足够了,而某些完整的解决方案还需要一些附加的功能,这是因为:
解析器不能合并那些关联的信息,该问题的解决可通过使用 Xpointer/Xlink 来设置表中外键的相关信息。
数据库中的一个text字段可能会包含标识(marked-up)信息。一个用于数据库的 SAX 解析器应该能解析这类数据并产生适当的SAX事件。如果这类功能对某个应用非常重要,可以通过重载generateSAXEventForColumn() 方法并解析该字段的内容以产生附加的 SAX 事件
在数据库中,一个数据库表包含了未排序的字段集;有关字段存储的排序并不重要。另一方面,一个 XML DTD,无法描述一个未排序的子元素集。
我们可以通过几种方法处理该问题。如果我们要将数据库转化为另一种 XML 文档,比如说一个 HTML 页面,为其定制的 XSLT 样式表可建立正确排序的输出结果。我们也可以重载 getSelectorSQLStatement () 方法直接提供一个正确排序的字段列表。
有时我们希望通过某些查询能将一个表的被选择部分作为一个 XML 文档封装。如果 XML 工具能实现这样的过滤,我们就可以更好的使用数据库。方法& nbspgetSelectorSQLStatement() 可以通过重载并返回合适的select 结果字符串。
解析器通过对结果集(result-set)对象使用 getString() 方法可获得字符串形式的某个字段值。该操作对于文本、数字等类型的字段非常合适,但对于二进制数据就不适合。当使用文本表示二进制数据时,在某些操作中会无法使用。解析器同样无法处理某些可被& nbspSQL3/JDBC2.0 接受的用户自定义的数据类型。
对于上述问题我们可以通过重载 generateSAXEventForCloumn() 方法以及提供一种合适的处理(implementation)来实现。
面向数据库的 DOM 编程接口实现
为了建立一个对应于一个数据库表的 DOM 树,我们可以遍历每一行每个字段并为其建立树结点,或者我们可以通过其它的类库,如  Sun 的 JAXP 工具,它可以通过一个 SAX 事件流建立一个 DOM 树。后一个方法更简单,代码更简练,因为它利用了一个已有的功能。为了通过这种方法实现 DOM 编程接口,我们需要一个合适的  SAX 数据库解析器,使我们的实现更方便。
将DOM数据库编程接口与 XQL 处理器相集成
通过类 JDBCDOMParser 实现面向数据库的 DOM 编程接口:
// JDBCDOMParser.java
package dbxml.dom;
import java.io.IOException;
import org.w3c.dom.document;
import org.xml.sax.SAXException;
import com.sun.xml.tree.XmldocumentBuilder;
import dbxml.sax.*;
public class JDBCDOMParser {
public static document createdocument(JDBCInputSource inputSource) throws SAXException, IOException {
XmldocumentBuilder documentBuilder = new XmldocumentBuilder();
JDBCSAXParser saxParser = new JDBCSAXParser();
documentBuilder.setParser(saxParser);
saxParser.parse(inputSource);
return documentBuilder.getdocument();
}
}
有关类 JDBCDOMParser 的实现较为简单,它通过 JAXP 所提供的 XmldocumentBuilder 类可以从一个SAX事件流构建一个 DOM 文档。JDBCDOMParser 仅有一个方法:createdocument(),它需要一个 JDBC 数据源作为参数。该方法建立一个 JDBCSAXParser 并可用其来解析一个实际的 XmldocumentBuilder 对象。然后它释放解析对象并返回 XmldocumentBuilder 对象中产生的结果。在实际编程实现中,XmldocumentBuilder 对象通过建立一个 DOM 文档的方法来响应 JDBCSAXParser 对象所产生的SAX事件。
利用面向数据库的 SAX 编程接口
我们已经看了一个通过面向数据库的SAX编程接口来实现 DOM 编程接口的实例。现在我们要看另一个使用SAX 编程接口的例子。在本节中,我们将看到如何将 SAX 数据库编程接口与XT(一个用 Java 写的 XSLT 处理器)相集成。通过这种集成,我们可以对一个存储在数据库中的虚拟XML文档直接使用 XSLT 样式表。
我们封装了实际建立一个 SAX 数据库源的逻辑,并用给定的XSLT样式表加以处理,在类JDBCXSLProcessor 中产生一个输出文件(使用 XT 中的 com.jclark.xsl.sax.Driver)。其中主要的方法包含三个参数:一个数据库特征文件,一个 XSLT 样式表文件,一个输出文件。
如我们即将在下面看到的,我们可以通过这种方法直接生成  HTML 页面而不需要一个临时的 XML 文件来过渡。另外,我们还将看到如何将 SAX 数据库编程接口与XT相结合,以将一个数据库转化为一个物理的 XML 文档。
利用 XSLT 样式表从数据库直接生成 HTML 页面
现在我们来看一个简单的样式表,它可以将一个高度规范的 XML 文档(基于数据库表)格式化显示。该数据库表将被格式化为一个  HTML 表。样式表 createTable.xsl 可以被用于处理任何具有表状结构的& nbspXML 文档。该样式表使用字段的标记名作为表头标题。
通过一个 XSLT 样式表将一个数据库转化为一个 XML 文档
尽管大多数 XML 应用程序可以直接使用 SAX 或 DOM 编程接口,不过在某些场合我们仍需要得到一个实际的 XML 文档。举例而言,对于某个不使用任何一种 XML API的工具,我们就需要一个物理 XML 文档。在这里,我们推荐一种由数据库生成 XML 文档的方法。在这种方法中,我们制定一个  XSLT 样式表来实现统一的转换。使用这样一个样式表(与一个 SAX 数据库编程接口相结合),我们可以生成一个与数据库表相对应的XML 文档。在此,作者提供了一个样式表 identity.xsl 可用于在当前版本的 XT 中,以实现数据的统一转换。
identity.xsl
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
注意虽然一个 XSLT 样式表可以容易地实现数据的成批转换,不过这种方法并不十分有效,因为一个完整的样式表其逻辑也许会非常复杂,从而影响样式表的建立及实际数据转换的效率。尤其当数据库表中包含大量记录时这个问题会特别明显。一个折衷的方法是写一个专门用于批量转换的应用程序。这样一个应用程序可以监听 SAX 事件并建立 XML 元素(及对应的实际数据),所产生的结果& nbspXML 文档与数据库表相对应。
使用DOM数据库编程接口
在大多数场合,SAX 数据库编程接口较 DOM 编程接口更节约系统资源。不过,某些应用程序要求随意的访问 XML 文档,因此就会需要提供一个类似与 DOM 的树形结构来表示数据库。
将 DOM 数据库编程接口与 XQL 处理器相集成
XML Query Language(XQL)是一种用于 XML 文档查找的语言,其语法与  Xpath 方式相近。在这里,我们将我们的 DOM 数据库解析器与 GMD-& nbspIPSI’S XQL Engine 相集成。通过这种集成我们可以对一个表征数据库表的& nbspXML 文档执行类似于 SQL 的查询。
作为一个集成的例子,作者提供一个简单封装的用于查询一个基于数据库表的 XML 文档。
类 JDBCXQLProcessor 建立一个类似于封装的环境,它接收客户的查询并输出结果文档。方法 PprocessQueries() 可以操作任何文档对象不仅是由 JDBCDOMParser 所建立的对象。它读系统,执行查询请求并输出系统的查询结果。Main() 方法可通过其参数建立一个 JDBCInputSource 对象并将其传递给 JDBCDOMParser 以获得一个与所给数据库表相对应的文档对象。
另外说明一点,通过& nbspXMLWriter 与JDBCDOMParser 写一个数据库到 XML 的转换采用的是快照方式。即从 JDBCDOMParser 得到一个文档对象并通过 XMLWriter.write(document) 写到目标文件中。
在本文中,我们讨论了如何通过面向数据库的 XML 编程接口来表征数据库中的信息。通过这种编程接口,我们可以避免将一个数据库转换为一个实际的 XML 文档以及避免保持二者间的同步。我们介绍了一种通过Java 实现的 SAX 与& nbspDOM 数据库编程接口,然后介绍了一种与 XT 相集成的 SAX 编程接口。我们演示了通过这种集成由数据库表直接导出 HTML 页面以及将数据库表转换为 XML 文档。最后,我们将 DOM 数据库编程接口与一个 XQL 处理器相集成。