Aus Linux-Magazin 10/2001

DB2 und Java

Eine JDBC-Schnittstelle gehört heute zur Standardausrüstung einer relationalen Datenbank. Im Gegensatz zu vielen Konkurrenten bietet aber DB2 weit mehr an Java-Unterstützung. Grund genug, einen genaueren Blick auf die Funktionalitäten zu werfen.

Im Linux-Magazin 9/01 (siehe [1]) haben wir IBMs relationale Datenbank DB2 im Detail beschrieben. Schwerpunkte waren dabei Funktionsumfang und Installation. Die Sicht der Anwendungsentwickler kam hier zum Zug. Nach einer Beschreibung der Pflichtübung, dem Zugriff über JDBC, sehen wir uns jetzt Techniken wie SQLJ, User Defined Functions und Stored Procedures an.

Die Schritte zur Installation sind im Kasten “Die Installation von DB2” nochmals kurz dargestellt, die detaillierte Beschreibung finden Sie in der vorigen Ausgabe des Linux-Magazins. Als Beispiel dient eine Testdatenbank mit dem Namen Jdbctest. Der Kasten “Erstellen einer DB2-Datenbank” erklärt die notwendigen Schritte. Dabei wurde auf Tuning verzichtet, um die Sache nicht unnötig kompliziert zu machen.

Variationen in JDBC

IBM bietet zwei Treiber für den Zugriff mittels JDBC auf DB2-Datenbanken an, den so genannten Application-Driver und den Net-Driver. Ersterer verwendet das Java Native Interface (JNI), um auf die lokale Client-Runtime von DB2 zuzugreifen. Die Abbildung 1 zeigt schematisch, wie es funktioniert. Der Nachteil dieser Architektur ist – außer dem Implementierungsaufwand für eine neue Plattform -, dass auf dem Client eine komplette DB2-Runtime installiert sein muss. Der Vorteil ist, dass der Zugriff auf DB2 mit genau dieser hoch optimierten Runtime erfolgt, also auch ihre Funktionalitäten vorhanden sind und nicht nur ein nach Java portiertes Subset.

Der JDBC-Treiber heißt »COM.ibm.db2. jdbc.app.DB2Driver«, die entsprechende URL für die Testdatenbank lautet »jdbc: db2:jdbctest«. Der Name der Datenbank ist in diesem Fall ein Aliasname, wie er im lokalen Database-Directory definiert ist. Die erforderliche Java-Funktionalität ist in der Datei »db2java.zip« zusammengefasst, die sich im CLASSPATH befinden muss.

Der Net-Driver »COM.ibm.db2.jdbc.net. DB2Driver« dagegen braucht keine lokale DB2-Installation, sondern greift über einen speziellen Server-Daemon (»db2jd«) auf die Datenbank zu (siehe Abbildung 2). Der Serverprozess wird auf dem Datenbankserver unter Angabe der Portnummer mittels

# db2jstrt 5555

gestartet. Der Daemon »db2jd« verwendet die DB2-Client-Runtime für den Serverzugriff, er könnte also auch auf einem dedizierten Rechner laufen, solange die eigentliche Datenbank in seinem Directory katalogisiert ist.

Im Gegensatz zum App-Driver ist die Funktionalität des Net-Drivers etwas eingeschränkt, so wird etwa das Java Transaktion API (JTA) nicht unterstützt oder der Aufbau einer Connection verlangt unbedingt einen User mit einem Passwort. Der App-Driver ermöglicht im Gegensatz dazu auch einen quasi anonymen Zugriff. Bei Verwendung des Net-Drivers sollten die Lizenzbestimmungen beachtet werden: Obwohl nur ein Prozess auf die Datenbank zugreift, ist eine Lizenz pro Client oder die Enterprise-Edition erforderlich.

Der Net-Driver ist insbesondere für die Verwendung innerhalb von Java-Applets aus Browsern heraus gedacht. Hierbei können auch die notwendigen Klassen zusammen mit dem Applet heruntergeladen werden:

<applet code="MyApplet.class" archive=Ó "db2java.zip">
</applet>

Ein Applet darf ja keine Verbindung zu einem anderen Server als dem Quell-Webserver aufbauen, in diesem Fall läuft also »db2jd« auf demselben Server wie der Web-Daemon.

