Java 版 (精华区)

发信人: rhine (有雨无风), 信区: Java
标  题: XML APIs for Databases
发信站: 哈工大紫丁香 (2000年12月17日19:36:37 星期天), 站内信件

XML APIs for Databases
By Ramnivas Laddad ; Reprinted from JavaWorld
For processing XML documents, most XML tools work with the SAX or DOM 
API. In this article, we'll look at a way to implement the same APIs 
directly over a database, enabling XML tools to treat databases as if 
they were XML documents. That way, we can obviate the need of converting
 a database. 

We'll see an implementation of the SAX API for Databases that should 
work with any database with a JDBCTM driver. Next, we'll examine an 
implementation of the DOM API for Databases that uses the SAX API 
internally. To demonstrate the SAX API for Databases, we'll look at 
its integration with XT (an XSLT processor). We'll also see an example 
of how such integration can create HTML pages that incorporate an XSLT 
stylesheet directly from a database and how it can convert a database 
into an XML document. Finally, we'll look at how the DOM API for 
Databases integrates with an XQL processor. 

In this article, I make use of existing tools rather than create new 
tools to illustrate the applications of the SAX and DOM APIs for 
Databases. I show how to leverage a number of available XML tools to 
work with a database. All the XML tools I mention are either available 
for free or free for noncommercial use (though you should, of course, 
check licensing agreements). 


Overview of the SAX and DOM APIs
SAX is an event-based API for XML. With it, the SAX parser reports 
events such as the start and end of elements to the application as it 
walks over the document. Because the parser reports events as it 
visits different parts of the document, it does not have to build any 
internal structure. That reduces the strain on system resources, which 
makes the parser attractive for large documents. For XML documents 
received as continuous streams, an event-based API is the only choice. 

The DOM API, on the other hand, follows a treelike construct. Elements 
have parent-child relations with other elements. With this API, the 
parser builds an internal structure such that an application can 
navigate it in a treelike fashion. DOM allows an application to have 
random access to the tree-structured document at the cost of increased 
memory usage. 


XML APIs for databases: The basics
Because of a database's highly regular data-storage structure, we can 
map it into data-centric XML documents. For example, we can transform 
a database table into an XML document with a DTD of the following form:
 
<!ELEMENT table rows*>
<!ELEMENT rows (column1, column2, ...)>
<!ELEMENT column1 #PCDATA>
<!ELEMENT column2 #PCDATA>
....


In other words, with an XML API for databases, we can make the 
database look like an XML document; these APIs present the database as a
 virtual XML document. We are at the most basic concept of 
object-oriented design: it is the interface -- not the implementation --
 that matters. In our situation, the tools using such an XML API need 
not care whether they are operating on a database table or an XML file.
 


 
A SAX or DOM parser can enable XML tools to work directly with databases
 

Back to Top 

Implementing the SAX API for Databases
To implement the SAX API for Databases, we need to implement a parser 
that operates on a JDBC data source, iterates over each row and column,
 and generates appropriate SAX events while iterating. The SAX 
specification provides the org.xml.sax.InputSource class that models a 
data source representing a URL or a byte stream. To represent a 
database, we need a specialized form of it that can represent a table in
 a database. We therefore implement JDBCInputSource, which extends the 
org.xml.sax.InputSource class. Let's look at JDBCInputSource in more 
detail: 
// 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);

}
}



In the code above, the constructor takes the information needed to 
connect to a database and the name of the table to be parsed. The method
 getConnection() connects to the database and returns a Connection 
object. 

Next, we need to implement the SAX parser that uses JDBCInputSource to 
iterate over database table rows and columns and generates SAX events 
along the way. To simplify the code, we create an abstract ParserBase 
class, which implements the org.xml.sax.Parser and has responsibility 
only for managing various handlers. We then create our SAX parser for 
the JDBC source JDBCSAXParser that extends the ParserBase class. 

(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; 
}
}


Let's examine the code in more detail. JDBCSAXParser includes several 
overloaded parse() methods. In the list below, the org.xml.sax.Parser 
interface requires implementing the parse(InputSource) and parse(String)
 methods. The other parse() methods simplify the code and allow 
derived classes to override them to modify the parser behavior. 


The parse(InputSource) method calls the parse(JDBCInputSource) method if
 the argument is of type JDBCInputSource; otherwise, it throws a 
SAXException as it cannot deal with it. 

The parse(String) method throws a SAXException as the information 
supplied is not sufficient to access the database. 

The parse(JDBCInputSource) method gets a Connection object from the 
input source and executes a query to obtain a ResultSet object. It 
then calls parse(ResultSet) with this object. 

The parse(ResultSet, String) method performs the core parsing logic. 
It iterates over each row in the result set and each column in the rows.
 The row iteration loop is surrounded by calls to startElement() and 
