Home ORDIX AG             Dienstleistung             Trainingsshop    Kunden / Referenzen Aktuelles    Kontakt
Home  Pfeil  ORDIX News  Pfeil  2/2006  Pfeil  Datenbanken
suche: 
Dieser Artikel richtet sich an Software-Entwickler, die mit der Datenbank- programmiersprache PL/SQL Web-Services aufrufen wollen.

Glossar

XML
Extensible Markup Language. XML ist eine so genannte Meta-Sprache zur Beschreibung von Dokumenten. Ein Vorteil von XML ist der vereinfachte Austausch von Daten, da XML-Formate in einer strengen Grammatik definiert werden können und so die Implementierung von zuverlässigen Schnittstellen erlaubt.
SOAP
Simple Object Access Protocol ist ein Protokoll, mit dem Daten zwischen Systemen ausgetauscht und Remote Procedure Calls durchgeführt werden können.
JDeveloper
JDeveloper ist eine grafische Entwicklungsumgebung von Oracle für die Programmiersprache Java und die Datenbank-Programmiersprache PL/SQL.
STATIC
Mit dem Schlüsselwort STATIC kann eine Methode in der Programmiersprache Java als eine Klassenmethode deklariert werden. Das besondere an einer Klassenmethode ist, dass diese zu einer Klasse und nicht zu einem Objekt gehört. Außerdem ist im Speicher immer nur eine Kopie einer Klassenmethode vorhanden.

PL/SQL Web-Services (Teil II)

Oracle stellt verschiedene Möglichkeiten zur Verfügung, um mit Web-Services zu kommunizieren. Im ersten Teil (siehe ORDIX News 4/2005) zeigten wir auf, wie existierende PL/SQL-Programme ohne großen Aufwand als Web-Services zur Verfügung gestellt werden können. Dieser Teil skizziert, wie mit der Datenbankprogrammiersprache PL/SQL Web-Services aufgerufen werden können. Es werden einige Alternativen anhand von Beispielen vorgestellt.

Einführung und Motivation

In einer Serviceorientierten Architektur (SOA) ist die Anbindung der einzelnen Systemkomponenten an Web-Services von besonderer Bedeutung. Damit auch eine Datenbank die vielen Vorteile von Web-Services (siehe Teil 1) nutzen kann, stellt Oracle mehrere Möglichkeiten zur Verfügung, um direkt aus der Datenbank heraus Web-Services aufzurufen (siehe Abbildung 1). Grundsätzlich stehen folgende drei Alternativen zur Verfügung, die wir anhand von Programmbeispielen erläutern:

  1. UTL_HTTP PL/SQL-Package
  2. Java Stored Procedure
  3. UTL_DBWS PL/SQL-Package

Abb. 1: Web-Services Call-Out - Die Datenbank als Web-Services Konsument.

A) Alternative mit UTL_HTTP PL/SQL-Package

Die Kommunikation zwischen Client und Server findet bei Web-Services über das HTTP-Standardprotokoll statt. Das HTTP-Standardprotokoll wird in Oracle mit Hilfe des PL/SQL-Packages UTL_HTTP unterstützt.

Dabei besteht die Möglichkeit, aus einem SQL- oder PL/SQL-Programm Daten über das HTTP-Protokoll auszutauschen. Das PL/SQL-Package UTL_HTTP kann also unter anderem auch dazu verwendet werden, Web-Services aus einem PL/SQL-Programm aufzurufen.

Anwendungsbeispiel mit UTL_HTTP

Das Beispielprogramm der Abbildung 2 zeigt, wie aus einem PL/SQL-Programm, ein Web-Service zur Ermittlung der ORDIX Seminarpreise (siehe Teil 1) gestartet wird.

