Performant arbeitende Java-Applikationen zu erstellen, ist eine Wissenschaft für sich. Ist gar der Zugriff auf eine Datenbank erforderlich, ist eine weitere Komponente zu berücksichtigen: Java Database Connectivity (JDBC) Function Calls. Werden einige grundlegende Richtlinien berücksichtigt, lassen sich JBDC-fähige WebSphere-Anwendungen deutlich beschleunigen. Compiler-Warnungen bei schlecht geschriebenem Code existieren nicht. Zudem sind auch keine Datenbank-Tools auf dem Markt verfügbar, die Datenbank-Requests ermitteln, die das gesamte System langsamer machen. Daher ist die Optimierung von Datenbank-Zugriffen ein sehr ehrgeiziges Vorhaben. Halten sich Entwickler jedoch an bestimmte Vorgaben, lassen sich sehr effiziente, unter IBM WebSphere laufende JDBC-Anwendungen erstellen.

Verwendung von Datenbank-Metadaten

Die oberste Regel ist der zurückhaltende Umgang mit Datenbank-Metadaten: Auch wenn es nahezu unmöglich ist, in einer WebSphere-Applikation keine Datenbank-Metadaten zu verwenden, kann die Performance dennoch deutlich durch einen wohl dosierten Einsatz derartiger Methoden verbessert werden. Um alle benötigten Information einer Ergebnisspalte zusammenzutragen, muss ein JDBC-Treiber möglicherweise eine Reihe von Queries, Subqueries und Joins ausführen. Die Abarbeitung dieser Serie von SQL-Abfragen braucht Zeit. Abhilfe schafft hier die Zwischenspeicherung von Ergebnissen.

Auch mit Null-Argumenten und Suchmustern sollte sehr vorsichtig umgegangen werden. Der ungezügelte Einsatz führt sehr schnell zu Datenmengen, die niemand braucht und die zudem noch die Netzbelastung unnötig anschwellen lassen. Diese Probleme können vermieden werden, wenn die Datenbank-Metadaten-Methoden von Anfang an möglichst viele Non-Null-Argumente enthalten.

Dummy Queries statt getColumns

Angenommen, in einer Anwendung hat der Benutzer die Wahl, welche der Spalten einer relationalen Tabelle er weiter bearbeiten will. Sollte er die Applikation „getColumns“ verwenden, um eine Ergebnismenge zu erhalten, oder besser eine vordefinierte Dummy Query unter Nutzung von „getMetaData“? Die beiden folgenden Listings verdeutlichen die Optionen:

Listing 1: GetColumns method

Dieser Call erzeugt eine Query des Systemkatalogs; möglicherweise muss ein Join vorbereitet und ausgeführt werden, der zu einem Result Set führt.

ResultSet WSrc = WSc.getColumns (… „UnknownTable“ …);

. . .
WSrc.next();
string Cname = getString(4);
. . .
// Der Benutzer fragt eine Zahl N an Zeilen ab
// und erhält als Ergebnis N = # Spalten der UnknownTable

Listing 2: GetMetadata method

Die Query wird auf dem Server nur vorbereitet, aber nicht ausgeführt.

// prepare dummy query

PreparedStatement WSps = WSc.prepareStatement
(… „SELECT * from UnknownTable WHERE 1 = 0“ …);
ResultSetMetaData WSsmd=wsps.getMetaData();
int numcols = WSrsmd.getColumnCount();

int ctype = WSrsmd.getColumnType(n)

// Die Informationen zu den Spalten liegen nun vor
// Das Beispiel mit getColumns liefert diese Informationen nicht.

In beiden Listings schickt der Benutzer eine Query an den Server. Im Listing 1 ist jedoch die Anfrage auszuwerten und dem Client ein Result Set zu übermitteln. Das Vorgehen in Listing 2 führt zu einer eindeutig besseren Performance.

Daten effizient abfragen

Der Server sollte bei einer Anfrage nur die tatsächlich benötigten Daten übermitteln und dazu die wirkungsvollste Methode nutzen. Eigentlich ist das eine Selbstverständlichkeit: Wenn es nicht unbedingt notwendig ist, sind Requests zu vermeiden, die umfangreiche Datenmengen erzeugen. Die einfachste Methode ist der Ausschluss großer Datenmengen aus der SELECT-Liste. Einige Anwendungen formulieren dieses Statement nicht, bevor die Query an den JDBC-Treiber geschickt wird. Falls möglich, ist eine Methode zu implementieren, die nicht alle Spalten einer Tabelle abfragt.

Mit den Methoden „getClob“ und „getBlob“ kann eine Anwendung kontrollieren, wie umfangreiche Datenmengen gefunden werden. Allerdings hat auch dieses Vorgehen seine Schwächen, denn der JDBC-Treiber emuliert diese Methoden, weil der True Locator Support im Datenbankmanagement-System fehlt. Daher muss der JDBC-Treiber zunächst alle Daten ermitteln, bevor er die getClob- und getBlob-Methoden einsetzen kann. Die Funktionen „setMaxRows“, „setMaxFieldSize“ und das treiberspezifische „SetFetchSize“ helfen, den Datenverkehr im Netzwerk zu reduzieren und damit die Performance zu verbessern.
Auch eine genaue Analyse der zu verarbeitenden Datentypen trägt dazu bei, die vorhandenen Ressourcen möglicht effizient einzusetzen. So ist im Falle von DB2 die Verarbeitung von Zeichenketten halb so aufwändig wie die von Integerwerten. Diese sind wiederum um 50 Prozent schneller als die von Gleitkomma-Formaten und Zeitangaben.

