Logo Oracle Deutschland   Deutschsprachige APEX und PL/SQL Community

REST Services und Application Express 5.1 - Teil 3

Erscheinungsmonat APEX-Version Datenbankversion
Juli 2017 ab 5.1 ab 11.2

Die Anforderung, mit REST Services zu arbeiten, haben auch Application Express-Entwickler mehr und mehr auf dem Schreibtisch. Zwar können Web Service Referenzen für REST Services in den Gemeinsamen Komponenten eingerichtet werden; die Unterstützung ist jedoch recht limitiert: So muss die JSON-Antwort, die der REST Service sendet, typischerweise manuell geparst und verarbeitet werden.

In den vergangenen Wochen konnten Sie bereits mehrere Tipps zum Thema im Rahmen der APEX-Community lesen: So haben Sie erfahren, wie Sie, mit der seit Version 5.1 vorhandenen neuen Packaged Applications REST Client Assistant ...

  • REST Client Assistant:
    Packaged Application REST Client Assistant

    Packaged Application REST Client Assistant

  • Sample REST Services:
    Packaged Application REST Client Assistant

    Packaged Application REST Client Assistant

In den bisherigen Tipps wurden stets alle Daten des REST Service abgerufen - entweder alle auf einmal, oder seitenweise. In der Praxis sind aber oft nur Teilmengen der Daten gefragt: man möchte die Ergebnismenge mit einem Filter einschränken.

"Das ist ja einfach" dürfte nun der allererste Gedanke sein - schließlich findet das JSON-Parsing mit SQL und den SQL-Funktionen XMLTABLE oder JSON_TABLE statt. Beide stellen die Daten so bereit, als ob sie aus einer Tabelle kämen - in der WHERE-Klausel können nun also beliebige SQL-Filter verwendet werden. Nehmen wir dazu das Earthquake-Beispiel aus dem Community-Tipp SQL und PL/SQL Code zum Zugriff auf den REST Service und zum JSON-Parsing erzeugen. Darin erfolgt die Darstellung der REST-Daten als Bericht mit folgender SQL-Abfrage:

select 
    j."TYPE", 
    j."MAG", 
    j."PLACE", 
    :
    j."ID"
from apex_collections c, json_table(
    c.clob001 format json,
    '$.features[*]'
    columns (
        "TYPE"      VARCHAR2(4000)  path '$.type',
        "MAG"       NUMBER          path '$.properties.mag',
        "PLACE"     VARCHAR2(4000)  path '$.properties.place',
        :
        "ID"        VARCHAR2(4000)  path '$.id'
    )
) j
where c.collection_name = 'REST_COLLECTION'

Es ist nun ein leichtes, hier einfach weitere Bedingungen in die WHERE-Klausel aufzunehmen, beispielweise wie folgt:

select 
    j."TYPE", 
    j."MAG", 
    j."PLACE", 
    :
    j."ID"
from apex_collections c, json_table(
    c.clob001 format json,
    '$.features[*]'
    columns (
        "TYPE"      VARCHAR2(4000)  path '$.type',
        "MAG"       NUMBER          path '$.properties.mag',
        "PLACE"     VARCHAR2(4000)  path '$.properties.place',
        :
        "ID"        VARCHAR2(4000)  path '$.id'
    )
) j
where c.collection_name = 'REST_COLLECTION'
  and j.mag             > 2 -- hier sind natürlich auch APEX-Items denkbar, bspw. :P1_MAG

Natürlich lassen sich auch Application Express-Seitenelemente aufnehmen - anhand von Benutzereingaben kann die Antwort des REST-Service auf diesem Wege sehr einfach gefiltert werden. So weit, so gut.

Problematisch wird dies allerdings, wenn die Antwort des REST Service potenziell sehr große Datenmengen zurückliefert - man stelle sich vor, ohne Filter würde eine sechs- oder gar siebenstellige Anzahl an Zeilen zurückgegeben. Viele REST-Services geben die Daten dann seitenweise ab; das wurde im Community-Tipp Größere Datenmengen seitenweise vom REST Service abrufen näher betrachtet. Ginge man wie oben vor, bedeutet das, dass man zunächst alle Daten, über mehrere Seiten hinweg, vom REST Service abrufen würde. Anschließend würde man, per SQL-Filter, das meiste davon wieder verwerfen. Es ist klar, dass dies für den "Erdbeben"-Service (unter 1.000 Zeilen) noch in Ordnung geht, bei größeren Datenmengen ist diese Vorgehensweise allerdings problematisch.

Das Ziel muss sein, den Filter bereits auf Seiten des REST Service anzuwenden. REST Services, die mit Oracle REST Data Services (ORDS) bereitgestellt werden, unterstützen in der Tat eine Filter-Syntax, die dann bereits serverseitig ausgeführt wird.