1   set serveroutput on
2   DECLARE
3     TYPE request IS RECORD (
4       method     VARCHAR2(256),
5       namespace  VARCHAR2(256),
6       body       VARCHAR2(32767));
7	 
8     TYPE response IS RECORD (doc xmltype);
9     req  request;
10    resp response;
11	
12    FUNCTION erzeuge_request(method VARCHAR2, namespace VARCHAR2) RETURN request AS
13      req request;
14    BEGIN
15      req.method    := method;
16      req.namespace := namespace;
17      RETURN req;
18    END erzeuge_request;
19	
20    PROCEDURE setzeParameter( req IN OUT NOCOPY request, 
21      name VARCHAR2, type VARCHAR2, value VARCHAR2 ) AS
22    BEGIN
23      req.body := req.body ||
24        '<'||name||' xsi:type="'||type||'">'||value||'';
25    END setzeParameter;
26	
27    PROCEDURE erstelle_envelope(req IN OUT NOCOPY request, 
28      env IN OUT NOCOPY VARCHAR2) AS
29    BEGIN
30      env := '<SOAP-ENV:Envelope
31        xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
32        xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
33        xmlns:xsd="http://www.w3.org/1999/XMLSchema">
34        <SOAP-ENV:Body><'||req.method||' '||req.namespace||'
35        SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'||
36        req.body||'';
37    END erstelle_envelope;
38	
39    FUNCTION starteHttp(req IN OUT NOCOPY request, url VARCHAR2) RETURN response AS
40      env         VARCHAR2(32767);
41      http_req    utl_http.req;
42      http_resp   utl_http.resp;
43      resp        response;
44    BEGIN
45      erstelle_envelope(req, env);
46      http_req := utl_http.begin_request(url, 'POST','HTTP/1.0');
47      utl_http.set_header(http_req, 'Content-Type', 'text/xml');
48      utl_http.set_header(http_req, 'Content-Length', length(env));
49      utl_http.write_text(http_req, env);
50	
51      http_resp := utl_http.get_response(http_req);
52      utl_http.read_text(http_resp, env);
53      utl_http.end_response(http_resp);
54	
55      resp.doc := xmltype.createxml(env);
56      resp.doc := resp.doc.extract('/soap:Envelope/soap:Body/child::node()',
57	                'xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"');   
58      RETURN resp;
59    END starteHttp;
60	
61    FUNCTION holeReturnWert( resp IN OUT NOCOPY response, 
62      name IN VARCHAR2, namespace IN VARCHAR2 ) RETURN VARCHAR2 AS
63    BEGIN
64      RETURN resp.doc.extract('//'||
65        name||'/child::text()', namespace).getstringval();
66      END holeReturnWert;
67	
68    BEGIN
69      req := erzeuge_request(method=>'holePreis', namespace=>'xmlns="urn:pck_seminar"');
70	     
71	  setzeParameter( req  => req, name => 'OrdixSeminar', 
72      type => 'xsd:string', value => 'Java Grundlagen');
73	
74	  resp := starteHttp( req => req, 
75      url => 'http://localhost:8888/seminarpreisews/seminarPreiseService');
76	      
77	  dbms_output.put_line( holeReturnWert( resp => resp, 
78      name => 'return', namespace => 'xmlns:ns1="urn:pck_seminar"') );
79    END;
80 /
Abb. 2: Ein Beispielprogramm mit UTL_HTTP zum Aufruf eines Web-Services. (vergrößern)

Dort wird zuerst mit Hilfe der Prozedur ERSTELLE_ENVELOPE eine SOAP-Nachricht zum Aufruf des Web-Services zusammengestellt. Abbildung 3 zeigt ein Beispiel einer solchen SOAP-Nachricht. Anschließend wird mit der Funktion STARTEHTTP die mit ERSTELLE_ENVELOPE erstellte SOAP-Nachricht über das PL/SQL-Package UTL_HTTP aufgerufen.

<SOAP-ENV:Envelope 
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <SOAP-ENV:Body>
    <holePreis xmlns="urn:pck_seminar" 
      SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
      <OrdixSeminar xsi:type="xsd:string">Java Grundlagen
    <holePreis>
  <SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Abb. 3: Die SOAP-Nachricht zum Aufruf eines Web-Services.

Web-Service Response wird im XMLTYPE abgelegt

Das Ergebnis des Web-Services "ORDIX Seminarpreise" (siehe Abbildung 4) wird in einem Datentyp XMLTYPE abgespeichert. Der Datentyp XMLTYPE steht ab Oracle9i zur Verfügung und ermöglicht die Speicherung von XML-Daten in der Datenbank (siehe "Oracle und XML: Ein besonderer Cocktail" in der ORDIX News 1/2006).

<SOAP-ENV:Envelope 
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <SOAP-ENV:Body>
    <holePreisResponse xmlns="urn:pck_seminar" 
      SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
      <return xsi:type="xsd:decimal">1800
    </holePreisResponse>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Abb. 4: Der Web-Service liefert das Ergebnis als SOAP-Nachricht zurück.

Das besondere an dem Datentyp XMLTYPE ist, dass durch diesen SQL-Operationen auf XML-Inhalte und XML-Operationen auf SQL-Inhalte möglich sind. XMLTYPE liefert außerdem spezielle Funktionen zum Erzeugen, Extrahieren und Indizieren von XML-Daten in einer Datenbank.

