4. XML y Bases de Datos

XML proporciona un marco estándar para intercambio de datos entre múltiples aplicaciones. Concretamente, XML ha sido asociado mayormente a aplicaciones en Internet debido a que el contenido de un documento XML es simplemente texto, lo cual facilita el intercambio de documentos a través de los distintos protocolos de Internet, sistemas operativos y "cortafuegos" (firewalls). Por otro lado, las aplicaciones que manejan datos, se benefician del uso de bases de datos (BD), para almacenar, consultar y modificar dichos datos. Es por ello que se están realizando esfuerzos para poder manejar documentos XML almacenados en una base de datos.

Concretamente, vamos a centrarnos en las soluciones proporcionadas por Oracle, utilizando el lenguaje Java. Oracle permite utilizar documentos XML y bases de datos de dos formas distintas:

En este tema analizaremos las dos aproximaciones.

4.1. Oracle XML SQL Utility (XSU)

La utilidad XSU de Oracle consiste en un conjunto de clases Java que aceptan queries desde las aplicaciones, acceden a la BD mediante JDBC, y devuelven el resultado del query en formato XML. Como proceso complementario, XSU acepta un documento XML que se adapta al esquema de la BD y guarda los datos en función de dicho esquema, tal y como se muestra a continuación.

La estructura del documento XML resultante está basado en la estructura interna de la base de datos, de forma que:

La utilidad XML SQL genera:

Mediante la utilidad XSU podemos realizar lo siguiente:

El mapeado por defecto de SQL a XML (y viceversa), consiste en incluir las filas devueltas por un query SQL en una etiqueta <ROWSET>, que constituye el elemento <ROWSET>. Est elemento es también el elemento raíz del documento XML generado. Dicho elemento <ROWSET> contiene uno o más elementos <ROW>. Cada uno de los elementos <ROW> contiene los datos de una de las filas devueltas de la base de datos. Específicamente, cada elemento <ROW> contiene uno o más elementos cuyos nombre y contenido son los de las columnas de la base de datos especificados en la lista SELECT del query SQL.

Por ejemplo, consideremos la siguiente tabla denominada emp:

CREATE TABLE emp
{ EMPNO NUMBER,
  ENAME VARCHAR2 (20),
  JOB VARCHAR2 (20),
  MGR NUMBER,
  HIREDATE DATE,
  SAL NUMBER,
  DEPTNO NUMBER
};

XSU puede generar el siguiente documento especificando el query select * from emp:

<?xml version='1.0'?>
<ROWSET>
  <ROW num="1">
    <EMPNO>7369</EMPNO>
    <ENAME>Smith</ENAME>
    <JOB>CLERK</JOB>
    <MGR>7902</MGR>
    <HIREDATE>12/17/1980 0:0:0</HIREDATE>
    <SAL>800</sal>
    <DEPTNO>20</DEPTNO>
  <ROW>
  <!-- filas adicionales ... -->
</ROWSET>
};

Las clases Java que implementan la utilidad XSU son:

Los pasos a seguir para generar documentos XML utilizando la clase OracleXMLQuery, son los siguientes:

Ejemplo1. Obtención de un String a partir de una sentencia SQL.

Se trata de recuperar datos de una tabla mediante una sentencia SQL y obtener el resultado como un documento XML, representado por un String.

import oracle.jdbc.driver.*;
import oracle.xml.sql.query.OracleXMLQuery;
import java.lang.*;
import java.sql.*;


class testXMLSQL {

   public static void main(String[] argv)
   {

     try{
      // create the connection
      Connection conn  = getConnection("scott","tiger");

      // Create the query class.
      OracleXMLQuery qry = new OracleXMLQuery(conn, "select * from emp");

      // Get the XML string
      String str = qry.getXMLString();

      // Print the XML output
      System.out.println(" The XML output is:\n"+str);
      qry.close();
     }catch(SQLException e){
      System.out.println(e.toString());
     }
   }

   // Creación de la conexión dado un nombre de usuario y una clave
   private static Connection getConnection(String username, String password)
     throws SQLException
   {
       DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
      // Create the connection using the JDBC driver
       Connection conn =
        DriverManager.getConnection
          ("jdbc:oracle:thin:@www.rvg.ua.es:1521:j2eebd", username,password);
      return conn;
   }
}

Si se quiere generar un árbol DOM en lugar de un String, podemos escribir el siguiente código:

import org.w3c.dom.*;
import oracle.xml.parser.v2.*;
import java.sql.*;
import oracle.xml.sql.query.OracleXMLQuery;
import java.io.*;

 class domTest{

   public static void main(String[] argv)
   {
      try{
       Connection conn  = getConnection("scott","tiger");
       OracleXMLQuery qry = new OracleXMLQuery(conn, "select * from emp");

       // Obtención del documento DOM
       XMLDocument domDoc = (XMLDocument)qry.getXMLDOM();

       // Escritura directamente a partir de DOM
       domDoc.print(System.out);

       // Si se prefiere escribir en un string buffer hacer lo siguiente:
       StringWriter s = new StringWriter(10000);
       domDoc.print(new PrintWriter(s));
       System.out.println(" The string version ---> "+s.toString());

       qry.close(); // Siempre se debe cerrar el query!!
      }catch(Exception e){
        System.out.println(e.toString());
      }
    }
    //getConnection es igual que el de la clase testXMLSQL
    private static Connection getConnection(...){...}
}

Ejemplo 2. Obtención de un documento XML a partir de ResultSets

En este caso, se proporciona un objeto ResultSet a la sentencia OracleXMLQuery. El objeto ResultSet se utiliza para "movernos" por el conjunto de resultados.

public class pageTest()
{
   Connection conn;
   OracleXMLQuery qry;
   ResultSet rset;
   int lastRow = 0;

   public pageTest(String sqlQuery)
   {
     conn  = getConnection("scott","tiger");
     Statement stmt = conn.createStatement(sqlQuery);// create a scrollable Rset
     ResultSet rset = stmt.executeQuery();  // get the result set..
     qry = new OracleXMLQuery(conn,rset);   // create a OracleXMLQuery instance
     qry.keepObjectOpen(true); // Don't lose state after the first fetch
   }
  
   // Returns the next XML page..!
   public String getResult(int startRow, int endRow)
   {
     rset.scroll(lastRow-startRow);  // scroll inside the result set
     qry.setMaxRows(endRow-startRow); // set the max # of rows to retrieve..!
     return qry.getXMLString();
   }

   // Function to still perform the next page. 
   public String nextPage()
   {
     String result = getResult(lastRow,lastRow+10);
     lastRow+= 10;
     return result;
   }

   public void close()
   {
     stmt.close();   // close the statement..
     conn.close();   // close the connection
     qry.close();    // close the query..
   }
  
   public void main(String[] argv)
   {
     pageTest test = new pageTest("select * from emp");

     int i = 0;
     // Get the data one page at a time..!!!!!
     while ((str = test.getResult(i,i+10))!= null)
     {
         System.out.println(str);
         i+= 10;
     }
     test.close();
   }
} 

Ejemplo 3. Inserción de valores en Columnas mediante OracleXMLSave

En este caso la entrada es un fichero XML con etiquetas ROW y ROWSET. Utilizaremos la tabla emp, mostrada anteriormente. Asumimos que proporcionamos los valores solamente para el número de empleado (EMPNO), nombre (ENAME), y trabajo (JOB), y que el resto de campos se "rellenan" automáticamente. Primero creamos una lista de nombres de columna que queremos insertar, y la pasamos a la instancia de OracleXMLSave.

import java.sql.*;
import oracle.xml.sql.dml.OracleXMLSave;
public class testInsert
{
   public static void main(String argv[])
     throws SQLException
   {
      Connection conn = getConnection("scott","tiger");
      OracleXMLSave sav = new OracleXMLSave(conn, "scott.emp");

      String [] colNames = new String[5];
      colNames[0] = "EMPNO";
      colNames[1] = "ENAME";
      colNames[2] = "JOB";

      sav.setUpdateColumnList(colNames); // set the columns to update..!

      // Assume that the user passes in this document as the first argument!
      sav.insertXML(argv[0]);
      sav.close();
   }
   //getConnection es igual que el de la clase testXMLSQL
    private static Connection getConnection(...){...}
}

Se genera una sentencia insert con la forma:

insert into scott.emp (EMPNO, ENAME, JOB) VALUES (?, ?, ?); 

Si el documento insertado contiene valores para el resto de columnas (JOB, HIREDATE,...), éstos se ignoran. Se realiza un insert por cada elemento ROW presente en la entrada. Por defecto, estas inserciones son encoladas.

Ejemplo 4. Actualización de una tabla usando las claves mediante OracleXMLSave

Vamos a actualizar ciertos valores de una tabla. Supongamos que queremos actualizar el salario de un empleado y el departamento en el que trabaja. Se dispone del siguiente documento XML como entrada:

  <ROWSET>