Mein generisches Testprogramm [2], das ich für den Vergleich der Java-SQL-Datenbanken InstantDB und Hypersonic entwickelt und im letzten Coffee-Shop [3] beschrieben habe, ist inzwischen für beide DB2-Driver-Varianten erweitert worden. Der Java-Code musste dafür im Prinzip nicht geändert werden, einzig ein Konfigurationsskript mit den Driver- und URL-Namen wurde angepasst.

Abbildung 1: Die DB2-App-Driver-Architektur.

Abbildung 1: Die DB2-App-Driver-Architektur.

Abbildung 2: Die DB2-Net-Driver-Architektur.

Abbildung 2: Die DB2-Net-Driver-Architektur.

Abbildung 3: Umwandlung von SQLJ-Code.

Abbildung 3: Umwandlung von SQLJ-Code.

Die Alternative: SQLJ

JDBC verwendet dynamisches SQL, das heißt: Die Statements werden erst bei der Programmausführung an die Datenbank geschickt und dort ausgeführt. Das erlaubt auch ein weitgehend datenbankunabhängiges Programmieren, wenn auf Spezialfunktionen des DB-Herstellers verzichtet wird.

Dynamisches SQL ist zwar einfach zu programmieren, hat aber einen großen Nachteil: Die Datenbank kann nicht im Voraus den Zugriff auf die Daten optimieren. Für klassische Sprachen stellen die DB-Hersteller deshalb Präprozessoren bereit, die proprietären Quellcode gleichzeitig in normalen Quellcode und in datenbankspezifische Packages verwandeln (siehe Abbildung 3). Das Package wird anschließend automatisch oder manuell an die Datenbank gebunden. Das Anwendungsprogramm greift später über dieses Package auf die Datenbank zu.

Da in vielen Programmen oft statisches SQL verwendet wird (etwa »INSERT«-Statements gefüllt mit Werten aus einer Maske), bietet hier dynamisches SQL keine Vorteile. Es ist nur notwendig, wenn tatsächlich die Inserts, Updates oder Queries dynamisch erstellt werden. Der Nachteil des zusätzlichen Präprozessor-Schritts ist bei Verwendung eines Makefile leicht zu verkraften.

Statisches SQL ist aber nicht notwendigerweise immer performanter, insbesondere wenn sich die Daten seit dem Binden stark verändert haben, könnten die dynamisch aufbereiteten Statements sogar bessere Zugriffspfade haben.

Das statische SQL heißt unter Java SQLJ [4]. Hierbei sind Datenbankhersteller und nicht Sun die treibenden Kräfte. SQLJ ist ein Ansi-Standard, damit sollte es einfach möglich sein, den Quellcode auf verschiedene SQLJ-fähige Datenbanken zu portieren.

In Listing 1 ist ein Ausschnitt aus einem SQLJ-Programm zu sehen. Hier wird ein Insert durchgeführt, wobei das normale »INSERT«-Statement über so genannte Host-Variablen gefüllt wird, sie sind durch den Doppelpunkt gekennzeichnet. Der SQLJ-Präprozessor (unter DB2 naheliegenderweise »sqlj« genannt) wandelt den Code in normalen Java-Quellcode um, der anschließend ganz normal kompiliert werden kann (siehe Listing 2). Gleichzeitig wird ein serialisiertes Objekt einer Hilfsklasse generiert (im Beispiel in der Datei »SQLJTest_SJProfile0.ser«). Sie dient als Grundlage für die Erzeugung des Packages, muss aber auch zur Laufzeit im richtigen Java-Package vorliegen, im Beispiel also im »de/bablokb /jdbc/«-Verzeichnis.

Bei statischem SQL ist noch zu beachten, dass alle verwendeten Datenbank-Objekte auch existieren. Das »INSERT«-Statement in Listing 1 verweist zum Beispiel auf die Tabelle »FileInfo«. Existiert sie nicht, kann der SQLJ-Installer den Code nicht an die Datenbank binden. »CREATE«- und »DROP-Statements sind hier unproblematisch, da sie auf Systemtabellen operieren.

Für den Java-Aufruf in Listing 2 gibt es auch einen Wrapper (»db2profc«), der allerdings nicht die Classic-VM selektiert. Und die Hotspot-Engine steigt bei mir mit der Bitte aus, einen Bugreport an Sun zu schicken.