Diese Filter-Syntax ist allerdings kein allgemeingültiger Standard - es ist also von REST-Service zu REST-Service unterschiedlich, ob eine Filter-Syntax überhaupt unterstützt wird - und wenn ja: wie diese konkret aussieht. So unterstützt der "Erdbeben"-Service keine Filter-Syntax. Im folgenden setzen wir also voraus, dass wir mit einem REST-Service arbeiten, der von Oracle REST Data Services bereitgestellt wird.

Detaillierte Informationen zur Filter-Syntax findet man in der ORDS-Dokumentation. Der Filter wird im JSON-Format als Query-String-Parameter "q" an die URL angehängt. Alternativ kann der REST-Service auch mit der HTTP Methode POST (anstelle von GET) aufgerufen werden - dann wird das Filter-JSON als Request-Body übergeben. Dazu ein Beispiel: wir nehmen, wie schon im Community Tipp Größere Datenmengen seitenweise vom REST Service abrufen an, dass ein REST-Service die Daten der wohlbekannten Tabelle EMP zurückliefert. Er sei unter der URL http://{server}:{port}/ords/scott/emp/ erreichbar. Die folgenden Codebeispiele rufen das JSON mit APEX_WEB_SERVICE ab und parsen das Ergebnis direkt mit der APEX_JSON-Funktion.

1. Beispiel: Alle Daten werden abgrufen

select 
    j."EMPNO", 
    j."ENAME", 
    j."JOB", 
    j."MGR", 
    to_date( j."HIREDATE", 'YYYY-MM-DD"T"HH  :MI:SS"Z"' ) as "HIREDATE", 
    j."SAL", 
    j."COMM", 
    j."DEPTNO"
from json_table(
    apex_web_service.make_rest_request(
        'http://{server}:{port}/ords/scott/emp/',
        'GET' ),
    '$.items[*]'
    columns (
        "EMPNO"      NUMBER          path '$.empno',
        "ENAME"      VARCHAR2(4000)  path '$.ename',
        "JOB"        VARCHAR2(4000)  path '$.job',
        "MGR"        NUMBER          path '$.mgr',
        "HIREDATE"   VARCHAR2(4000)  path '$.hiredate',
        "SAL"        NUMBER          path '$.sal',
        "COMM"       NUMBER          path '$.comm',
        "DEPTNO"     NUMBER          path '$.deptno'
    )
) j
/

EMPNO  ENAME   JOB        MGR   HIREDATE             SAL   COMM  DEPTNO  
------ ------- ---------- ----- -------------------- ----- ----- ------
7369   SMITH   CLERK      7902  17.12.1980 08:00:00  800         20      
7499   ALLEN   SALESMAN   7698  20.02.1981 08:00:00  1600  300   30      
7521   WARD    SALESMAN   7698  22.02.1981 08:00:00  1250  500   30      
7566   JONES   MANAGER    7839  02.04.1981 08:00:00  2975        20      
7654   MARTIN  SALESMAN   7698  28.09.1981 07:00:00  1250  1400  30      
7698   BLAKE   MANAGER    7839  01.05.1981 07:00:00  2850        30      
7782   CLARK   MANAGER    7839  09.06.1981 07:00:00  2450        10      
7788   SCOTT   ANALYST    7566  09.12.1982 08:00:00  3000        20      
7839   KING    PRESIDENT        17.11.1981 08:00:00  5000        10      
7844   TURNER  SALESMAN   7698  08.09.1981 07:00:00  1500  0     30      
7876   ADAMS   CLERK      7788  12.01.1983 08:00:00  1100        20      
7900   JAMES   CLERK      7698  03.12.1981 08:00:00  950         30      
7902   FORD    ANALYST    7566  03.12.1981 08:00:00  3000        20      
7934   MILLER  CLERK      7782  23.01.1982 08:00:00  1300        10    

14 rows selected.

2. Beispiel: Nur DEPTNO 30 soll abgrufen werden.

select 
    j."EMPNO", 
    j."ENAME", 
    j."JOB", 
    j."MGR", 
    to_date( j."HIREDATE", 'YYYY-MM-DD"T"HH  :MI:SS"Z"' ) as "HIREDATE", 
    j."SAL", 
    j."COMM", 
    j."DEPTNO"