<ROW num="1">
<EMPNO>7369</EMPNO>
<SAL>1800</SAL>
<DEPTNO>30</DEPTNO>
</ROW>
<ROW>
<EMPNO>2290</EMPNO>
<SAL>2000</SAL>
<HIREDATE>12/31/1992</HIREDATE>
<!-- additional rows ... -->
</ROWSET>
Para actualizar los valores correspondientes se debe suministrar a la utilidad XSU la lista de nombres de columnas clave. Éstas forman parte de la cláusula WHERE en la sentencia UPDATE correspondiente. En la tabla anterior, el número de empleado (EMPNO) es el clampo clave. El código para realizar la actualización se muestra a continuación:
import java.sql.*;
import oracle.xml.sql.dml.OracleXMLSave;
public class testUpdate
{
   public static void main(String argv[])
     throws SQLException
   {
      Connection conn = getConnection("scott","tiger");
      OracleXMLSave sav = new OracleXMLSave(conn, "scott.emp");

      String [] keyColNames = new String[1];
      keyColNames[0] = "EMPNO";
      sav.setKeyColumnList(keyColNames);

      // Assume that the user passes in this document as the first argument!
      sav.updateXML(argv[0]);
      sav.close();
   }
   //getConnection es igual que el de la clase testXMLSQL
    private static Connection getConnection(...){...}
}

En este ejemplo, se generan dos sentencias UPDATE. Para el primer elemento ROW, se genera una sentencia UPDATE para actualizar los campos SAL y JOB como sigue:

update scott.emp SET SAL = 1800 and DEPTNO = 30 WHERE EMPNO = 7369; 

Para el segundo elemento ROW:

update scott.emp SET SAL = 2000 and HIREDATE = 12/31/1992 WHERE EMPNO = 2290; 

Ejemplo 5. Borrado de valores clave especificados mediante OracleXMLSave

Para realizar borrados a partir de documentos, se puede especificar la lista de columnas clave. Dichas columnas se usan en cláusulas WHERE de sentencias DELETE statement. Si no se proporciona una lista de columnas clave, entoces se crea una nueva sentencia DELETE para cada elemento ROW del documento XML, en donde la lista de columnas en la cláusula WHERE se instancia con las proporcionadas en el elemento ROW.

En este caso, vamos a proporicionar como entrada el documento XML utilizado para el Ejemplo 4. Pretendemos utilizar la sentencia DELETE únicamente con valores clave como predicados, para lo cual utilizamos la función setKeyColumnList.

import java.sql.*;
import oracle.xml.sql.dml.OracleXMLSave;
public class testDelete
{
   public static void main(String argv[])
     throws SQLException
   {
      Connection conn = getConnection("scott","tiger");
      OracleXMLSave sav = new OracleXMLSave(conn, "scott.emp");

      String [] keyColNames = new String[1];
      keyColNames[0] = "EMPNO";
      sav.setKeyColumnList(keyColNames);

      // Assume that the user passes in this document as the first argument!
      sav.deleteXML(argv[0]);
      sav.close();
   }
   //getConnection es igual que el de la clase testXMLSQL
    private static Connection getConnection(...){...}
}
En este caso se genera una única sentencia DELETE con la forma:
DELETE FROM scott.emp WHERE EMPNO=? 

4.2. Oracle XML DB

La mayor parte de los datos de las aplicaciones y contenidos Web se almacenan en bases de datos relacionales, sistemas de ficheros, o una combinación de ambos. A medida que el volumen de datos XML transportados aumenta, el coste de regenerar estos documentos XML se incrementa y los métodos de almacenamiento se vuelven menos efectivos para contener los datos XML. Oracle XML DB es una tecnología nativa para almacenamiento y recuperación de documentos XML. El objetivo es integrar las ventajas de la tecnología de una base de datos relacional con la tecnología XML.

Para facilitar el manejo nativo de datos XML en la base de datos Oracle se utiliza el tipo de datos XMLType, pudiendo elegir entre almacenar el documento XML como una columna XMLType, o como una tabla XMLType.

Java proporciona un API para manejar XMLTYpe mediante queries SQL. Se pueden crear tablas y columnas XMLType, así como consultar y recuperar datos de cualquier documento XML, independientemente de cómo haya sido almacenado en la BD. Dicho API está formado por dos paquetes:

Para acceder a datos XMLType usando JDBC se debe usar la clase oracle.xdb.XMLType, concretamente el método createXML():