endElement() with a table marker as the element-name argument. 
Similarly, each column iteration loop is surrounded by calls to 
startElement() and endElement() with a row marker as the element-name 
argument. In both cases an empty attribute list passes as the second 
argument to the startElement() methods. On each visit to a column, the 
generateSAXEventForColumn() method is called with column-name and 
column-value arguments. The value of a column is accessed by the 
getString() method on the result-set object, as we need a string 
representation of the column data to be notified in the characters() SAX
 event. 

The convenience method parse(String, String, String, String) simply 
creates a JDBCInputSource object with the arguments passed to it and 
then calls the parse(JDBCInputSource) method with it. 
The protected methods of JDBCSAXParser offer some customization 
possibilities through overriding: 


The generateSAXEventForColumn() method generates events for column data.
 A null value for a column in a database has a different meaning from 
a column with an empty string. We capture the difference by not firing 
any events for a column that has a null value. Another choice for 
representing a null value in a database is to use a binary attribute 
like isNull. With this option, a true value will be set for null data; 
otherwise it will be false. 

The getTableMarker(), getRowMarker(), and getColumnMarker() methods 
return reasonable defaults for table, row, and column markers. Derived 
classes may override these to provide custom markups. 

The getSelectorSQLStatement() method returns a "select * from 
<tableName>" string. Derived classes can override it to provide a 
different select query string to offer database-level filtering. 
The JDBCSAXUtils convenience class provides two methods for creating a 
JDBCInputSource: it can be done from either a property file or a 
Property object. There's no need to supply a long list of parameters 
that describe a database to an application that uses either the SAX or 
DOM APIs for database. The class expects the user to supply a property 
file that contains entries for a database URL, a user name and 
password to connect to the database, a JDBC driver to establish a 
connection, and a table name. The code below demonstrates a typical 
property file: 

URL=jdbc:odbc:db1
user=jw
password=jw-passwd
table=portfolio
driver=sun.jdbc.odbc.JdbcOdbcDriver



Back to Top 

The story so far ...
We now have a simple parser that can generate appropriate SAX events for
 the information in a database table. It takes care of null data and 
offers some marker customization. While such functionality may be 
sufficient for some applications, the complete solution will consider 
additional functionality because: 


The parser does not incorporate relational information. That can be 
solved by using a XPointer/XLink to set the reference to a foreign key 
in a table. 

A text column in a database may contain marked-up data. A SAX parser for
 databases should parse those data as well and generate appropriate 
SAX events. If such functionality is important for an application, it 
could override generateSAXEventForColumn() and parse the content of 
the column and generate additional SAX events. 

In databases, a table contains an unordered list of columns; the order 
in which columns are stored is not important. An XML DTD, on the other 
hand, does not have a way to describe an unordered collection of child 
elements. 
We can deal with this problem in a few ways. If the task is to convert a
 database into another XML document, say an HTML page, the XSLT 
stylesheet written for that purpose can create output in the correct 
order. We could also override the getSelectorSQLStatement() method to 
supply an explicit list of columns in the correct order. 


It is desirable to present only a selected part of a table as a document
 based on some query. While XML tools can do the filtering, databases 
are better at it. The getSelectorSQLStatement() method can be overridden
 to return the appropriate select query string. 

The parser uses the getString() method on the result-set object to 
obtain the string representation of the value in a column. This works 
fine for columns with text, numbers, and so on, but it does not work 
well with binary data. While binary data can be represented as text, 
that may not be suitable for certain tasks. The parser also does not 
deal with user-defined types available with SQL3/JDBC 2.0. 
We can solve both problems by overriding the generateSAXEventForColumn()
 method and providing a suitable implementation. 


Back to Top 


Implementing the DOM API for Databases
To build a DOM tree for a database table, we could iterate over rows and
 columns and build nodes for a tree as we visit them. Or we could employ
 another library, like Sun's JAXP implementation, that builds a DOM tree
 from a SAX event stream. The latter approach is simpler, and it 
requires less coding because it reuses an existing facility. To 
implement the DOM API using such an approach we need just a clever reuse
 of the SAX parser for databases we implemented earlier. 
Class JDBCDOMParser implements the DOM API for Databases: 

// 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();
}
}


The implementation for class JDBCDOMParser is simple. It uses the 
XmlDocumentBuilder class provided by JAXP to build a DOM document from a
 SAX event stream. The JDBCDOMParser has only one method: 
createDocument(), which takes a JDBC data source as the argument. The 
method creates a JDBCSAXParser and sets it as a parser for an 
XmlDocumentBuilder object. It then fires the parsing and returns the 
resulting document from the XmlDocumentBuilder object. Internally, the 
XmlDocumentBuilder object responds to the SAX events generated by the 
JDBCSAXParser object by building a DOM document. 


Using the SAX API for Databases
We have already looked at one example of using the SAX API for Databases
 to implement a DOM API for Databases. Here we look at another example 
of using the SAX API for Databases. In this section, we look at using 
the SAX API for Databases to integrate with XT -- an XSLT processor 
written in Java. With such integration, we can apply an XSLT 
stylesheet directly to the virtual XML documents stored in a database. 