Anschließend wird der Rückgabewert des Web-Services - hier der ORDIX Seminarpreis - mit Hilfe der XMLTYPE-Funktion EXTRACT aus der XML-Struktur herausgelöst und ausgegeben.

B) Alternative mit Java Stored Procedure

Eine Java Stored Procedure ist eine mit der Programmiersprache Java erstellte Stored-Procedure. Innerhalb einer Java Stored Procedure besteht die Möglichkeit, einen Web-Service direkt aufzurufen.

Voraussetzungen für einen Web-Service- Zugriff aus einer Java Stored Procedure

Soll aus einer Java Stored Procedure, die sich innerhalb einer Oracle Datenbank befindet, ein Web-Service aufgerufen werden, so müssen zuerst die SOAP-Klassen, die in der Datei ORACLE_HOME\oc4j\soap\lib\soap.jar vorhanden sind, in eine Datenbank unter dem Datenbankbenutzer SYS geladen werden (siehe Abbildung 5).

loadjava -u sys/passwd@orcl10 -r -v -s -grant PUBLIC soap.jar
Abb. 5: Verschiedene SOAP-Klassen in eine Datenbank laden.

Um das SOAP-Protokoll in der Datenbank nutzen zu können, müssen dem entsprechenden Datenbankbenutzer, der den Web-Service aufrufen soll, die PropertyPermission- und SocketPermission-Rechte vergeben werden (siehe Abbildung 6).

exec dbms_java.grant_permission
('USER1', 'SYS:java.util.PropertyPermission', '*', 'read,write');
exec dbms_java.grant_permission
('USER1', 'SYS:java.net.SocketPermission', 'localhost', 'resolve');
Abb. 6: Privilegien für den Java Stored Procedure Eigentümer vergeben.

Java-Stub-Klasse

Außerdem wird eine so genannte Java-Stub-Klasse zum Aufruf des Web-Services benötigt. Eine Java-Stub-Klasse dient als Web-Service-Client und kann z. B. mit Hilfe von JDeveloper (in der "New Gallery" unter "Web Services" und "Web Service Stub/Skeleton") erstellt werden (siehe Abbildung 7).

import oracle.soap.transport.http.OracleSOAPHTTPConnection;
import org.apache.soap.encoding.SOAPMappingRegistry;
import java.math.BigDecimal;
import java.net.URL;
import org.apache.soap.rpc.Call;
import org.apache.soap.Constants;
import java.util.Vector;
import org.apache.soap.rpc.Parameter;
import org.apache.soap.rpc.Response;
import org.apache.soap.Fault;
import org.apache.soap.SOAPException;
import java.util.Properties;

/**
 * Generated by the Oracle JDeveloper 10g Web Services Stub/Skeleton Generator.
 * Date Created: Wed Dec 14 20:51:09 CET 2005
 * WSDL URL: http://localhost:8888/seminarpreisews/seminarPreiseService?WSDL
 */
public class Pck_seminarStub {
  private String _endpoint = "http://localhost:8888/seminarpreisews/seminarPreiseService";
  private OracleSOAPHTTPConnection m_httpConnection = null;
  private SOAPMappingRegistry m_smr = null;

  public Pck_seminarStub() {
    m_httpConnection = new OracleSOAPHTTPConnection();
    m_smr = new SOAPMappingRegistry();
  }

  public static BigDecimal holePreisWebService(String kurs) {
     try {
       Pck_seminarStub stub = new Pck_seminarStub();
       return stub.holePreis(kurs);
     } catch (Exception e) { e.printStackTrace(); }
     return null;
  }

  public BigDecimal holePreis(String param0) throws Exception {
    BigDecimal returnVal = null;
    URL endpointURL = new URL(_endpoint);
    Call call = new Call();
    call.setSOAPTransport(m_httpConnection);
    call.setTargetObjectURI("urn:pck_seminar");
    call.setMethodName("holePreis");
    call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
    Vector params = new Vector();
    params.addElement(new Parameter("param0", String.class, param0, null));
    call.setParams(params);
    call.setSOAPMappingRegistry(m_smr);
    Response response = call.invoke(endpointURL, "urn:pck_seminar/holePreis");
    if (!response.generatedFault()) {
      Parameter result = response.getReturnValue();
      returnVal = (BigDecimal)result.getValue(); }
    else {
      Fault fault = response.getFault();
      throw new SOAPException(fault.getFaultCode(), fault.getFaultString());
    }
    return returnVal;
  }
}
Abb. 7: Das Beispiel einer Java-Stub-Klasse. (vergrößern)