Der Aufruf von Stored Procedures

Eine der schwierigsten Aufgaben der JDBC-Programmierung ist der adäquate Einsatz von Stored Procedure Calls. Ein JDBC-Treiber kann Stored Procedures eines Datenbank-Servers auf zwei Arten aufrufen: Erstens indem die Prozedur ausgeführt wird wie jedes andere Statement auch und zweitens als Remote Procedure Call direkt auf dem Server; dies optimiert die Ausführung.

Wird eine Stored Procedure als SQL-Statement ausgeführt, parst der Datenbank-Server das Statement, validiert die Argumenttypen und wandelt die Argumente in die korrekten Datentypen um. Das SQL-Statement gelangt immer als Zeichenkette zum Datenbank-Server – beispielsweise: {call getCustName (56789)}. Auf den ersten Blick scheint das Argument der Funktion „getCustName“ ein Integer-Wert zu sein. Dennoch wird das Argument – in einem Character-String eingeschlossen – zum Server geschickt. Der Datenbank-Server analysiert das SQL-Statement und konvertiert die Kette ‚56789’ in eine Integer-Variable. Dazu ein Beispiel:

CallableStatement cstmt = conn.prepareCall (_call getCustNaame (56789)_);

ResultSet rs = cstmt.executeQuery ();

Der unmittelbare Aufruf eines RPC auf dem Datenbank-Server vermeidet den zuvor beschriebenen Overhead, den die Verwendung einer SQL-Zeichenkette erzeugt. Statt dessen wird die Prozedur mit Argumentwerten aufgerufen, die bereits in ihren ursprünglichen Datentypen vorliegen. Auch hier ein Beispiel:

CallableStatement cstmt – conn.prepareCall (_Call getCustName (?)_);

cstmt.setLong (1,56789);
ResultSet rs=cstmt.executeQuery();

Design von WebSphere-Applikationen

Von großer Bedeutung für die Performance einer Applikation ist das Connection Management. Statt diverse Connections einzurichten, lässt sich die Geschwindigkeit durch den einmaligen Aufbau der Verbindung und die anschließende Verwendung mehrerer Statement-Objekte deutlich steigern. So können Connection-Objekte mit mehreren Statement-Objekten verbunden sein. Diese halten im Speicher Informationen zu SQL-Statements vor und können so eine Reihe von SQL-Statements managen. Eine signifikante Verbesserung der Performance lässt sich mittels eines Connection-Poolings erreichen – insbesondere dann, wenn die Verbindung über das Internet erfolgt.

Abwicklung von Transaktionen

Die Durchführung von Transaktionen geht einher mit einer Reihe von Zugriffen auf die Festplatte und kann sehr zeitintensiv, extrem langsam und Disk-I/O-intensiv sein. Die Standardeinstellung sollte daher stets folgendermaßen sein: WSConnection.set-AutoCommit(false). Bei einem Commit muss der Datenbank-Server jede Page, die aktualisierte oder neue Daten enthält, auf die Festplatte befördern. Dies ist kein Sequential, sondern ein Searched Write, um vorhandene Daten in der Tabelle zu ersetzen. Standardmäßig ist Autocommit aktiviert, wenn eine Verbindung zu einer Datenquelle aufgebaut wird: Autocommit bremst die Performance, da zur Ausführung eines Vorgangs viele Disk-Zugriffe notwendig sind.

Verfügen Datenbank-Server nicht über den Autocommit-Modus, muss der JDBC-Treiber explizit ein COMMIT-Statement und ein BEGIN TRANSACTION für jede Operation vorsehen, die an den Server geschickt wird. Zusätzlich zu dem beträchtlichen Speicherplatz, der für die Unterstützung des Autocommit-Modus benötigt wird, kommt es auch noch zu Leistungseinbußen durch bis zu drei Netzwerk-Requests für jedes durch eine Applikation ausgelöste Statement. Auf der anderen Seite lässt sich durch einen wohldosierten Einsatz von Transaktionen die Applikations-Performance steigern. So reduziert eine Transaktion im aktivierten Modus den Datendurchsatz, da Zeilen über einen langen Zeitraum gesperrt sind und andere Benutzer darauf keinen Zugang erhalten. Transaktionen sollten also in bestimmten Intervallen abgewickelt werden, so dass möglichst viele Aktivitäten gleichzeitig erfolgen können.

Fazit

Mit einem sehr sorgfältigen Design und einer ebensolchen Implementierung lässt sich die Performance von JDBC-Applikationen erheblich steigern. Werden Regeln wie der kalkulierte Einsatz von Datenbank-Metadaten-Methoden, die Konzentration auf tatsächlich benötigte Daten in den Abfragen und die Auswahl von hochwirksamen Funktionen zur Performance-Optimierung umgesetzt, laufen WebSphere-Applikationen effizienter und werden durch verringerten Datenverkehr im Netzwerk entlastet. Die im Text geschilderten Prinzipien treffen zu auf alle Versionen des IBM Application Server in Kombination mit Connect for JDBC von DataDirect Technologies – einem Type 4-JDBC-Treiber für die performante Verbindung der Java-Plattform J2EE mit allen aktuellen Datenbanken wie DB2, Informix, Oracle, SQL Server oder Sybase.

DataDirekt Technologies GmbH

D–85356 München-Flughafen

Telefon: (+49) 089/63 63 74 1-7

www.datadirect-technologies.com