数据库和XML提供了数据存储的完整功能。数据库保存数据用于高效的数据查询,而XML则提供了一种不同应用间信息交换的简单途径。为了利用XML的优点,我们需要将数据库表转化为XML文档。然后我们便可以使用指定的XML工具对这些文档进行其它处理。例如,XML文档可通过XSLT样式表转化为HTML页显示,或通过如XQL这样基于XML的查询语言进行检索,或作为一种数据交换格式,等等。然而,通常将一个数据库转化为XML文档代价不菲,要包括开始时数据转化的花费及以后数据源同步的花费。

     为了能处理XML文档,大多数XML工具使用SAX与DOM编程接口。本文中,我们将看到一种数据库通用编程接口的实现方法,它使得XML工具能象处理XML文档一样处理数据库。通过这种方法,我们可以避免对数据库的XML物理转化。

 

      我们会看到一种用于数据库的SAX编程接口实现,它可通过任何JDBC引擎实现对数据库的操作。然后,我们会看到一种用于数据库的DOM编程接口实现,它是通过SAX编程接口间接实现的。为了演示这种用于数据库的SAX编程接口,我们会看到将其与XT(一种XSLT处理器)的集成。我们同样会看到有关这种集成一个范例,它演示了通过将XSLT样式表如何直接作用于数据库来建立一个HTML页面,及如何将一个数据库转化为一个XML文档。最后,我们会看到如何将用于数据库的DOM编程接口与一个XQL处理器相结合。

    本文中,作者利用已有的工具而不是建立一个新的工具来阐明用于数据库的SAX及DOM应用,并显示如何支持众多的XML工具对数据库进行操作。所有本文中提及的XML工具都是免费的(自由软件或非商业用途免费),当然,即使如此,你仍然应该好好看一下有关版权的说明。

 ·   SAX与DOM编程接口的概况

      SAX是一种基于事件的XML编程接口。通过它,SAX解析器可搜索一个XML文档,并告诉应用程序如某元素的开始与结束等事件。由于解析器是通过查看XML文档的不同部分来产生事件的,因此不需要建立任何内部的结构。这大大减少了对系统资源的需求,并且对那些较大的XML文档的解析非常合适。对于那些以接收数据流形式处理的XML文档,基于事件的XML编程接口是唯一选择。

      另一方面,DOM编程接口采用的是一种树型结构。元素之间具有亲子关系,通过DOM编程接口,解析器基于XML文档建立一个内部结构,从而使应用可以通过树型模式对其进行操作。DOM允许一个应用随意访问树型结构文档,代价是增加了内存的负荷。

     · 面向数据库XML编程接口:基本内容

    由于数据库具有高度规范的数据存储结构,因此我们可以将其映射为以数据为中心的XML文档。例如,我们可以通过如下的DTD范例转化一个数据库为XML文档:

<!ELEMENT table rows*> 
<!ELEMENT rows (column1, column2, ...)> 
<!ELEMENT column1 #PCDATA> 
<!ELEMENT column2 #PCDATA> 
....


      换句话说,通过一个XML数据库编程接口,我们可以使数据库看起来像一个XML文档:即使用API将数据库封装为一个虚拟的XML文档。这里我们使用了面向对象设计的基本概念:即我们提供的是一个接口,而不是方法的实现。从应用的角度,使用这种XML数据库编程接口的工具并不关心它们处理的实际是XML文档或是一个数据库表。

    · 面向数据库的SAX编程接口实现

   为了实现数据库用的SAX编程接口,我们需要实现一个基于JDBC的解析器,遍历数据源的每一行与列,并产生适当的SAX事件。SAX规范提供了org.xml.sax.InputSource类,它可以将一个数据源以一个URL或一个数据字节流的方式引用。我们可以使用JDBCInputSource,它扩展了org.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 in 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, String 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; 
} 
}

(未完待续)