Follow Us on Twitter

XML ondersteuning in PL/SQL

Mei 2003 - De vorige Whitebooks van deze serie over XML gingen over Oracle's XML Developers Toolkit (XDK) en over de XML ondersteuning in de Oracle8i en 9i databases. Dit Whitebook gaat in op een aantal XML functies van PL/SQL (Oracle8i en Orace9i) waarmee XML documenten aangemaakt kunnen worden. 

De volgende onderwerpen worden besproken:

  • de XMLGEN package
  • de XML-SQL utility voor PL/SQL
  • de XMLDOM package

In de voorbeelden gebruiken we de welbekende DEPT als master tabel en EMP als detail tabel. Iedere employee heeft werkt voor exact 1 department. Ieder department heeft geen, 1 of meer employees.

SELECT deptno, dname FROM dept;

DEPTNO     DNAME
---------- --------------
10         ACCOUNTING
20         RESEARCH
30         SALES
40         OPERATIONS

SELECT empno, ename, deptno FROM emp;
EMPNO ENAME  DEPTNO
----- -----  ----------
7369  SMITH  20
7499  ALLEN  30
7521  WARD   30
7566  JONES  20
7654  MARTIN 30
7698  BLAKE  30
7782  CLARK  10
7788  SCOTT  20
7839  KING   10
7844  TURNER 30
7876  ADAMS  20
7900  JAMES  30
7902  FORD   20
7934  MILLER 10 

XMLGEN Package

De eenvoudigste manier om een XML document te maken met PL/SQL is met behulp van de XMLGEN package. De functie getXml() accepteert parameters voor de SQL query en het meta data type (zoals de DTD) en retourneert een CLOB met het XML document.

SELECT xmlgen.getXml
       ('SELECT empno "EMP_NO", ename "NAME", deptno "DEPT_NO"
        FROM emp WHERE deptno = 10'
       , 0
       ) 
FROM dual;
<?xml version = '1.0'?>
<ROWSET>
   <ROW num="1">
      <EMP_NO>7782</EMP_NO>
      <NAME>CLARK</NAME>
      <DEPT_NO>10</DEPT_NO>
   </ROW>
   <ROW num="2">
      <EMP_NO>7839</EMP_NO>
      <NAME>KING</NAME>
      <DEPT_NO>10</DEPT_NO>
   </ROW>
   <ROW num="3">
      <EMP_NO>7934</EMP_NO>
      <NAME>MILLER</NAME>
      <DEPT_NO>10</DEPT_NO>
   </ROW>
</ROWSET>

Het result is een root element genaamd ROWSET, welke een aantal ROW-elementen bevat. Ieder ROW-element heeft een rij volgnummer als attribuut en bevat de elementen EMP_NO, NAME, en DEPT_NO. Merk op dat de namen van de ROW-elementen iets afwijken van de kolom namen van de EMP tabel.

Het gebruiken van de XML-SQL utility (XSU)

De XML-SQL Utility (XSU) biedt een eenvoudige mogelijkheid om gegevens uit een SQL query om te zetten naar XML en vice versa. XSU biedt een goede basis functionaliteit om gegevens middels XML op te halen en wegschrijven naar de database.

DBMS_XMLQUERY en DBMS_XMLSAVE zijn twee packages waarmee XML gegenereerd en opgeslagen kan worden middels XML. De DBMS_XMLSAVE package wordt gebruikt om XML data direct in de database op te slaan. Mogelijke functies zijn insertXML(), updateXML(), en deleteXML(). In deze Whitebook zullen we ons echter voornamelijk focussen op het genereren van XML met behulp van DBMS_XMLQUERY.

Het genereren van XML door de aanroep van de getXML() functie resulteert in een CLOB die het XML document bevat. De zogenaamde " context handle" - die benodigd is in vervolg aanroepen - wordt in de eerste stap gecreëerd.

DECLARE
    queryCtx  dbms_xmlquery.ctxType;
    result    CLOB;
BEGIN
    -- definieer de query context
    queryCtx := dbms_xmlquery.newContext
                ('SELECT empno "EMP_NO", ename "NAME", deptno "DEPT_NO"
                 FROM emp WHERE deptno = :DEPTNO'
                ); 

De ROW en ROWSET tag namen kunnen eenvoudig worden gewijzigd indien de DTD of de XML schema definitie expliciet gedefinieerde tags hebben die afwijken van de kolommen:

    dbms_xmlquery.setRowTag
       (queryCtx, 'EMP');
    dbms_xmlquery.setRowSetTag
      (queryCtx, 'EMPSET'); 