We wrap the logic of creating a SAX source for a given database source 
and processing it with the given XSLT stylesheet to produce an output 
file in class JDBCXSLProcessor, which is based on com.jclark.xsl.sax.
Driver from XT. The main method takes three arguments: a database 
property file, an XSLT stylesheet file, and an output file. 

As we'll see below, we can use this approach to generate HTML pages 
directly without incurring the penalty of creating an intermediate XML 
file. Moreover, we'll look at how we can use the integration of the 
SAX API for databases and XT to convert a database into a nonvirtual XML
 document. 

(To view the source code for JDBCXSLProcessor.java, click here.) 

Generating HTML pages directly from a database using an XSLT 
stylesheet
Here we look at a simple stylesheet that formats a highly regular XML 
document represented by a database table. The database table is 
formatted as an HTML table. The generic stylesheet createTable.xsl can 
be used to transform any XML document with a tablelike structure. The 
stylesheet uses names of markers for columns as table headers. 

(To view the stylesheet createTable.xsl, click here.) 

Converting a database to XML with an XSLT stylesheet
While most XML applications work with the SAX or DOM API, we may need to
 obtain an XML document file in some situations. For example, we need an
 XML document to use a tool that does not work with either API. Here, 
I suggest a way to convert a database into an XML file. With this 
approach, we write an XSLT stylesheet to do the identity transformation.
 Using such a stylesheet with the SAX API for Databases will yield an 
XML document that represents a table in the database. I have provided 
a stylesheet -- identity.xsl -- for the identity transformation that 
works with the current implementation of XT. 


<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="@*|*">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>
</xsl:stylesheet>

Note that while an XSLT stylesheet easily performs the identity 
transformation, it is not very efficient as it goes through the 
complex general-purpose logic of applying the full stylesheet. This 
inefficiency can be a particular problem for database tables with 
large numbers of records. An alternative approach is to write an 
application to perform the identity transformation. Such an 
application would listen to SAX events and create XML elements and 
data in response to them, resulting in an XML document that represents 
the table in the database. 


Using the DOM API for Databases
For most situations, the SAX API for Databases is more memory 
efficient than the DOM API for Databases. However, some applications 
need random access to the XML documents and therefore require the 
treelike structure that the DOM API for Databases offers. 
Integrate the DOM API for Databases with the XQL processor
XML Query Language (XQL), a query language for XML documents, has a 
syntax similar that of to XPath patterns. Here, we integrate our DOM 
parser for database with GMD-IPSI's XQL Engine. With such integration, 
we can perform SQL-like queries on the XML document representing a 
database table. 

As an example of integration, I provide a simple shell for querying 
the XML document obtained from a database table. The class 
JDBCXQLProcessor creates a shell-like environment, which takes the 
queries from the user and prints the resulting document. The method 
processQueries() can work with any Document object -- not just objects 
created by the JDBCDOMParser. It reads the System.in for the query 
string, performs the query, and prints the result on System.out. The 
main() method creates a JDBCInputSource object from its argument and 
uses it with JDBCDOMParser to obtain a Document object corresponding 
to the given database table. 

(To view the code for JDBCXQLProcessor.java, click here.) 

As a side note, writing a database-to-XML converter is a snap with 
XMLWriter and JDBCDOMParser. Just get a Document object from 
JDBCDOMParser and write it to the desired file with XMLWriter.
write(Document). 


Conclusion
In this article, we discussed using the XML APIs for database to tap the
 information residing in databases. With such APIs, we can avoid the 
costs associated with converting a database into XML documents and the 
cost of keeping them synchronized. We presented a Java implementation of
 SAX and DOM APIs for databases, which can work with any database with a
 JDBC driver. We then presented an integration of SAX API for 
Databases with XT. We illustrated the uses of such integration to create
 HTML pages directly from a database and to convert databases into XML 
documents. Finally, we integrated the DOM API for Databases with an 
XQL processor. 
Complete Source Code: zip file 


 

Reprinted with permission from the January 2000 edition of JavaWorld 
magazine. Copyright Web Publishing Inc., an IDG Communications company.
 Register for editorial e-mailalerts 


About the Author
Ramnivas Laddad is a Sun-certified architect of Java technology (Java 
2). He has a master's in electrical engineering with a specialization in
 communication engineering. He has six years of experience with 
several software projects involving GUI, networking, and distributed 
systems. He has developed object-oriented software systems in Java for 
the last two years and in C++ for the last five years. 

Ramnivas works at Real-Time Innovations, where he is working to design 
and develop ControlShell, a component-based programming framework for 
complex real-time systems. 

Back to Top 

------------------------------------------------------------------------
--------

--
           海纳百川,
                   有容乃大,
                           壁立千尺,
                                   无欲则刚。    

※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: dip.hit.edu.cn]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:212.367毫秒