from json_table(
    apex_web_service.make_rest_request(
        'http://{server}:{port}/ords/scott/emp/?q=' ||
            utl_url.escape( '{"deptno": { "$eq": 30 } }' ),
        'GET' ),
    '$.items[*]'
    columns (
        "EMPNO"      NUMBER          path '$.empno',
        "ENAME"      VARCHAR2(4000)  path '$.ename',
        "JOB"        VARCHAR2(4000)  path '$.job',
        "MGR"        NUMBER          path '$.mgr',
        "HIREDATE"   VARCHAR2(4000)  path '$.hiredate',
        "SAL"        NUMBER          path '$.sal',
        "COMM"       NUMBER          path '$.comm',
        "DEPTNO"     NUMBER          path '$.deptno'
    )
) j
/

EMPNO  ENAME   JOB       MGR   HIREDATE             SAL   COMM  DEPTNO  
------ ------- --------- ----- -------------------- ----- ----- ------
7499   ALLEN   SALESMAN  7698  20.02.1981 08:00:00  1600  300   30      
7521   WARD    SALESMAN  7698  22.02.1981 08:00:00  1250  500   30      
7654   MARTIN  SALESMAN  7698  28.09.1981 07:00:00  1250  1400  30      
7698   BLAKE   MANAGER   7839  01.05.1981 07:00:00  2850        30      
7844   TURNER  SALESMAN  7698  08.09.1981 07:00:00  1500  0     30      
7900   JAMES   CLERK     7698  03.12.1981 08:00:00  950         30    

6 rows selected.

3. Beispiel: Nur Zeilen mit einem SAL zwischen 2000 und 4000, aber keine SALESMEN.

select 
    j."EMPNO", 
    j."ENAME", 
    j."JOB", 
    j."MGR", 
    to_date( j."HIREDATE", 'YYYY-MM-DD"T"HH  :MI:SS"Z"' ) as "HIREDATE", 
    j."SAL", 
    j."COMM", 
    j."DEPTNO"
from json_table(
    apex_web_service.make_rest_request(
        'http://{server}:{port}/ords/scott/emp/?q=' ||
            utl_url.escape( '
                {"$and": [ 
                    { "sal": { "$between": [ 2000, 4000 ] } }, 
                    { "job": { "$ne": "SALESMAN" } } 
                ] }' ),
        'GET' ),
    '$.items[*]'
    columns (
        "EMPNO"      NUMBER          path '$.empno',
        "ENAME"      VARCHAR2(4000)  path '$.ename',
        "JOB"        VARCHAR2(4000)  path '$.job',
        "MGR"        NUMBER          path '$.mgr',
        "HIREDATE"   VARCHAR2(4000)  path '$.hiredate',
        "SAL"        NUMBER          path '$.sal',
        "COMM"       NUMBER          path '$.comm',
        "DEPTNO"     NUMBER          path '$.deptno'
    )
) j
/

EMPNO  ENAME   JOB       MGR   HIREDATE             SAL   COMM  DEPTNO  
------ ------- --------- ----- -------------------- ----- ----- ------
7499   ALLEN   SALESMAN  7698  20.02.1981 08:00:00  1600  300   30      
7521   WARD    SALESMAN  7698  22.02.1981 08:00:00  1250  500   30      
7654   MARTIN  SALESMAN  7698  28.09.1981 07:00:00  1250  1400  30      
7698   BLAKE   MANAGER   7839  01.05.1981 07:00:00  2850        30      
7844   TURNER  SALESMAN  7698  08.09.1981 07:00:00  1500  0     30      
7900   JAMES   CLERK     7698  03.12.1981 08:00:00  950         30   

5 rows selected.

Der entscheidende Unterschied zum weiter oben gezeigten Filtering in der SQL WHERE-Klausel ist, dass der Filter hier auf Seite des REST Service angewendet wird - es werden also von vorneherein nur die Daten übertragen, die auch wirklich von Interesse sind. So bald es um größere Datenmengen geht, ist die Nutzung serverseitiger Filter absolut notwendig. Das bedeutet aber auch, dass es einen REST-Service braucht, der solche Filter unterstützt.

Wie die Nutzung der ORDS Filter-Syntax in einer Application Express-Anwendung aussehen kann, ist in der Packaged Application Sample REST Services illustriert.

Die Beispielanwendung Sample REST Services illustriert die Übergabe eines Filters an den REST Service

Abbildung 3: Die Beispielanwendung Sample REST Services illustriert die Übergabe eines Filters an den REST Service

Natürlich erlaubt ORDS die Kombination des Abfragefilters mit der im früheren Commumity-Tipp vorgestellten Pagination Syntax. Sie können nun also einen Application Express-Bericht erzeugen, mit dem man nicht nur durch die Antwort des REST-Service blättern kann - vielmehr lassen sich nun auch Filter des Anwendungs an den REST-Service übergeben. Experimentieren Sie ruhig ein wenig damit.

zurück zur Community-Seite