Voordat de query kan worden uitgevoerd moet de waarde van variabele (:DEPTNO) worden doorgegeven (het zogenaamde " binden" van de waarde van de variabele aan de SQL query). Let er op dat de betreffende "bind"  variabelen met een ’:’ beginnen.

    dbms_xmlquery.setBindValue
       (queryCtx, 'DEPTNO', 10); 

Nu kan de query worden uitgevoerd om vervolgens het XML resultaat te verkrijgen als een CLOB. De eenvoudige procedure printClobOut() ondersteunt het printen van de CLOB naar het scherm. Als laatste moet de "query handle" worden afgesloten en vrijgegeven.

    result := dbms_xmlquery.getXml(queryCtx);
   printClobOut(result);
    dbms_xmlquery.closeContext(queryCtx);
END;
/

Het resultaat is als volgt:

<?xml version = '1.0'?>
<EMPSET>
   <EMP num="1">
      <EMP_NO>7782</EMP_NO>
      <NAME>CLARK</NAME>
      <DEPT_NO>10</DEPT_NO>
   </EMP>
<EMP num="2"> <EMP_NO>7839</EMP_NO> <NAME>KING</NAME> <DEPT_NO>10</DEPT_NO> </EMP> <EMP num="3"> <EMP_NO>7934</EMP_NO> <NAME>MILLER</NAME> <DEPT_NO>10</DEPT_NO> </EMP> </EMPSET>

De DBMS_XMLQUERY package biedt nog meer functionaliteit zoals 

  • Pagineren met behulp van setMaxRows() en setSkipRows(). 
  • Het gebruiken van stylesheets met behulp van setStylesheetHeader() en useStylesheet(). De utility genereert eerst het XML document, roept dan de parser aan, past de stylesheet toe, en genereert vervolgens het resultaat hetgeen zeer ten goede komt aan de performance. 

XMLDOM Package

De XMLDOM package implementeert het Document Object Model Interface (DOM Interface) zoals gedefineerd door de W3C XML aanbevelingen. De volgende interfaces zijn o.a. gedefinieerd:

  • DOM Attribute interface 
  • DOM Document interface 
  • DOM DocumentType interface 
  • DOM DOMImplementation interface 
  • DOM Element interface 
  • DOM Node interface 
  • DOM NodeList interface 
  • DOM Notation interface 
  • DOM ProcessingInstruction interface 
  • DOM Text interface 

We zullen met behulp van eenvoudig voorbeeld het gebruik van de XMLDOM package toelichten. Allereerst dienen een aantal speciale privileges aan de gebruiker van onderstaande code te worden gegeven. Een private synonymvereenvoudigt het gebruik van de package. Bijvoorbeeld voor de gebruiker scott:

GRANT javauserpriv to scott;
GRANT javasyspriv to scott;
GRANT EXECUTE ON xmldom TO scott;
CREATE SYNONYM scott.xmldom FOR SYS.xmldom; 

In de declaratie hebben we een aantal DOM references nodig. De DOMDocument handle is de belangrijkste - het wordt gebruikt in de meeste vervolg aanroepen. Vervolgens zijn meerdere DOM node handles benodigd om de refereren aan de hoofd node, de root node, de user node, en de item node voor ieder element. De cursor selecteert de employees voor een bepaald department.

DECLARE
    doc       xmldom.DOMDocument;
    main_node xmldom.DOMNode;
    root_node xmldom.DOMNode;
    user_node xmldom.DOMNode;
    item_node xmldom.DOMNode;
    root_elmt xmldom.DOMElement;
    item_elmt xmldom.DOMElement;
    item_text xmldom.DOMText;
    CURSOR get_users(p_deptno NUMBER) IS
      SELECT empno
      , ename
      , deptno
      , rownum
      FROM emp
      WHERE deptno = p_deptno; 

We beginnen met het aanmaken van een nieuwe document handle. Vervolgens maken we de hoofd node voor dit document. Het root element is genaamd EMPSET en wordt als child node aan de hoofd node toegevoegd. De geretourneerde handle wordt gebruikt als root node voor vervolg aanroepen.

BEGIN
    doc       := xmldom.newDOMDocument;
    main_node := xmldom.makeNode(doc);
    root_elmt := xmldom.createElement(doc, 'EMPSET');
    root_node := xmldom.appendChild(main_node, xmldom.makeNode(root_elmt)); 