import oracle.xdb.XMLType; 
            ... 
   DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
   Connection conn = 
    DriverManager.getConnection
         ("jdbc:oracle:thin:@www.rvg.ua.es:1521:j2eebd","schema","passwd");

   OraclePreparedStatement stmt = (OraclePreparedStatement) 
                     conn.prepareStatement("select e.poDoc from po_xml_tab e"); 
       ResultSet rset = stmt.executeQuery(); 
       OracleResultSet orset = (OracleResultSet) rset; 

   while(orset.next())
        { 
       // get the XMLType 
       XMLType poxml = XMLType.createXML(orset.getOPAQUE(1)); 
       // get the XMLDocument as a string... 
   Document podoc = (Document)poxml.getDOM(); 

Se puede seleccionar el tipo de datos XMLType en JDBC de dos maneras distintas:

Se puede utilizar también el método getObject() para obtener directamente XMLType a partir del ResultSet:

import oracle.xdb.XMLType;
...

 OraclePreparedStatement stmt = 
    (OraclePreparedStatement) conn.prepareStatement(
          "select e.poDoc from po_xml_tab e");

ResultSet rset = stmt.executeQuery();
OracleResultSet orset = (OracleResultSet) rset;
while(orset.next()) {

// get the XMLType
XMLType poxml = (XMLType)orset.getObject(1);

// get the XML as a string...
String poString = poxml.getStringVal();
}

La actualización/Inserción/Borrado de datos XMLType usando JDBC también se puede realizar de dos formas:

Ejemplo 6. Actualización del elemento discount dentro de la orden de compra, almacenada en una columna XMLType. Para localizar el elemento se utiliza una expresión basada en XPath. Se actualiza el árbol DOM en memoria y se escribe el XML actualizado en la columna correspondiente.

//CLASSPATH debe contener classes12.zip, xmlparserv2.jar, y oraxdb.jar

import java.sql.*;
import java.io.*;
import oracle.xml.parser.v2.*;
import org.xml.sax.*;
import org.w3c.dom.*;
import oracle.jdbc.driver.*;
import oracle.sql.*;
import oracle.xdb.XMLType;
public class tkxmtpje
{
static String conStr = "jdbc:oracle:oci8:@";
static String user = "scott";
static String pass = "tiger";
static String qryStr =
"SELECT x.poDoc from po_xml_tab x "+
"WHERE x.poDoc.extract('/PO/PONO/text()').getNumberVal()=200";
static String updateXML(String xmlTypeStr)
{
System.out.println("\n===============================");
System.out.println("xmlType.getStringVal():");
System.out.println(xmlTypeStr);
System.out.println("===============================");
String outXML = null;
try{
DOMParser parser = new DOMParser();
parser.setValidationMode(false); parser.setPreserveWhitespace (true);
parser.parse(new StringReader(xmlTypeStr));
System.out.println("xmlType.getStringVal(): xml String is well-formed");
XMLDocument doc = parser.getDocument();
NodeList nl = doc.getElementsByTagName("DISCOUNT");
for(int i=0;i<nl.getLength();i++){
XMLElement discount = (XMLElement)nl.item(i);
XMLNode textNode = (XMLNode)discount.getFirstChild();
textNode.setNodeValue("10");
} //end for
StringWriter sw = new StringWriter();
doc.print(new PrintWriter(sw));
outXML = sw.toString();
//print modified xml
System.out.println("\n===============================");
System.out.println("Updated PurchaseOrder:");
System.out.println(outXML);
System.out.println("===============================");
}
catch ( Exception e )
{
e.printStackTrace(System.out);
}
return outXML;
}
public static void main(String args[]) throws Exception
{
try{
System.out.println("qryStr="+ qryStr);
DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
Connection conn =
DriverManager.getConnection ("jdbc:oracle:thin:@rvg7.i3a.ua.es:j2eebd", user, pass);
Statement s = conn.createStatement(); OraclePreparedStatement stmt;
ResultSet rset = s.executeQuery(qryStr);
OracleResultSet orset = (OracleResultSet) rset;
while(orset.next()){
//retrieve PurchaseOrder xml document from database
XMLType xt = XMLType.createXML(orset.getOPAQUE(1));
//store this PurchaseOrder in po_xml_hist table
stmt = (OraclePreparedStatement)conn.prepareStatement(
"insert into po_xml_hist values(?)");
stmt.setObject(1,xt); // bind the XMLType instance
stmt.execute();
//update "DISCOUNT" element
String newXML = updateXML(xt.getStringVal());
// create a new instance of an XMLtype from the updated value
xt = XMLType.createXML(conn,newXML);
// update PurchaseOrder xml document in database
stmt = (OraclePreparedStatement)conn.prepareStatement(
"update po_xml_tab x set x.poDoc =? where "+
"x.poDoc.extract('/PO/PONO/text()').getNumberVal()=200");
stmt.setObject(1,xt); // bind the XMLType instance
stmt.execute();
conn.commit();
System.out.println("PurchaseOrder 200 Updated!");
} //end while
//delete PurchaseOrder 1001
s.execute("delete from po_xml x"+
"where x.xpo.extract"+
"('/PurchaseOrder/PONO/text()').getNumberVal()=1001");
System.out.println("PurchaseOrder 1001 deleted!");
}
catch( Exception e )
{
e.printStackTrace(System.out); }
}
}