Die Java-Stub-Klasse muss ebenfalls in die Datenbank geladen werden (siehe Abbildung 8). Hierbei ist darauf zu achten, dass die Methoden in der Java-Stub-Klasse, die den Web-Service aufrufen, mit der Eigenschaft STATIC deklariert sind. Denn innerhalb einer Datenbank können nur STATIC Methoden aufgerufen werden.

loadjava -user user1/passwd@orcl10 -resolve -verbose Pck_seminarStub.java
Abb. 8: Die Java-Stub-Klasse in die Datenbank laden.

PL/SQL-Wrapper

Um den Zugriff aus der Datenbankprogrammiersprache PL/SQL auf die Java-Stub-Klasse zu ermöglichen, muss ein so genannter PL/SQL-Wrapper für die innerhalb der Datenbank vorhandenen Java-Stub-Klassen erstellt werden (siehe Abbildung 9). Ein PL/SQL-Wrapper kann wiederum wie eine herkömmliche PL/SQL-Stored Function gestartet werden (siehe Abbildung 10).

CREATE OR REPLACE FUNCTION holePreis(kurs VARCHAR2) 
RETURN NUMBER
AS LANGUAGE JAVA
NAME 'Pck_seminarStub.holePreisWebService(java.lang.String) 
return java.lang.BigDecimal';
/
show errors
Abb. 9: Einen PL/SQL-Wrapper für eine Java-Stub-Klasse erstellen.

SQL> SELECT holePreis('Java Grundlagen') Seminar_Preis FROM dual;

SEMINAR_PREIS
-------------
         1600
Abb. 10: Die Java Stored Procedure holePreis starten.

C) Alternative mit UTL_DBWS PL/SQL-Package

Ab der Oracle Version 10g stellt Oracle für die Kommunikation mit Web-Services ein neues PL/SQL-Package UTL_DBWS zur Verfügung. Mit diesem Package ist es ebenfalls möglich, aus der Datenbankprogrammiersprache PL/SQL Web-Services aufzurufen.

Voraussetzungen für das PL/SQL-Package UTL_DBWS

Um das PL/SQL-Package UTL_DBWS verwenden zu können, muss die Datei UTL_DBWS_jserver.jar in die Datenbank geladen werden.

Die aktuelle UTL_DBWS_jserver.jar Datei kann auf der Oracle Internetseite [1] heruntergeladen werden und z. B. mit dem Programm LOADJAVA in eine Datenbank unter dem Datenbankbenutzer SYS geladen werden (siehe Abbildung 11).

loadjava -u sys/passwd@orcl10 -r -v -f -s -grant public -noverify -genmissing utl_dbws_jserver.jar
Abb. 11: Die Datei utl_dbws_jserver.jar in eine Datenbank laden. (vergrößern)

Außerdem muss für das Package UTL_DBWS ein PUBLIC SYNONYM erstellt werden (siehe Abbildung 12).

CREATE PUBLIC SYNONYM UTL_DBWS FOR SYS.UTL_DBWS;
Abb. 12: Ein Public Synonym für das PL/SQL-Package UTL_DBWS erstellen.

Beispielprogramm mit PL/SQL-Package UTL_DBWS

Der Zugriff auf einen Web-Service erfolgt mit Hilfe des Packages UTL_DBWS (siehe Abbildung 13).

set serveroutput on size 1000000
DECLARE
  v_service             UTL_DBWS.SERVICE;
  v_call                UTL_DBWS.CALL;
  v_service_qname       UTL_DBWS.QNAME;
  v_port_qname          UTL_DBWS.QNAME;
  v_operation_qname     UTL_DBWS.QNAME;
  v_string_type_qname   UTL_DBWS.QNAME;
  v_decimal_type_qname  UTL_DBWS.QNAME;
  v_ret                 ANYDATA;
  v_retx_string         VARCHAR2(100);
  v_retx_len            NUMBER;
  v_params              UTL_DBWS.ANYDATA_LIST;