Voor ieder record uit de query wordt een nieuw element genaamd EMP aangemaakt. Analoog aan het vorige voorbeeld wordt een rij volgnummer toegevoegd als attribuut aan het element. Het element wordt als child node toegevoegd aan de root node. De geretourneerde handle wordt gebruikt als user node voor vervolg aanroepen.

    FOR get_users_rec IN get_users(10) LOOP
        item_elmt := xmldom.createElement
                      (doc
                      , 'EMP'
                      );
        xmldom.setAttribute
                      (item_elmt
                      , 'num'
                      , get_users_rec.rownum
                      );
        user_node := xmldom.appendChild
                      (root_node
                      , xmldom.makeNode(item_elmt)
                      ); 

Nu worden de tekst elementen toegevoegd aan het DOM document. In de eerste stap wordt een nieuwe element genaamd EMP_NO aangemaakt. Dit element wordt toegevoegd als child node aan de user node. In de tweede stap wordt een tekst node aangemaakt welke de record data bevat, in dit geval het employee nummer. Deze tekst node wordt als child node toegevoegd aan de item node.

        item_elmt := xmldom.createElement
                      (doc
                      , 'EMP_NO'
                      );
        item_node := xmldom.appendChild
                      (user_node
                     , xmldom.makeNode(item_elmt)
                      );
       item_text := xmldom.createTextNode
                      (doc
                      , get_users_rec.empno
                      );
        item_node := xmldom.appendChild
                      (item_node
                      , xmldom.makeNode(item_text)
                      ); 

Hetzelfde kan nu gedaan worden voor de tekst elements NAME en DEPT_NO.

Nadat alle records verwerkt zijn en alle gegevens in het DOM document opgenomen zijn, kan het bijvoorbeeld weggeschreven worden en de geallokeerde resources weer worden vrijgegeven:

    END LOOP;
    xmldom.writeToFile(doc, '/tmp/xml/docSample.xml');
    xmldom.freeDocument(doc);
END;
/

Let er op dat de XMLDOM package de XML file op iedere willekeurige plek kan wegschrijven ongeacht de de waarde van de database parameter UTL_FILE_DIR.

De weggeschreven file bevat de volgende regels:

<?xml version = '1.0' encoding = 'UTF-8'?>
<EMPSET>
   <EMP num="1">
      <EMP_NO>7782</EMP_NO>
      <NAME>CLARK</NAME>
      <DEPT_NO>10</DEPT_NO>
   </EMP>
   <EMP num="2">
      <EMP_NO>7839</EMP_NO>
      <NAME>KING</NAME>
      <DEPT_NO>10</DEPT_NO>
   </EMP>
   <EMP num="3">
      <EMP_NO>7934</EMP_NO>
      <NAME>MILLER</NAME>
      <DEPT_NO>10</DEPT_NO>
   </EMP>
</EMPSET> 

Het gebruik van de XMLDOM package kan complex lijken in het begin, maar de structuur van het DOM document is volgens een eenvoudige manier opgebouwd: Ieder element of attribuut moet toegevoegd worden als node aan een parent node. De zelfde aanpak met vergelijkbare methoden wordt gebruikt in de XML implementatie van Java.

Enige beperkingen: 

  • Let er op dat de gehele DOM in het geheugen wordt gehouden totdat het document wordt vrijgegeven. Het totaal aantal nodes is daarom afhankelijk van de configuratie van het database geheugen, bijv. met een JAVA_POOL_SIZE van 10.0000.000 bytes kunnen ongeveer 20.000 nodes worden aangemaakt.
  • Een andere beperking is een geheugen lek in de Java implementatie van de DOM interface en de XML parser. Deze bug is gerapporteerd voor Sun Solaris en wordt verholpen door Oracle 8.1.7.3 en bug fix 2104071 of met Oracle 9.2.

Conclusie

De XMLGEN package kan worden gebruikt voor het genereren van eenvoudige XML documenten. De gegevens worden opgehaald met 1 SQL statement.

XSU heeft een tweetal packages DBMS_XMLQUERY en DBMS_XMLSAVE voor het genereren en opslaan van XML documenten. Extra functionaliteit is voorhanden om waarden van variabelen aan de query te "binden", voor het pagineren van de result set voor het direct toepassen van een stylesheets.

De meest geavanceerde aanpak is met behulp van de XMLDOM package. Deze biedt de optimale ondersteuning voor de DOM interface. Iedere DTD of XML schema definitie kan met behulp van deze package geïmplementeerd worden.

Waardering:
 
Tags:

Reacties

Nieuwe reactie inzenden

De inhoud van dit veld is privé en zal niet openbaar worden gemaakt.

Meer informatie over formaatmogelijkheden

CAPTCHA
Deze vraag is om te testen of u een persoon bent en om spam te voorkomen
Image CAPTCHA
Enter the characters shown in the image.