X

Technologie - Trends - Tipps&Tricks
in deutscher Sprache

JSON nativ auch mit Oracle JDBC 20c

Marcel Boermann-Pfeifer
Principal Solution Engineer

Aktuell als Beta-Version in unserer Cloud zum Test verfügbar bietet Oracle Database 20c neben vieler anderer Features einen neuen JSON-Datentyp an, der den bisherigen Umgang mit JSON-Dokumenten weiter vereinfacht und darüber hinaus Vorteile bei Platzverbrauch und Performance bieten kann. Auch Java-Programmierer profitieren vom neuen JSON-Datentyp, denn der neueste Oracle Database 20c JDBC Treiber ermöglicht eine transparente Einbindung bestehender JSON APIs für Java. Bestehende JSON-Datenobjekte können somit ohne großen Programmier- und auch ohne Konvertierungsaufwand leicht in die Datenbank übernommen werden.

Der neue JSON-Datentyp in Oracle Database 20c

Es gibt viele Blogs, die bereits über den neuen JSON-Datentyp in Oracle Database 20c berichten. Deswegen will ich hier nur kurz darauf eingehen als Mittel zum Zweck - um zu erklären, was "unter der Haube" und damit im JDBC Treiber geschieht.
Ein kurzes SQL-Beispiel für den neuen JSON-Datentyp in Oracle Database 20c könnte wie folgt aussehen:

CREATE TABLE EMP (empData JSON);

Hier wird eine neue Tabelle angelegt mit nur einer Spalte, die JSON- Dokumente enthält - im echten Leben bitte immer mit einer Primärschlüssel-Spalte. In Datenbank Versionen vor 20c konnten JSON- Dokumente in CLOB,BLOB und STRING Spalten abgelegt werden, ein Check-Constraint sorgte dafür, daß die Spalte inhaltlich als JSON betrachtet wurde und somit JSON-spezifische Operationen darauf ausgeführt werden konnten. Die interne Speicherung der JSON- Dokumente erfolgte vor Version 20c noch als Text bzw. String, also noch nicht optimiert.
Und damit stoßen wir auf den ersten großen Unterschied zum neuen JSON-Datentyp mit Oracle 20c: die Datenbank legt JSON-Dokumente nun in einem internen Binär-Format ab, das schneller gelesen und interpretiert werden kann und auch Platz spart, sowohl beim Speichern als auch bei der Übertragung über das Netzwerk.
Dieses Binärformat trägt den Namen "OSON", in Anlehnung an die Namen Oracle und JSON. Die JSON-Strukturen werden also nicht bei Ablage in Objekt-Strukturen übersetzt; diese wären wahrscheinlich zu unflexibel gewesen, zu starr in ihrer Struktur.

Nach Erzeugung einer Tabelle mit JSON-Datentyp kann auf die Spalte direkt mit SQL samt JSON-Hierarchien zugegriffen werden. Ein JSON- Dokument läßt sich direkt einfügen, z.B.:

INSERT INTO EMP VALUES (
   JSON
      {'name': 'Smith',
       'job': 'Programmer'}
);

Auch Abfragen auf JSON-Dokumente sind mit Version 20c direkt möglich, ohne Sonderbehandlung der betroffenen Spalten durch "treat as json":

SELECT t.data.name.string()
FROM EMP t
WHERE t.data.job.string() = 'Programmer';

Eine vergleichbare Abfrage in vorhergehenden Versionen wie 18c,19c sähe ein klein wenig komplexer aus:

with json_doc as
          (select treat (empData as json) as jsontreat from emp)
   select j.jsontreat.name 
     from json_doc j
    where j.jsontreat.job = 'Programmer';


Das interne OSON-Format

Gelangen wir nun zu des "Pudels Kern", zum JDBC Treiber und wie JSON-Datentypen darin behandelt werden. Kurz gesagt und vorweggenommen kann dieser Treiber das interne OSON-Format direkt verwenden bzw. erzeugen, so daß die Datenbank diesen Schritt nicht übernehmen muß und die Daten bereits optimiert durch das Netz übertragen werden. Je größer das JSON-Dokument, desto größer die Ersparnis im OSON-Format. Hier ist eine kleine Größen-Übersichts-Tabelle meiner Kollegen aus dem Product Management zu sehen:

Einen spürbaren Performancegewinn erzielt die Oracle Datenbank gegenüber der bloßen Speicherung im Text-Format durch den internen Aufbau von OSON. Attributnamen verschwinden und werden durch Pointer ersetzt, zusätzliche Metadaten über die relative Position der Werte von Attributen ermöglichen schnelleren Zugriff durch Überspringen und direkten Zugriff. Und natürlich fehlen dann auch alle Klammern und Quotes.

So wird beispielsweise aus einem JSON-Dokument der Form:

{"name":"Smith","job":"Programmer","salary":40000} 
Eine interne Repräsentation singemäß ähnlich dieser:
Metadaten: "name"->0, "salary"->1, "job"->2
Offsets: [0->0 , 1->17, 2->7]
Werte: Smith,Programmer,4000

Auf diese Weise werden JSON-Abfragen aber auch -Änderungen spürbar wenn nicht gar drastisch schneller. Hierzu zwei Diagramme meiner Kollegen aus dem Product Management zu Queries und Updates von JSON Dokumenten:




Der JDBC Treiber 20c