Anstatt der Angabe »package« in den »prepoptions« kann auch »bind« angegeben werden. Dann wird eine so genannte Bind-Datei erzeugt, die später in einem separaten Schritt an die Datenbank gebunden wird. Dieses Vorgehen ist realistischer, da während der Entwicklung gegen eine Test-DB gearbeitet und das Bind-File später von einem Administrator an die produktive Datenbank gebunden wird.

Listing 1: SQLJTest.sqlj

021: package de.bablokb.jdbc;
023: import java.io.*;
024: import java.sql.*;
025: import sqlj.runtime.*;
026: import sqlj.runtime.ref.*;
027:
035: public class SQLJTest {
148:
149:   private void insertStatic(String filename, int size, Timestamp modtime,
150:                                String username, String groupname, int links, String perms)
151:   throws Exception {
152:   #sql { INSERT INTO FileInfo
153:                       (filename,size,modtime,username,groupname,links,perms)
154:           VALUES(:filename,:size,:modtime,:username,:groupname,:links,:perms)
155:        };
156:   }
169: }

Serverseitiges Java mit UDFs und Stored Procedures

JDBC und SQLJ dienen der Datenbank-Programmierung auf Client-Seite. DB2 bietet aber außerdem serverseitiges Java in Form von User Defined Functions (UDF) und so genannten Stored Procedures an. Beide lassen sich zwar auch mit anderen Sprachen implementieren, wegen der Mächtigkeit von Java mit seinen vielen APIs ist diese Sprache aber in den meisten Fällen eine gute Wahl.

Anwendungscode in der Datenbank abzulegen hat eine ganze Reihe von Vorteilen. Zum einen wird natürlich die Rechenlast von einem Client auf einen potenziell mächtigen Server verlegt. Bei den heute teilweise üblichen Desktop-Clients ist zwar sehr viel Rechenpower vorhanden, ist der Client aber ein Webserver, sieht die Rechnung schon wieder anders aus. Außerdem reduziert serverseitiges Java in einigen Fällen den Netzverkehr, etwa wenn eine UDF in einer »WHERE«-Bedingung die Daten entsprechend filtert. Weitere Argumente sind auch die Wiederverwendung von Code sowie der quasi objektorientierte Ansatz, bei dem die Daten und die zu ihrer Verarbeitung notwendigen Methoden eine Einheit bilden.

Vor der Verwendung von serverseitigem Java sind noch ein paar Konfigurationseinstellungen vorzunehmen. Die Dokumentation verlangt explizit das JDK 1.1.8 von IBM, ob es auch mit einer anderen Version geht, habe ich zwar nicht getestet, aus Einträgen der DB2-Java-Seite [5] vermute ich aber, dass es auch mit moderneren Versionen geht.

Der Pfad zur Java-Installation wird mit dem »update-databasemanager-configuration«-Befehl gesetzt:

> db2 update dbm cfg using jdk11_pathÓ  /usr/jdk118

Danach ist ein Neustart der DB2-Instanz notwendig. Es ist weiterhin darauf zu achten, dass die Java-Libs vom dynamischen Linker zu finden sind – entweder müssen symbolische Links aus einem Standardverzeichnis (etwa »/usr/lib«) gesetzt oder die Datei »/etc/ld.so.conf« angepasst werden.

Listing 2: Umwandlung SQLJ-Code und Package-Generierung

01: Precompile:
02: ==========
03:
04: > sqlj -url=jdbc:db2:jdbctest -compile=false SQLJTest.sqlj
05:
06:
07: Package erstellen:
08: =================
09:
10: > java -classic COM.ibm.db2.sqlj.DB2SQLJInstaller 
11:       -url=jdbc:db2:jdbctest 
12:       -user=db2user -password=foo 
13:       -prepoptions="package using SQLJTest" SQLJTest_SJProfile0
14:
15:
16: Package Info:
17: =============
18:
19: > db2 list packages
20:
21:                      Bound     Total                          Isolation
22: Package    Schema    by        sections      Valid   Format   level     Blocking
23: ---------- --------- --------- ------------- ------- -------- --------- --------
24: SQLJTEST   DB2USER   DB2USER               2 Yes     3        CS        U
25:
26:   1 record(s) selected.