BEGIN
  -- Service-Instanz erzeugen...
  v_service_qname := UTL_DBWS.TO_QNAME(null, 'pck_seminar');
  v_service       := UTL_DBWS.CREATE_SERVICE(v_service_qname);
  
  -- Call-Instanz erzeugen...
  v_port_qname      := UTL_DBWS.TO_QNAME(null, 'pck_seminarPortType');
  v_operation_qname := UTL_DBWS.TO_QNAME('http://tempuri.org/pck_seminar.wsdl', 'holePreis');
  v_call := UTL_DBWS.CREATE_CALL(v_service, v_port_qname, v_operation_qname);
  
  -- Eigenschaften festlegen...
  UTL_DBWS.SET_TARGET_ENDPOINT_ADDRESS(v_call, 
           'http://localhost:8888/seminarpreisews/seminarPreiseService');
  UTL_DBWS.SET_PROPERTY(v_call, 'ENCODINGSTYLE_URI', 'http://schemas.xmlsoap.org/soap/encoding/');
  
  -- Parameter hinzufuegen...
  v_string_type_qname := UTL_DBWS.TO_QNAME('http://www.w3.org/2001/XMLSchema', 'string');
  UTL_DBWS.ADD_PARAMETER(v_call, 'param0', v_string_type_qname, 'ParameterMode.IN');

  -- Return-Typ festlegen... 
  v_decimal_type_qname := UTL_DBWS.TO_QNAME('http://www.w3.org/2001/XMLSchema', 'decimal');
  UTL_DBWS.SET_RETURN_TYPE(v_call, v_decimal_type_qname);
  
  -- Web-Service starten...
  v_params(0) := ANYDATA.CONVERTVARCHAR('Oracle SQL');
  v_ret := UTL_DBWS.INVOKE(v_call, v_params);  
  DBMS_OUTPUT.PUT_LINE('Preis: ' || v_ret.accessNumber);
END;
/
Abb. 13: Ein Beispielprogramm mit UTL_DBWS. (vergrößern)

Zuerst werden eine Service-Instanz und eine Call-Instanz erzeugt. Diese beiden Instanzen werden benötigt, um Eigenschaften wie unter anderem URL-Adresse, Operationen oder Encodingstyle-URI zu definieren und anschließend den Web-Service aufzurufen.

Außerdem werden Parameter hinzugefügt und der Datentyp des Rückgabewertes definiert. Schließlich werden mit der Funktion UTL_DBWS.INVOKE der vorher definierte Web-Service aufgerufen und die Rückgabewerte ausgegeben.

Vergleich der Alternativen

Bei der Alternative A) mit dem PL/SQL-Package UTL_HTTP handelt es sich um eine reine PL/SQL-Lösung, die mit dem herkömmlichen HTTP-Protokoll realisiert wurde.

Diese Alternative sendet demnach einen HTTP-Request und bekommt als Antwort einen HTTP-Response. Der HTTP-Request und der HTTP-Response beinhalten jeweils eine SOAP-Nachricht im XML-Format.

Dagegen verwenden die beiden anderen Alternativen B) und C) direkt das SOAP-Protokoll, das auf dem HTTP-Protokoll basiert.

Performancetest

Zum Vergleich der hier vorgestellten Alternativen wurde eine Messung der Zugriffszeiten auf den Web-Service "SeminarPreiseService" vorgenommen.

Die folgende Übersicht stellt die durchschnittlichen Zugriffszeiten auf den Web-Service dar. Die Messung zeigt, dass die Alternative A) (mittels UTL_HTTP) den schnellsten Zugriff auf den Web-Service ermöglicht:

Alternative: Durchschnittliche Zugriffszeit auf einen Web-Service in Sekunden:
A) UTL_HTTP 0,04
B) Java Stored Procedures 0,07
C)UTL_DBWS 0,23

Resümee

In diesem Beitrag wurde aufgezeigt, wie mit der Datenbankprogrammiersprache PL/SQL auf Web-Services zugegriffen werden kann. Es wurden drei Alternativen vorgestellt, die alle zum Aufruf eines Web-Services aus einer Oracle Datenbank verwendet werden können. Die Alternative A) ermöglicht dabei den schnellsten Zugriff auf einen Web-Service.

Da das PL/SQL-Package UTL_DBWS noch über viele undokumentierte Funktionen verfügt und erst ab der Oracle Version 10g vorhanden ist, sollte es mit Vorsicht eingesetzt werden. Vor allem bei den Performancetests benötigt UTL_DBWS die längsten Zugriffszeiten auf den Web-Service.

Die beiden Varianten UTL_HTTP und Java Stored Procedure können zudem bereits in den Vorversionen von Oracle10g eingesetzt werden.

Markus Fiegler (info@ordix.de).