Aus JSON-Tabellenspalten können über den Treiber direkt JSONP und JSONB Objekte gefüttert und gelesen werden, d.h. bereits bestehender Java Code muß nicht erst umständlich nach JSON-Text serialisiert werden. Der Zugriff auf JSON-Spalten erfolgt dabei völlig Norm-Konform und ohne augenscheinlichen proprietären Code.

Das Auslesen der JSON-Spalte erfolgt zunächst wie gehabt durch ein SELECT-Statement, eingebettet in eine entsprechende Java Klasse:

     ResultSet rs = stmt.executeQuery("SELECT empData FROM EMP");

Dann werden die Datensätze eingelesen und die JSON-Spalte über einen getObject() Aufruf geholt. Ein Parameter des getObject-Aufrufs ist das Zielformat bzw. der Zieltyp, in den das JSON eingelesen werden soll. Und dies ist die Besonderheit am neuen JDBC Treiber - es gibt mehrere mögliche vordefinierte Zieltypen, die das interne Speicherformat optimal überführen und weitere Codierung, Parsing, Konvertierung überflüssig machen.

      rs.next();
      javax.json.JsonObject smith = rs.getObject(1, javax.json.JsonObject.class);

In diesem Beispiel wird direkt ein "javax.json.JsonObject" zurückgegeben, d.h. der Treiber erzeugt aus dem internen Format ein Objekt, das man direkt nach Java Standard JSR-374 (JSON-P) benutzen kann um Attribute auszulesen und zu ändern.

String jobName = smith.getString("job");

Neben JsonObject sind einige weitere solcher Datentyp-Mappings im JDBC Treiber hinterlegt. Dieselbe JSON-Spalte kann problemlos in folgende Typen übertragen werden:
 

Zieltyp Variante Zweck
java.lang.String
java.io.Reader
java.io.InputStream
JSON als Text String-Verarbeitung,
Eingabe-Stream für diverse JSON Parser
oracle.sql.json.OracleJsonValue
javax.json.JsonValue
JSON als Baum-Modell Verwendung per JSON-P API, Oracle-Eigenes Baum-Modell
oracle.sql.json.OracleJsonParser
javax.json.stream.JsonParser
JSON als Event-Modell / Parser Vorbereitung für JSON-B API, d.h. einfaches Binding von Java Klassen an JSON
oracle.sql.json.OracleJsonDatum
			
pures OSON Für eigene Zwecke / Debugging


Richtig gesehen, der JDBC Treiber kommt mit einer eigenen API zur Verarbeitung von JSON daher. Damit ließe sich weiter Platz sparen, weitere JSON APIs könnten weggelassen werden, wenn man sich auf OracleJsonValue bzw. OracleJsonObject beschränkte.
Eine vollständige Liste aller im Treiber enthaltenen Klassen ist auch für Version 20c bereits online verfügbar und kann hier eingesehen werden:
https://docs.oracle.com/en/database/oracle/oracle-database/20/jajdb/index.html
Dies gilt auch für die gesamte Dokumentation der Datenbank Version 20c mit allen neuen Features:
https://docs.oracle.com/en/database/oracle/oracle-database/20/index.html
 

JSON-P, JSON-B und Jackson

Neben dem bisher angedeuteten Mapping von OSON in ein JsonObject gemäß der JSONP Spezifikation kann das eingelesene Objekt auch direkt an eine reguläre Java Klasse, ein POJO, gebunden werden. Dafür existiert die Java Spezifikation JSR-367, JSON-B oder auch JSON-Binding API. Diese setzt auf JSON-P auf (der API für JSON-Verarbeitung oder auch -processing) und benötigt für die Kopplung an ein beliebiges Java Objekt (POJO) einen JsonParser statt eines JsonObjects. Das sieht sinngemäß wie folgt aus:

      rs.next();
      javax.json.stream.JsonParser parser = rs.getObject(1, javax.json.stream.JsonParser.class);
      org.eclipse.yasson.YassonJsonb jsonb = (org.eclipse.yasson.YassonJsonb)JsonbBuilder.create();
      EmpPojo e = jsonb.fromJson(parser, EmpPojo.class);
      String jobName = e.getJob();

Hier bedient sich der Programmierer aber eines kleinen Tricks: die JSONB Referenz-Implementierung YASSON kennt das Binding von einem JsonParser an ein POJO. In der Spezifikation selbst ist dieses Mapping leider nicht enthalten. Deswegen wird hier der JsonbBuilder in den YassonJsonb gecastet.
 

Ältere JSON Projekte verwenden vermutlich die Jackson API, um aus JSON Objekten Java Klassen zu erzeugen bzw. zu befüllen. Jackson bietet ebenfalls JSON Parser und ein JSON Binding, kann aber lediglich Brücken zu JSONP und JSONB schlagen und akzeptiert selbst keine JsonParser oder JsonObjects nach JSR Standard. Trotzdem kann aus einem JSON-Datentyp in Oracle via Jackson ein Java Objekt/POJO entstehen. Allerdings muß dazu das OSON Format tatsächlich nach Text bzw. in einen Stream konvertiert werden. Aber es funktioniert auch in diesem Fall:
 

      rs.next();
      java.io.Reader smith = rs.getObject(1, java.io.Reader.class);
      com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
      EmpPojo emp = objectMapper.readValue(smith, EmpPojo.class);
      String jobName = emp.getJob();

Vollständige Beispiele für JDBC 20c mit JSONB, JSONP und Jackson können auf https://github.com/ilfur/oraJDBC_JSON eingesehen und heruntergeladen werden.
 

Viel Erfolg und Spaß beim Testen und Benchmarking !

 

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.