Eine UDF in Java

Als Beispiel soll hier die Erzeugung einer UDF dienen. In der Beispieldatenbank aus [2] werden Ausgaben des Kommandos »find(1)« abgelegt, unter anderem auch die Permission-Bits (zwar als String, aber oktal kodiert). Die UDF mit dem Namen »isExecutable« soll hier den Wert »1« zurückliefern, wenn die Datei ausführbar ist, ansonsten »0«. Die Implementation ist in Listing 3 zu sehen. Die letzten drei Zeichen des Strings werden dabei in ein Integer umgewandelt und mit einer Maske verglichen.

Es gibt zwei Möglichkeiten, UDFs zu definieren. Die modernere Möglichkeit verwendet SQLJ-konforme Verfahren. Hier ist es nötig, eine JAR-Datei zu erzeugen und an die Instanz zu binden. Die traditionelle Alternative implementiert das Interface »COM.ibm.db2.app.UDF« und muss gewisse Konventionen einhalten, ansonsten muss sich die erzeugte Klasse nur im CLASSPATH der Instanz wiederfinden. Listing 3 zeigt die Implementation dieser Variante. Wenig interessant ist das zweite Argument der Methode »isExecutable()«: Es ist nur vorhanden, um der Aufrufkonvention Genüge zu tun. Das eigentliche Ergebnis wird über die »set()«-Funktion (in Zeilen 40 und 42) gesetzt.

Die Funktion muss der Datenbank natürlich noch bekannt gemacht werden. Das dazu notwendige Kommando ist in Listing 4 zu sehen. Details zur Syntax sind in der umfassenden Dokumentation zu finden. Anschließend kann ein Query-Aufruf die Funktion nutzen:

> db2 'SELECT * FROM FileInfo WHEREÓ  isExecutable(perms) = 1'

Die Installation von DB2

Es geht um dies Szenario: Ein Rechner (Hostname »arcturus«) im Testnetz fungiert als Datenbankserver. Hier wird die Datenbank-Engine der Enterprise Edition installiert. Auf einen Client (Hostname »sirius«) kommt die Personal Developers Edition von DB2. Die Installation erfolgt entweder menügeführt über das DB2-eigene Installationsprogramm »db2setup« oder mit einer Response-Datei. Die Details sind in [1] nachzulesen.

Der Datenbank-Server muss der Client-Runtime bekannt sein. Technisch erledigt das ein Eintrag im internen Katalog aller DB2-Instanzen (lokal oder remote). Für die Katalogisierung erfolgt vorbereitend auf dem Client ein Eintrag in die Datei »/etc/services«:

db2prod 50000/tcp

Der Wert »db2prod« ist ein beliebiger, eindeutiger Name in der Client-»/etc/services«. Der Wert in der Server-»/etc/services« spielt keine Rolle. Der Wert »50000/tcp« gibt Port und Protokoll (stets TCP) wieder. Hier ist derselbe Port wie bei der Instanz-Installation auf dem Server zu wählen.

Damit lässt sich anschließend der DB-Server katalogisieren. Auf dem Client wird dazu der Befehl

# db2 catalog tcpip node db2pnode remote arcturus server db2prod

ausgeführt. Der Wert »db2pnode« ist der logische Name der Instanz, dieser wird über den Service-Namen »db2prod« mit dem Port »50000« auf dem Remote-Server »arcturus« verknüpft. Danach steht der DB2-Server auf dem Client zur Verfügung.

Erstellen einer DB2-Datenbank

Eine Datenbank lässt sich über die grafische Oberfläche (das DB2-Control Center) oder mit dem DB2-Command-Line-Processor (CLP) erstellen. Letzteres ist sehr einfach, die Kommandos können entweder in einer Datei gespeichert und anschließend dem CLP übergeben oder aus einer Shell heraus aufgerufen werden. Auf dem Client sind die folgenden Befehle auszuführen:

> db2 attach db2pnode user db2user using foo
> db2 create database jdbctest
> db2 detach db2pnode
> db2 catalog database jdbctest as jdbctest at node db2pnode

