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:
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:
oracle.xml.sql.query.OracleXMLQuery
: API para generación de
documentos XMLoracle.xml.sql.dml.OracleXMLSave
: API para guardar, insertar,
actualizar y borrarOracleXMLQuery
,
son los siguientes:
//importar el driver de Oracle import oracle.jdbc.driver.*; //cargar el driver JDBC de Oracle DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver()); //crear la conexión Connection conn= DriverManager.getConnection ("jdbc:oracle:thin:@www.rvg.ua.es:1521:j2eebd","schema","passwd");
OracleXMLQuery
proporcionando un string
SQL o un objeto ResultSet
import oracle.xml.sql.query.OracleSMLQuery; OracleXMLQuery qry = new OracleXMLQuery (conn, "select * from emp*");
//para obtener el resultado como un string String xmlString = qry.getXMLString;
//para obtener el resultado como un árbol DOM org.w3c.DOM.Document domDos = qry.getXMLDOM();
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>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
<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>
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=?
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:
oracle.xdb
: en donde se define la clase XMLType
createXML() |
Crea un XMLType. Usar este método cuando se accede a los datos a través de JDBC |
getStringVal() |
Recupera un valor String conteniendo los datos XML del XMLType correspondiente |
extract() |
Extrae un conjunto dado de nodos desde XMLType |
existsNode() |
Comprueba la existencia de un conjunto dado de nodos en XMLType |
transform() |
Transforma MLType usando un documento XSL |
isFragment() |
Comprueba si XMLType es un documento o un fragmento de él |
getDOM() |
Recupera el documento DOM asociado con el XMLType |
oracle.xdb.dom
: implementa las interfaces definidas por org.w3c.dom
XMLDocument()
representa el DOM del documento XML correspondiente.
Se puede recuperar el XMLType
asociado mediante la función getXMLType()
de la clase XMLDocument
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:
getStringVal()
en SQL y devolviendo el resultado como
java.lang.String
en Java:
... OraclePreparedStatement stmt = (OraclePreparedStatement) conn.prepareStatement( "e.poDoc.getStringVal() poString "+ " from po_xml_tab e"); ResultSet rset = stmt.executeQuery(); OracleResultSet orset = (OracleResultSet) rset; while(orset.next()){ // the first argument is a string.. String poString = orset.getString(1); }
getOPAQUE()
en PreparedStatement
para obtener la toda la instancia XMLType
, y usar el constructor
XMLType
. A continuación se pueden usar las funciones Java sobre
la clase XMLType
para acceder a los datos
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; // get the XMLType XMLType poxml = XMLType(orset.getOPAQUE(1)); // get the XML as a string... String poString = poxml.getStringVal();
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:
INSERT/UPDATE/DELETE
,
y usar el constructor XMLType
dentro de la sentencia SQL:
OraclePreparedStatement stmt = (OraclePreparedStatement) conn.prepareStatement( "update po_xml_tab set poDoc = XMLType(?) "); // the second argument is a string.. String poString = "<PO><PONO>200</PONO><PNAME>PO_2</PNAME></PO>"; // now bind the string.. stmt.setString(1,poString); stmt.execute();
setObject()
(o setOPAQUE()
)
en PreparedStatement
para determinar la instancia XMLType
:
import oracle.xdb.XMLType; ... OraclePreparedStatement stmt = (OraclePreparedStatement) conn.prepareStatement( "update po_xml_tab set poDoc = ? "); // the second argument is a string String poString = "<PO><PONO>200</PONO><PNAME>PO_2</PNAME></PO>"; XMLType poXML = XMLType.createXML(conn, poString); // now bind the string.. stmt.setObject(1,poXML); stmt.execute();
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); }
}
}