1 .
Introducción a los APIs SAX y DOM
2 .
APIs de XML para Bases de Datos: Lo Básico
3 .
Implementar el API SAX para Bases de Datos
4 .
Implementar el API DOM para Bases de Datos
5 .
Usar el API SAX para Base de Datos
6 .
Usar el API DOM para Bases de Datos
7 .
Conclusión
|
Nota:
Puedes encontrar la versión original en Inglés de este artículo en javaworld
|
Para procesar documentos XML, la mayoría de las herramientas XML trabajan con los APIs SAX o DOM. En este artículo, veremos una forma
de implementar los mismo APIs directamente sobre una base de datos, permitiendo a las herramientas de XML tratar con bases de datos como
si fueran documentos XML. De esta forma, podemos obviar la necesidad de convertir una base de datos.
Veremos una implementación del API SAX para Bases de Datos que debería funcionar con cualquier base de datos con
un driver JDBC. Luevo examinaremos una implementación del API DOM para Bases de Datos que usa el API SAX internamente.
Para demostrar el API SAX para Bases de Datos, veremos su integración con XT (un procesador XSLT). También veremos un ejemplo
de como dicha integración puede crear páginas HTML que incorporan hojas de estilo XSLT directamente desde la base de datos. Finalmente,
veremos como el API DOM para bases de datos se integra con un procesador XQL.
En este artículo, haremos uso de las herramientas existentes en vez de crear nuevas herramientas para ilustrar las aplicaciones de los
APIs SAX y DOM para bases de datos. Hemos visto como ha crecido el número de herramientas XML disponibles para trabajar con bases
de datos. Todas las herramientas XML que mencionamos aquí o son gratuitas o lo són para un uso no-comercial (aunque deberías,
por supuesto, comprobar los acuerdos de licencia).
Introducción a los APIs SAX y DOM
SAX es un API para XML basado en eventos. Con él, el analizador SAX reporta los eventos como el inicio o final de los elementos de la
aplicación y mientras pasa a través del documento. Como el analizador reporta los eventos mientras visita las diferentes partes del
documento, no tiene que construir una estructura interna. Esto reduce los recursos de sistema necesarios, lo que hace a este analizador
atractivo para grandes documentos. Para documentos XML recibidos como streams continuos, un API basado en eventos es la única
elección.
Por otro lado, el API DOM, sigue una construcción estilo árbol. Los elementos tienen relaciones padre-hijo con otros elementos. Con este
API, el analizador construye una estrucura interna por la que una aplicación puede navegar. DOM permite que una aplicación tenga
acceso aleatorio al documento estructurado como un árbol, y el coste es el incremento de la memoria utilizada.
APIs de XML para Bases de Datos: Lo Básico
A causa de la estructura altamente regular del almacenamiento de datos en una base de datos, podemos mapearla dentro de documentos
XML centrados en datos. Por ejemplo, podemos transformar una tabla de una base de datos en un documento XML con un DTD de la
siguiente forma:
<!ELEMENT table rows*>
<!ELEMENT rows (column1, column2, ...)>
<!ELEMENT column1 #PCDATA>
<!ELEMENT column2 #PCDATA>
....
En otras palabras, con un API XML para base de datos, podemos hacer que la base de datos se parezca a un
documento XML; estos APIs presentan la base de datos como un documento XML virtual. Tenemos los conceptos más básico del
diseño orientado a objetos: es el interface -- no la implementación -- lo que importa. En nuestra situación, las herramientas que usan
dichos APIS XML no necesitan tener cuidado de si están operando con una tabla de una base de datos o con un fichero XML.
Un analizador SAX o DOM puede permitir a las herramientas XML trabajar directamente con bases de datos.
Implementar el API SAX para Bases de Datos
Para implementar el API SAX para Bases de Datos, necesitamos implementar un analizador que opere sobre una fuente de datos
JDBC, iterar sobre cada fila y columna, y generar los eventos apropiados mientras iteramos. La especificación SAX proporciona la
clase org.xml.sax.InputSource que modela la fuente de datos representando una URL o un stream
de bytes. Para representar una base de datos, necesitamos una forma especializada que pueda representar una tabla de una base
de datos. Por lo tanto implementamos JDBCInputSource, que extiende la clase
org.xml.sax.InputSource . Echemos un vistazo en más detalle a la clase
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);
}
}
En el código de arriba, el constructor toma la información necesaria para conectar con la base de datos y el nombre de la tabla a
analizar. El método getConnection() conecta con la base de datos y devuelve un objeto
Connection.
Luego, necesitamos implementar el analizador SAX que usa JDBCInputSource para iterar sobre
las filas y columnas de la tabla de la base de datos y generar eventos SAX. Para simplificar el código, hemos creado una clase
abstracta ParserBase, que implementa org.xml.sax.Parser
y tiene responsabilidad sólo para manejar varios controladores. Después creamos nuestro analizador SAX para la fuente JDBC
JDBCSAXParser que extiende la clase ParserBase.
(Aquí puedes ver el código de ParserBase.java.)
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;
}
}
Examinemos el código en más detalle. JDBCSAXParser incluye varios metodos
parse() sobrecargados. En la lista de abajo, el interface
org.xml.sax.Parser requiere que se implementen los métodos
parse(InputSource) y parse(String). Los otros métodos
parse() simplifican el código y permiten a las clases derivadas sobreescribirlos para modificar el
comportamiento del analizador.
- El método parse(InputSource) llama al método
parse(JDBCInputSource) si el argumento es del tipo JDBCInputSource;
de otro modo lanza una SAXException ya que no puede tratarlo.
- El método parse(String) lanza una SAXException si la información
suministrada no es suficiente para acceder a la base de datos.
- El método parse(JDBCInputSource) obtiene un objeto Connection
de la fuente de entrada y ejecuta una consulta para obtener un objeto ResultSet. Luego llama a
parse(ResultSet) con este objeto.
- El método parse(ResultSet, String) realiza la lógica principal del análisis. Itera sobre cada fila de
la hoja de resultados y sobre cada columna de las filas. El bucle de iteracción de filas está rodeado por llamadas a
startElement() y endElement() con un marcador de tabla como argumento
element-name. Similarmente, cada bucle de iteracción de columna está rodeado por llamadas a
startElement() y endElement() con un marcador de fila como el argumento
element-name. En ambos casos una lista de atributo vacía se pasa como el segundo argumento a los métodos
startElement(). En cada visita a una columna, se llama al método
generateSAXEventForColumn() con argumentos column-name y column-value. Se accede al valor de una
columna mediante el método getString() sobre el objeto result-set,
porque necesitamos una representación string de los datos de la columna para que sean notificados en el evento SAX
characters().
- El método de conveniencia parse(String, String, String, String) simplemente crea un objeto
JDBCInputSource con los argumentos pasados y luego llama al método
parse(JDBCInputSource) con él.
Los métodos protected de JDBCSAXParser ofrecen algunas posibilidades de
personalización a través de su sobreescritura:
- El método generateSAXEventForColumn() genera eventos para los datos de columna. Un valor null
para una columna en una base de datos tiene diferentes significados para una columna con string vacío. Capturamos la diferencia no
disparando ningún evento para las columnas que tienen un valor null. Otra elección para representar un valor null en una base de datos
es usar un atributo binario como isNull. Con esta opción, un valor true
será configurado para datos nulos; de otro modo será false.
- Los métodos getTableMarker(), getRowMarker(), y
getColumnMarker() devuelven valores por defecto razonables para los marcadores de tabla, fila y
columna. Las clases derivadas podrían sobrescribirlos para proporcionar marcas personalizadas.
- El método getSelectorSQLStatement() devuelve un string
"select * from <tableName>". Las clases derivadas pueden sobreescribirlo para proporcionar una
consulta select diferente para ofrecer nivel de filtrado ala base de datos.
La clase de conveniencia JDBCSAXUtils proporciona dos métodos para crear un
JDBCInputSource: puede hacerse desde un fichero de propiedades o desde un objeto
Property. No necesitamos suministrar una larga lista de parámetros que describan una base de datos a una
aplicación que use los APIs SAX o DOM para bases de datos. La clase espera que el usuario suministre un fichero apropiado que contenga
entradas para una URL de la base de datos, un nombre de usuario y una password para conectar con la base de datos, un driver JDBC para
establecer la conexión, y un nombre de tabla. El código de abajo muestra un fichero de propiedades típico:
URL=jdbc:odbc:db1
user=jw
password=jw-passwd
table=portfolio
driver=sun.jdbc.odbc.JdbcOdbcDriver
La historia hasta ahora ...
Ahora tenemos un sencillo analizador que puede generar los eventos SAX apropiados para la información de una tabla de la base de datos.
Tiene cuidado con los datos nulos y ofrece alguna personalización de los marcadores. Aunque dichas funcionalidades podrían parecer
suficientes para algunas aplicaciones, la solución completa considerará funcionalidades adicionales porque:
- El analizador no incorpora información relacional. Esto puede resolverse usando un XPointer/XLink para seleccionar la referencia a una
clave exterior en una tabla.
- Una columna de texto en una base de datos podría contener datos marcados. Un analizador SAX para bases de datos también debería analizar
aquellos datos y generar los eventos SAX apropiados. Si dicha funcionalidad es importante para una aplicación, podría
sobreescribir generateSAXEventForColumn() y analizar el contenido de la columna y generar eventos
SAX adicionales.
- En bases de datos, una tabla contiene una lista desordenada de columnas; el orden en que se almacenan las columnas no es importante.
Por otro lado, un DTD XML, no tiene forma de describir una colección de elementos hijos desordenados.
Podemos tratar este problema de varias formas. Si la tarea es para convertir una base de datos en otro documento XML, digamos una página
HTML, la hoja de estilo XSLT escrita para este propósito puede crear la salida en el orden correcto. También podríamos sobreescribir el
método getSelectorSQLStatement() para suministrar una lista explícita de columnas en el orden
correcto.
- Es deseable para presentar sólo una parte seleccionada de una tabla como un documento basado en alguna consulta. Como las
herramientas pueden hacer filtrado, las bases de datos son mejores para ello. Se puede sobreescribir el método
getSelectorSQLStatement() para que devuelva el string de la consulta apropiada.
- El analizador usa el método getString() del objeto result-set para obtener la representación string del
valor de una columna. Esto funciona bien con texto, números, etc, pero no funciona bien con datos binarios. Aunque los datos binarios
puedan ser representados como texto, podrían no ser apropiados para ciertas tareas. El analizador tampoco trata con tipos definidos por
el usuario disponibles con SQL3/JDBC 2.0.
Podemos resolver ambos problemas sobreescribiendo el método generateSAXEventForColumn()
y proporcionando una implementación adecuada.
Implementar el API DOM para Bases de Datos
Para construir un árbol DOM para una tabla de base de datos, podríamos iterar sobre las filas y columnas y construir nodos para un
árbol mientras los visitamos. O podríamos emplear otra librería, como la implementación JAXP de Sun, que construye un árbol DOM
desde un stream de eventos SAX. La última aproximación es más simple, y requiere menos codificación porque reutiliza una facilidad
existente. Para implementar el API DOM usando dicha aproximación sólo necesitamos una reutilización adecuada del analizador SAX
para bases de datos que implementamos anteriormente.
La clase JDBCDOMParser.java implementa el API DOM
para bases de datos:
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();
}
}
La implementación de la clase JDBCDOMParser es sencilla. Usa la clase
XmlDocumentBuilder proporcionada por JAXP para construir un documento DOM desde un stream de eventos
SAX. JDBCDOMParser tiene sólo un método: createDocument(), que toma
una fuente de datos JDBC como argumento. El método crea un JDBCSAXParser y selecciona un analizador
para el objeto XmlDocumentBuilder. Luego dispara el analizador y devuelve el documento resultante desde
el objeto XmlDocumentBuilder. Internamente el objeto XmlDocumentBuilder
responde a los eventos SAX generados por el objeto JDBCSAXParser para construir un documento DOM.
Usar el API SAX para Base de Datos
Ya hemos visto un ejemplo de uso del API SAX para bases de datos para implementar un API DOM para bases de datos. Ahora veremos otro
ejemplo de uso del API SAX para bases de datos. En esta sección, lo usaremos para integrarlo con XT -- un procesador XSLT escrito en
Java. Con dicha integración, podemos aplicar una hoja de estilos XSLT directamente a los documentos XML virtuales almacenados en una
base de datos.
Hemos envuelto la lógica de creación de una fuente SAX para una fuente de base de datos dada y la hemos procesado con la hoja de
estilo XSLT dada para producir un fichero de salida en la clase JDBCXSLProcessor, que está basada en
com.jclark.xsl.sax.Driver de XT. El método principal toma tres argumentos: un fichero de propiedades de una
base de datos, un fichero de hoja de extilo XSLT y un fichero de salida.
Como veremos abajo, podemos usar esta aproximación para generar páginas HTML directamente sin incurrir en la penalización de tener
que crear el fichero XML intermedio. Además, veremos cómo usar la integración del API SAX para bases de datos y XT para convertir
una base de datos en un fichero XML no virtual.
(Aquí puedes ver el código fuente de DBCXSLProcessor.java)
Generar páginas HTML directamente desde una base de datos usando una hoja de estilo XSLT
Aquí vemos una sencilla hoja de estilo que formatea un documento XML altamente regular representado por una tabla de base de datos.
La tabla de la base de datos es formateada como una tabla HTML. Se puede usar la hoja de estilo genérica
createTable.xsl para transformar cualquier documento XML con una estructura parecida a una tabla. La
hoja de estilo usa nombres de marcas para columnas como cabeceras de tablas.
(Aquí puedes ver el código de la hoja de estilo createTable.xsl
Convertir una base de datos a XML con una hoja de estilo XSLT
Aunque la mayoría de las aplicaciones funcionan con los APIs SAX o DOM, podríamos necesitar obtener un fichero XML en algunas
situaciones. Por ejemplo, necesitamos un documento XML para usar una herramienta que no funciona con ninguno de estos APIs. Aquí,
sugerimos una forma de convertir una base de datos en un fichero XML. Con esta aproximación, escribimos una hoja de estilo XSLT para
hacer la transformación de identidad. Usando dicha hoja de estilo con el API SAX para bases de datos se construirá un documento XML que
representa una tabla en la base de datos. Hemos proporcionado una hoja de estilo -- identity.xsl --
para transformación de identidad que funciona con la implementación actual de 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>
Observa que aunque una hoja de estilo XSLT realiza fácilmente la transformación de identidad, no es muy eficiente y va a través de la
lógica compleja de propósito general para aplicar toda la hoja de estilo. Esta ineficacia puede ser un problema para tablas de bases de
datos con un gran número de registros. Una alternativa es escribir una aplicación que realice la transformación de identidad. Dicha
aplicación escucharía los eventos SAX y crearía elementos XML y datos en respuesta a esos eventos, resultando en un documento
XML que representa la tabla de la base de datos.
Usar el API DOM para Bases de Datos
Para la mayoría de las situaciones, el API SAX para bases de datos es más eficiente que el API DOM. Sin embargo, algunas aplicaciones
necesitan acceso aleatorio a documentos XML y por lo tanto requieren la estructura en forma de árbol que ofrece el API DOM para
bases de datos.
Integrar el API DOM para Bases de Datos con el Procesador XQL
XML Query Language (XQL), un lenguaje de consultas para documentos XML, tiene una síntaxis similar a los patrones Xpath. Aquí,
hemos integrado nuestro analizador DOM para bases de datos con GMD-IPSI's XQL Engine. Con dicha integración, podemos realizar
consultas al estilo SQL sobre el documento XML que representa la tabla de la base de datos.
Como un ejemplo de integración, proporcionamos un sencillo shell para consultar el documento XML obtenido de la tabla de la base
de datos. La clase JDBCXQLProcessor crea un entorno "shell", que toma las consultas del usuario
e imprime el documento resultante. El método processQueries() puede trabajar con cualquier
objeto Document -- no sólo con objetos creados por el
JDBCDOMParser. Lee el string de consulta desde System.in, e imprime
el resultado sobre System.out. El método main() crea un objeto
JDBCInputSource a partir de su argumento y lo usa con
JDBCDOMParser para obtener un objeto Document que corresponde
con la tabla de la base de datos.
Aquí tienes el código fuente de JDBCXQLProcessor.java
Como nota lateral, escribir una conversión base-de-datos-a-XML es fácil con XMLWriter y
JDBCDOMParser. Sólo obtenemos el objeto Document desde
JDBCDOMParser y escribimos el fichero deseado con
XMLWriter.write(Document).
Conclusión
En este artículo, hemos explicado el uso de los APIS de XML para bases de datos para atrapar la información residente en bases
de datos. Con estos APIs, podemos evitar el coste asociado de convertir una base de datos en documentos XML y el coste de
mantenerlos sincronizados. Presentamos una implementación Java de los APIs SAX y DOM para bases de datos, que pueden trabajar
con cualquier base de datos con un driver JDBC. Hemos presentado una integración del API SAX para bases de datos con XT. Hemos
ilustrado el uso de dicha integración para crear directamente páginas HTML desde una base de datos y convertir las bases de datos
en documentos XML. Finalmente, hemos integrado el API DOM para bases de datos con un procesador XQL.
Desde aquí puedes bajarte un fichero zip con todos los códigos fuentes de este artículo: jw-01-dbxml.zip