Aus Performance-Gründen erzeugt der erste »db2«-Befehl einen Back-End-Prozess, der die Verbindung mit dem DB2-Server offen hält. Der Befehl »attach« sorgt dafür, dass das nachfolgende Kommando auf dem Server ausgeführt wird. Danach wird die Datenbank erstellt. »create database« hat sehr viele Optionen, ein Blick in die Dokumentation empfiehlt sich also unbedingt.

»Detach« löst die Bindung an den Server wieder, denn der letzte Befehl ist auf dem Client auszuführen. Der »catalog«-Befehl trägt die Remote-Datenbank in das lokale Datenbank-Verzeichnis ein. Hier wäre es auch möglich, lokal einen anderen Alias (»catalog database foo as bar«) zu verwenden.

Die Liste aller DBs im lokalen Verzeichnis erhält man über:

> db2 list database directory

finally{}

Natürlich ist noch deutlich mehr zum Thema Java und DB2 zu sagen als hier angerissen wurde. Die Dokumentation – bestehend aus Guides, References und den Beispielprogrammen – ist aber in diesem Punkt übersichtlich und umfassend, so dass kaum eine Frage offen bleibt, wenn man genug Zeit für ihre Lektüre aufbringt.

IBM versucht zurzeit sehr stark, DB2 als Plattform für E-Business-Anwendungen zu positionieren, insbesondere im Zusammenhang mit seinem Web-Applikations-Server Websphere. Die Java-Integration ist damit zu einem zentralen Gesichtspunkt geworden. Es ist also davon auszugehen, dass diese Funktionalitäten in Zukunft noch weiter ausgebaut werden. Auch die Pflege der aktuellen Versionen von DB2 wird intensiv betrieben. Meiner persönlichen Erfahrung nach gehört DB2 zu den am besten gepflegten Softwareprodukten der IBM im Client-Server-Umfeld.

Auch im nächsten Coffee-Shop bleibe ich dem Thema relationale Datenbanken und Java treu. Es wird um Java Data Objects (JDO) gehen. Damit soll die objektorientierte Java-Welt mit den Tabellen der relationalen DBs auf einfache Weise verheiratet werden. (uwo)

Listing 3: UdfLib.java

21: package de.bablokb.udf;
23: import COM.ibm.db2.app.*;
24:
25: public class UdfLib extends UDF {
27:   private final static int EXEC_MASK = 0111;
28:
33:   public void isExecutable(String perms, int result)  throws Exception {
34:     try {
35:       perms = perms.trim();
36:       if (perms.length() == 4)
37:         perms = perms.substring(1);
38:       int intPerms = Integer.parseInt(perms,8);
39:       if ((intPerms & EXEC_MASK) > 0)
40:         set(2,1);
41:       else
42:         set(2,0);
43:     } catch (Exception e) {
44:       setSQLmessage(e.getMessage().substring(0,69));
45:       throw e;
46:     }
47:   }
48: }

Listing 4: Registrierung einer Java-UDF

01: CREATE FUNCTION isExecutable ( CHAR(4) )
02:   RETURNS int
03:   EXTERNAL NAME 'de.bablokb.udf.UdfLib!isExecutable'
04:   LANGUAGE java
05:   PARAMETER STYLE db2general
06:   DETERMINISTIC FENCED
07:   NOT NULL CALL
08:   NO SQL
09:   NO EXTERNAL ACTION
10:   NO SCRATCHPAD
11:   NO FINAL CALL
12:   ALLOW PARALLEL
13:   NO DBINFO;

Der Autor

Bernhard Bablok arbeitet bei der AGIS mbH (Allianz Gesellschaft für Informatik Service mbH) als Systemprogrammierer im Bereich Systems Management. Wenn er nicht Musik hört, mit dem Fahrrad oder zu Fuß unterwegs ist, beschäftigt er sich mit Themen rund um Objektorientierung. Er ist unter coffee-shop@bablokb.de erreichbar.

Infos

[1] In der Bundesliga: IBMs Datenbank DB2, Linux-Magazin 9/2001, S. 108ff.

[2] JDBC-Beispielprogramm: [http://www.bablokb.de/jdbc/]

[3] Coffee-Shop: Bank in der Westentasche – Kleine Datenbanksysteme in Java, Linux-Magazin 9/2001, S. 118ff.

[4] SQLJ-Homepage: [http://www.sqlj.org/]

[5] Infos zu DB2 und Java: [http://www.ibm.com/software/data/db2/java/]

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben