Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Buch: Java ist auch eine Insel - Zum Katalog
gp Kapitel 21 Datenbankmanagement mit JDBC
  gp 21.1 Das relationale Modell
    gp 21.1.1 Relationale und objektorientierte Datenbanken
  gp 21.2 JDBC: Der Zugriff auf Datenbanken über Java
  gp 21.3 Die Rolle von SQL
    gp 21.3.1 Ein Rundgang durch SQL-Anfragen
    gp 21.3.2 Datenabfrage mit der Data Query Language (DQL)
  gp 21.4 Datenbanktreiber für den Zugriff
  gp 21.5 Datenbanken und ihre Treiber
    gp 21.5.1 Datenbank Interbase
    gp 21.5.2 Interbase JDBC-Treiber
    gp 21.5.3 Die Datenbank Microsoft Access
    gp 21.5.4 Die JDBC-ODBC Bridge
    gp 21.5.5 ODBC einrichten und Access damit verwenden
    gp 21.5.6 Oracle8i Enterprise Edition
  gp 21.6 Eine Beispiel-Abfrage
  gp 21.7 Mit Java an eine Datenbank andocken
    gp 21.7.1 Der Treibermanager
    gp 21.7.2 Eine Aufzählung aller Treiber
    gp 21.7.3 Log-Informationen
    gp 21.7.4 Den Treiber laden
    gp 21.7.5 Wie Treiber programmiert sind
    gp 21.7.6 Verbindung zur Datenbank
  gp 21.8 Datenbankabfragen
    gp 21.8.1 Abfragen über das Statement-Objekt
    gp 21.8.2 Ergebnisse einer Abfrage im ResultSet
  gp 21.9 Java und SQL-Datentypen
    gp 21.9.1 Die getXXX()-Methoden
  gp 21.10 Transaktionen
  gp 21.11 Elemente einer Datenbank hinzufügen und aktualisieren
    gp 21.11.1 Batch-Updates
  gp 21.12 Vorbereitete Anweisungen (Prepared Statements)
    gp 21.12.1 PreparedStatement-Objekte vorbeiten
    gp 21.12.2 Werte für die Platzhalter eines PreparedStatement
  gp 21.13 Metadaten
    gp 21.13.1 Metadaten über die Tabelle
    gp 21.13.2 Informationen über die Datenbank
  gp 21.14 Die Ausnahmen bei JDBC
  gp 21.15 Java Data Objects (JDO)

Kapitel 21 Datenbankmanagement mit JDBC

Alle Entwicklung ist bis jetzt nichts weiter als ein
Taumeln von einem Irrtum in den anderen.
– Henrik Ibsen

Die Sammlung, der Zugriff und die Verwaltung von Informationen in unserem »Informationszeitalter« nehmen eine wichtige Rolle in der Wirtschaft ein. Während früher Informationen auf Papier gebracht wurden, bietet die EDV Datenbankverwaltungsysteme (DBMS, engl. Database Management Systems) an. Diese arbeiten auf einer Datenbasis (den Informationseinheiten, die miteinander in Beziehung stehen). Die Programme, die die Datenbasis kontrollieren, bilden die zweite Hälfe der DBMS. Die früher modernen Netzwerkmodelle oder hierarchischen Datenmodelle sind mittlerweile überholt – befinden sich aber noch in Einsatz –, und an deren Stelle trat das relationale Datenbankmodell. Dies sind, kurz gesagt, Tabellen, die untereinander in Beziehung stehen.


Galileo Computing

21.1 Das relationale Modell  downtop

Die Grundlage für relationale Datenbanken sind Tabellen, Spalten und Zeilen. Diese drei Wörter verraten uns zwar nicht, was hier relational ist, doch die Relationen werden in Tabellen abgebildet. Eine Zeile (auch Tupel genannt) entspricht einem Element einer Tabelle, eine Spalte (auch Attribut genannt) einem Eintrag einer Tabelle. Die Relation lässt sich hervorragend als Tabelle grafisch darstellen, wobei in der vertikalen die Spalten und in der horizontalen die Zeilen angegeben sind.

Tabelle 21.1  
Lfr_Code Lfr_Name Adresse Wohnort
004 Hoven G. H. Sandweg 50 Linz
009 Baumgarten R. Tankstrasse 23 Hannover
011 Strauch GmbH Beerenwerg 34a Linz
013 Spitzmann Hintergarten 9 Aalen
... ... ... ...

Jede Tabelle stellt eine logische Sicht der Benutzer dar. Hier wird zwischen Datenbankausprägung und Datenbankschema unterschieden. Die Zeilen einer Relation stellen die Datenbankausprägung dar, während die Struktur der Tabelle – also Anzahl und Name der Spalten – das Datenbankschema beschreibt. Um nun auf diese Tabellen Zugriff zu bekommen, um damit die Datenbankausprägung zu erfahren, brauchen wir Abfragemöglichkeiten. Java lässt uns mit dem jetzigen Modell (JDBC) auf relationale Datenbanken zugreifen.


Galileo Computing

21.1.1 Relationale und objektorientierte Datenbanken  downtop

Für die objektorientierten Datenbanken, die stark im Kommen sind, ist noch kein fertiges Konzept geschaffen. (Dies liegt unter anderem auch daran, dass zurzeit die meisten Datenbanken noch relational sind und Standards fehlen.) Mit JDBC-2 Treibern ist jedoch ein Übergang zum SQL 3-Standard geschaffen, in dem objektorientierte Konzepte eine größere Rolle spielen.


Galileo Computing

21.2 JDBC: Der Zugriff auf Datenbanken über Java  downtop

JDBC ist die inoffizielle Abkürzung für Java Database Connectivity und bezeichnet einen Satz von Klassen und Methoden, um relationale Datenbanksysteme von Java zu nutzen. Das JDBC-Projekt wurde 1996 gestartet und die Spezifikation im Juni 1996 festgelegt. Die Klassen sind ab dem JDK 1.1 im Core-Paket integriert. Mit der JDBC-API und den JDBC-Treibern wird eine wirksame Abstraktion von Datenbanken erreicht, sodass durch die einheitliche Programmierschnittstelle die Funktionen unterschiedlicher Datenbanken gleich genutzt werden können. Das Lernen von verschiedenen Zugriffsmethoden für unterschiedliche Datenbanken der Hersteller entfällt. Wie diese spezielle Datenbank dann nun wirklich aussieht, wird uns wegen der Abstraktion verheimlicht. Jede Datenbank hat ihr eigenes Protokoll (und eventuell auch Netzwerkprotokoll), aber diese Implementierung ist nur dem Datenbanktreiber bekannt.

Das Modell von JDBC setzt auf dem X/OPEN SQL-Call-Level-Interface (CLI) auf und bietet somit die gleiche Schnittstelle wie Microsofts ODBC (Open Database Connectivity). Dem Programmierer gibt JDBC Funktionen, um Verbindungen zu Datenbanken aufzubauen, Datensätze zu lesen oder neue Datensätze zu verfassen. Zusätzlich können Tabellen aktualisiert und Prozeduren auf der Server-Seite ausgeführt werden.

Wir wollen kurz die Schritte skizzieren, die für einen Zugriff auf eine relationale Datenbank mit JDBC nötig sind:

1. Installieren der JDBC-Datenbank-Treiber.

Wir beschränken uns im Folgenden auf die Verbindung zu dem komerziellen Datenbanksystem Microsoft-Access und der quasi-freien Datenbank Interbase von Borland. Interbase wurde von den Lesen des Java-Magazins neben Oracle als bestes Produkt 2001 in der Sparte Datenbank normiert.


Galileo Computing

21.3 Die Rolle von SQL  downtop

SQL ist eine Anfragesprache, in der Benutzer angeben, auf welche Daten sie zugreifen möchten. Obgleich »Anfragesprache« etwas irreführend klingt, beinhaltet sie auch Befehle zur Datenmanipulation und Datendefinition, um beispielsweise neue Tabellen zu erstellen. Nachdem Anfang der 70er-Jahre das relationale Modell für Datenbanken, also Tabellen, populär wurde, entstand im IBM-Forschungslabor San Jose (jetzt Almaden), ein Datenbanksystem mit dem Namen »System R«. Das relationale Modell wurde 1970 von Dr. E. F. Codd entwickelt. System R bot eine Anfragesprache, die SEQUEL (Structured English Query Language) genannt wurde. Später wurde SEQUEL in SQL umbenannt. Da sich relationale Systeme einer großen Beliebtheit erfreuten, wurde 1986 die erste SQL-Norm vom ANSI-Konsortium verabschiedet. 1988 wurde der Standard geändert und 1992 entstand die zweite Version von SQL (SQL-2 bzw. SQL-92 genannt). Da die wichtigen Datenbanken alle SQL-2 verarbeiten, kann ein Programm über diese Befehle die Datenbank steuern, ohne verschiedene propietäre Schnittstellen nutzen zu müssen. Dennoch können über SQL die speziellen Leistungen einer Datenbank genutzt werden.

Tabelle 21.2   Entwicklung von SQL
Sprache Entwicklung
SQUARE 1975
SEQUEL 1975, IBM Research Labs San Jose
SEQUEL2 1976, IBM Research Labs San Jose
SQL 1982, IBM
ANSI-SQL 1986
ISO-SQL 1989, drei Sprachen Level 1, Level 2, +IEF
Firmenstandards IBM SQL, OS/2 SQL, X/Open UNIX SQL, ...
SQL-2 (bzw. SQL-92) 1992
SQL-3 in Bearbeitung

Damit sich ein Datenbanktreiber JDBC-kompatibel nennen kann, muss er mindestens SQL-92 unterstützen. Das heißt jedoch nicht, dass die existierenden Treiber alle Eigenschaften von SQL-92 unterstützen.


Galileo Computing

21.3.1 Ein Rundgang durch SQL-Anfragen  downtop

Da das Wort »Anfragesprache« irgendwie eine Art Programmiersprache suggeriert, sind wir interessiert an einem Beispiel. Um es vorweg zu sagen: Es gibt nur eine Hand voll wichtiger Befehle und SELECT, UPDATE, CREATE decken schon einen Großteil ab.

Beispiel Eine einfache Abfrage in SQL
SELECT Lfr_Name
FROM LIEFERANTEN

Tabellen nehmen die Benutzerdaten auf und mit dem Kommando FROM wählen wir die Tabelle LIEFERANTEN aus, die für die Berechnung nötig ist. Die Tabelle LIEFERANTEN enthält drei Attribute (die Spalten), die wir mit SELECT auswählen. In einer Datenbank werden normalerweise mehrere Tabellen verwendet, ein so genanntes Datenbank-Schema. Jede Tabelle gehört genau zu einem Schema.

SQL-Abfragen sind nahe an einer natürlichsprachlichen Formulierung. Im oberen Beispiel liest sich die Abfrage einfach als: »Wähle die Spalte Lfr_Name aus der Tabelle LIEFERANTEN«. Der Designer einer Datenbank muss sich natürlich vor der Umsetzung der Tabellen und somit der Relationen gründlich Gedanken machen. Denn eine spätere Änderung der Struktur ist teuer. So muss schon am Anfang einkalkuliert werden, welche Daten in welchen Ausprägungen auftreten können. Nach Statistiken des amerikanischen Library of Congress verdoppelt sich insgesamt alle fünf Jahre die Informationsmenge. Was wäre, wenn diese Informationen alles Einträge in einer Datenbank wären, und jemand würde feststellen, dass das Tabellenschema ungünstig ist? Eine Datenbank muss also schon enorm leistungsfähig sein, um sich einer solchen Menge stellen zu können. Datenbankdesigner nennen den Vorgang von einem ersten Modell zur fertigen Relation Normalisierung.

Bevor wir mit den einzelnen Sprachelementen von SQL fortfahren, an dieser Stelle ein paar Regeln für SQL-Ausdrücke:

gp  Die SQL-Anweisungen sind unabhängig von der Groß- und Kleinschreibung. Im Text sind die SQL-Kommandos großgeschrieben, damit die Ausdrücke besser lesbar sind.
gp  Leerzeichen, Return, Tabulatoren sind in einer Anfrage bedeutungslos. Im Folgenden werden zur besseren Lesbarkeit Zeilenumbrüche verwendet.
gp  SQL-Anweisungen werden mit einer Zeichenkette abgeschlossen. Diese unterscheidet sich aber von Datenbank zu Datenbank. Häufig ist dies ein Semikolon; es kann aber auch ein \go sein. Wir werden die Anweisungen in den Beispielen nicht abschließen, da JDBC diesen Abschluss automatisch vornimmt.

Im Folgenden werden wir uns noch intensiver um SQL-Anfragen kümmern. Es wird sich zeigen, dass eine einzelne Anweisung sehr ausdrucksstark sein kann. JDBC hat aber mit dieser Ausdrucksstärke nichts zu schaffen, es weiß noch nicht einmal um ihre Korrektheit. JDBC leitet den SQL-Befehl einfach an den Treiber weiter und dieser leitet das Kommando dann wiederum an die Datenbank weiter.


Galileo Computing

21.3.2 Datenabfrage mit der Data Query Language (DQLdowntop

Unsere ersten Beispiele, die wir benutzen werden, basieren auf einer existierenden Datenbank mit Pflanzen, Bestellungen und Lieferanten. Interessieren wir uns nun also für Abfragekommandos, um auf die Inhalte zuzugreifen. In SQL steckt auch schon das Wort »Query«, unsere Anfrage. Das wichtigste Element ist hierbei das schon oben genannte SELECT.

SELECT {Feldname, Feldname,..|*} 
( * = alle Felder )
FROM Tabelle [, Tabelle, Tabelle....]
[WHERE {Bedingung}]
[ORDER BY Feldname [ASC|DESC]...]
[GROUP BY Feldname [HAVING {Bedingung}]]

Das angenehme bei SQL ist, dass wir uns nicht um das Wie kümmern müssen, sondern nur um das Was. Wir fragen also etwa »Welche Lieferanten wohnen in Aalen?« und formulieren:

SELECT Lfr_Name
FROM LIEFERANTEN
WHERE Wohnort='Aalen'

Dabei ist es uns egal, wie die Datenbankimplementierung mit dieser Anfrage umgeht. Und hier unterscheiden sich auch die Anbieter in ihrer Leistungsfähigkeit und in den Preisen.

Kümmern wir uns nun um die verschiedenen Schreibweisen von SELECT. Geben wie im SELECT eine Spalte an, so bekommen wir nur die Ergebnisse dieser Spalte zurück. Eine Anfrage, die alle Spalten zurückgibt, wird mit dem »*« geschrieben. Damit wir also nicht nur den Namen des Kunden bekommen, sondern auch noch die anderen Angaben – um ihm gleich einen Auftrag zu geben – schreiben wir Folgendes, um eine Liste aller Lieferanten in Aalen zu bekommen:

SELECT * FROM LIEFERANTEN WHERE 
Wohnort='Aalen'

Wir sehen, dass es keinen Unterschied macht, ob die Anfragen in mehrere Zeilen aufgespaltet sind oder in einer Zeile stehen. Wir werden jedoch zur besseren Lesbarkeit bei einzelnen Zeilen bleiben.

Zeilen ausfiltern und logische Operatoren

Die SELECT-Anweisung geht über die Spalten und die WHERE-Angabe filtert Zeilen nach einem Kriterium heraus. Wir haben zunächst mit einer Gleich-Abfrage gearbeitet. SQL kennt die üblichen Vergleichsoperatoren: = gleich, <> ungleich, > größer, < kleiner, >= größer gleich, <= kleiner gleich. Vergleiche werden mit einem einfachen Gleichheitszeichen und nicht durch == formuliert. Die Vergleichsoperatoren lassen sich durch die Operatoren AND, OR und NOT weiter verfeinern. Bei numerischen Daten können wir auch die Rechenoperatoren (+, –, *, /) anwenden. Anstelle vielfacher AND-Anfragen lässt sich mit zwei SQL-Anweisungen auch der Wertebereich noch weiter einschränken. Mit BETWEEN Wert1 AND Wert2 lässt sich testen, ob ein Vergleichswert zwischen Wert1 und Wert2 liegt. Mit IN (Werteliste) wird getestet, ob der Vergleichswert in der angegebenen Werteliste liegt. Für Zeichenketten spielt noch LIKE eine Rolle, da hier ein Mustervergleich vorgenommen werden kann. Mit IS NULL lässt sich mit einem NULL-Wert vergleichen.

Wenn wir diese SQL-Anweisung von der Datenbank ausführen lassen, so wollen wir die Daten gerne nach dem Preis sortiert haben. Dazu lässt sich die SELECT-Anweisung mit einem ORDER BY versehen. Dahinter folgt die Spalte, nach der sortiert wird. Jetzt wird die Tabelle aufsteigend sortiert, also der kleinste Wert unten. Wünschen wir die Sortierung absteigend, dann setzen wir noch DESC hintenan.

Wir wollen nun die Informationen mehrerer Tabellen miteinander verbinden. Dazu führen wir eine Tupelvariable ein. Diese kann dann eingesetzt werden, wenn sich Attribute nicht eindeutig den Relationen zuordnen lassen. Dies ist genau dann der Fall, wenn zwei Relationen verbunden werden sollen und beide den gleichen Attributnamen besitzen.

Beispiel Die SQL-Anweisung zeigt die Verwendung der Variable, die jedoch hier nicht nötig ist, da nur eine Tabelle verwendet wird.
SELECT L.Lfr_Name, L.Wohnort
FROM LIEFERANTEN L
WHERE L.Wohnort = 'Aalen'

Der Buchstabe »L« ist hier nur eine Abkürzung, eine Art Variable. Abkürzungen für Spalten werden in SQL auch mit AS abgetrennt, etwa

FROM LIEFERANT AS L

Standardmäßig darf der Relationenname auch als Tupelvariable benutzt werden. Normalerweise können wir mit Tupelvariablen Schreibarbeit sparen.

SELECT LIEFERANTEN.Lfr_Name, LIEFERANTEN.Wohnort
FROM LIEFERANTEN

Damit wir auch zwei Tabellen gleichzeitig zeigen können, trennen wir die Anfragen mit Komma. Das Attribut der Tabelle wird wie bei Objekten mit einem Punkt vom Tabellennamen getrennt.

Gruppenfunktionen

Mit Gruppenfunktionen (auch Aggregationsfunktionen) lassen sich etwa Durchschnittswerte oder Minima über Spalten beziehen. Sie liefern genau einen Wert, beziehen sich jedoch auf mehrere Tabellenzeilen. Die folgende Anfrage liefert alle Anbieter aus Aalen:

SELECT COUNT(*)
FROM LIEFERANTEN
WHERE Wohnort = 'Aalen'

Die Spalten, die die Gruppenfunktion bearbeitet, steht in Klammern hinter dem Namen. Die SQL-Standardfunktionen (es gibt datenbankabhängig noch viel mehr) sind in der folgenden Tabelle aufgeführt:

Tabelle 21.3   Die Standardfunktionen in SQL
AVG Durchschnittswert
COUNT Anzahl aller Einträge
MAX Maximalwert
MIN Minimalwert
SUM Summe aller Einträge in einer Spalte


Galileo Computing

21.4 Datenbanktreiber für den Zugriff  downtop

Damit wir JDBC nutzen können, brauchen wir einen passenden Treiber für die Datenbank. JavaSoft unterscheidet vier Treiber-Kategorien:

1. JDBC-ODBC Bridge Treiber Da es am Anfang der JDBC-Entwicklung keine Treiber gab, haben sich die Entwickler etwas ausgedacht: Eine JDBC-ODBC-Brücke, die die Aufrufe von JDBC in ODBC-Aufrufe der Client-Seite umwandelt. Die Methoden sind nativ. Im Folgenden werden wir die Brücke einsetzen, wenn wir Access über ODBC ansprechen.

Versuchen wir daher einmal einige Unterscheidungsmerkmale herauszuarbeiten. Ein Kriterium ist, ob sie in Java implementiert sind oder plattformabhängigen Programmcode beinhalten. Die Treiber vom Typ 3 und Typ 4 sind vollständig in Java implementiert und daher portabel. Treiber vom Typ 0 oder Typ 1 sind das nicht, da sie zum einen für die JDBC-ODBC-Brücke auf die Plattform-Bibliothek für ODBC zurückgreifen müssen, und zum anderen auf plattformspezifische Zugriffsmöglichkeiten für die Datenbank. Damit ist der Nachteil verbunden, dass Applets mit diesen Treibern nichts anfangen können. Ein Applet erlaubt es nicht, nativen Code von anderen Quellen zu laden und auszuführen. Das ist auch schwierig, wenn etwa ein Macintosh mit Power PC Prozessor einen binären Treiber für eine MS-SQL Datenbank installieren möchte. Die Quintessenz ist: Applets können damit keine Verbindung zu einer externen Datenquelle aufbauen.

Besonders eine Definition der Typ3-Treiber fällt schwer, da die Definition von Sun nicht unbedingt auf den ersten Blick klar ist. Zum Vergleich beginnen wir mit Typ 4. Diese Treiber sprechen mit dem datenbankspezifischen Protokoll direkt mit der Datenbank über einen offenen IP-Port. Dies ist in einer Direktverbindung die performanteste Lösung. Jedoch ist sie nicht immer möglich. Ein Grund ist, dass manche Datenbanken wie MS-Access, dBase oder Paradox kein Netzwerkprotokoll definieren. Da erfüllen Typ 3-Treiber eine Vermittlerrolle, denn dieser Treibertyp spricht nicht mit der Datenbank, sondern mit einer Softwareschicht, die zwischen der Anwendung und der Datenbank sitzt: die so genannte Middleware. Diese kann etwa in der Mitte die Anweisungen entgegennehmen und an die Datenbank weiterleiten. Für Applets und Internetdienste hat ein Typ 3-Treiber zudem den Vorteil, dass ihre Klassendateien oft kleiner als Typ 4-Treiber sind, da ein komprimiertes Protokoll eingesetzt werden kann. Über das spezielle Protokoll zur Middleware ist auch eine Verschlüsselung der Verbindung möglich. Kaum eine Datenbank unterstützt verschlüsselte Datenbankverbindungen. Da zudem das Middleware-Protokoll unabhängig von der Datenbank ist, müssen auf der Client-Seite für einen Datenbankzugriff auf mehrere Datenbanken auch nicht mehr alle Treiber installiert werden, sondern im günstigsten Fall nur noch ein Typ 3-Treiber von einem Anbieter. Die Ladezeiten sind damit deutlich niedriger.


Galileo Computing

21.5 Datenbanken und ihre Treiber  downtop

Wir wollen uns im Folgenden mit einigen Datenbanken beschäftigen, die für den Zugriff unter Java geeignet sind.


Galileo Computing

21.5.1 Datenbank Interbase  downtop

Die Datenbank Interbase 6 (http://www.interbase.com) von InterBase Software Corporation, eine Tochter der Borland International Inc, implementiert viele Anforderungen aus dem ANSI-SQL-92. Interbase, kurz IB, bietet gespeicherte Prozeduren oder Trigger und ist eine ausgewachsene performante Lösung, die zum Beispiel von Motorola, Nokia, MCI, Northern Telecom, First National Bank of Chicago, Bear Stearns, The Money Store, US-Army, NASA, Boeing, National Semiconductor und Boston Stock Exchange seit Jahren eingesetzt wird. Warum wir uns hier für Interbase und nicht für Oracle, DB 2 oder Microsoft SQL entscheiden, ist darin begründet, dass Borland den Quellcode sowie die ausführbaren Dateien für Windows, Novell Netware und verschiedenen UNIX-Versionen wie Linux und Solaris zur Verfügung stellt (http://www.inprise.com/interbase/downloads) und auf eine Variante der Mozilla Public License (MPL) V 1.1 setzt. IB kann auf dem Rechner leicht installiert werden und benötigt wenige Megabytes Hauptspeicher.

Interbase installieren

Nach dem Download der 5.11 MB (für die Version ib_server_6_0_1.zip) können wir mit setup.exe IB installieren. Nach der Installation wird der Server automatisch gestartet und auch bei jedem Windows-Start mit hochgefahren, da er als Windows-Service eingetragen ist. Ändern lässt sich dies unter Start/Programme/InterBase Service Manager.

Obwohl wir den Server auch auf der Konsole administrieren können, ziehen wir eine grafische Bedienoberfläche vor. Dazu rufen wir unter Start/Programme/InterBase das Programm IBConsole auf. Im linken Baum findet sich unter InterBase Servers eine Auflistung der Server. Bisher ist dort keiner aufgeführt, weshalb wir einen Server eintragen, der automatisch mit der Installation mit installiert wird. Entweder öffnen wir mit Doppelklick auf InterBase Servers den Dialog Register Server and Connect oder wir bringen das Fenster durch den Menüeintrag Servers nach vorne. In den Login-Informationen müssen wir zum Serverzugang Benutzername und Passwort eintragen. Folgende Einträge sind vorbelegt:

User Name: SYSDBA
Passwort: masterkey

Eine Datenbank einbinden

Falten wir im linken Fenster den Baum bei Local Server auf, so treten die Einträge Databases, Backup, Server Log und Users hervor. Bisher ist noch keine Datenbank eingetragen, aber für Übungen wollen wir eine Datenbank verwenden. Um eine Datenbank mit dem Manager zu verbinden, gehen wir entweder über das Menü Database/Register oder aktivieren auf Databases in der Baumansicht mit der rechten Maustaste den ersten Menüpunkt Register.

Nützliche Hilfsprogramme und Konvertieren

Über den grafischen Datenbankadministrator lassen sich Tabellen verwalten, Trigger programmieren und einfache SQL-Anweisungen absetzen. Eine hübsche Erweiterung der Fähigkeiten bietet die InterBase Workbench (kurz IBWorkbench) von Upscene Productions unter http://www.interbaseworkbench.com. Mit diesem Programm lassen sich einfach Tabellen anlegen, Constraints und Rechte festlegen. Integriert ist ein leistungsfähiger SQL-Editor mit Debugger für gespeicherte Prozeduren.

Ein Problem ist immer die Konvertierung von vorhandenen Daten in ein anderes Datenbankformat. Liegen Tabellen zum Beispiel in Microsoft Access vor und diese sollen nach InterBase wandern, so bietet sich zum Beispiel der Weg über ODBC-Treiber an. Bei der Interbase 6 Version liegt leider kein ODBC-Treiber bei, sodass dieser aus der alten Version bezogen werden muss. Dafür müssen wir unter http://www.interbase.com/open/downloads/ 5x_eval_kits.html die ältere Version laden, das Installationstool aufrufen und lediglich den ODBC-Treiber installieren. Wird die Datenquelle anschließend im ODBC-Manager eingetragen, so lassen sich die Tabellen von einem beliebigen ODBC-Client lesen. Wer Access nutzt und existierende Daten in die IB importieren möchte, muss über einen kleinen Umweg gehen. Zuerst sollten in der IB-Datenbank die Tabellen mit gleichem Schema angelegt werden. Anschließend sollte die zu konvertierende Tabelle in Access geladen werden und mittels Tabellen verknüpfen auf die IB-Datenbank verwiesen werden. Per Copy&Paste lassen sich jetzt die Daten kopieren. Es gibt leider keinen Weg in Access, mit dem die Schemata der Tabellen aus einer Datenbank auf eine andere übertragen werden können.


Galileo Computing

21.5.2 Interbase JDBC-Treiber  downtop

Aus Java-Sicht interessiert uns bei der InterBase Datenbank kein ODBC-Treiber, sondern ein Java-Datenbanktreiber. Dieser trägt den Namen InterClient und ist eine 100 %-ige Java-Implementierung. Für die Java-Version gibt es ein plattformabhängiges Installationsprogramm. Daher pflegt Borland auf der Webseite http://www.interbase.com/open/ downloads/ib_download.html unterschiedliche Betriebssystemversionen. Die Version InterClient 1.6 für Windows besitzt lediglich eine Dateilänge von 2.6 MB.

Nach der Installation können wir den Treiber testen und die Test-HTML-Seite CommDiag.html im Installationsverzeichnis (etwa C:\Program Files\InterBase Corp\InterClient) im Browser aufrufen. Dieser erzeugt dann ein Fenster und gibt die Meldung

interbase.interclient.Driver registered

aus.


Galileo Computing

21.5.3 Die Datenbank Microsoft Access  downtop

Mit Microsoft Access lässt sich mit wenigen Handgriffen eine Datenbank zusammenbauen. Der folgende Ablauf gilt für Access 2000. Wird Access geöffnet, erscheint ein Dialog mit dem Eintrag Leere Access-Datenbank. (Alternativ lässt sich eine neue Datenbank unter dem Menüpunkt Datei, Neu ... einrichten.) Wir wählen den ersten Eintrag Datenbank und speichern dann die Datenbank unter einem aussagekräftigen Namen. Access benötigen wir nicht mehr direkt, da die Kommunikation mit der Datenbank anschließend über den ODBC-Manager läuft. Dieser setzt dann auf den SQL-Kern von Access auf. Im ODBC-Manager muss die Datenquelle dazu angemeldet werden. Dies beschreiben wir etwas später. Mit SQL können nun die Relationen eingetragen und darauf die Anfragen gestellt werden.

Access wird als Beispiel für die JDBC-ODBC-Brücke beschrieben. Viele Anwender haben das Office-Paket von Microsoft zu Hause installiert und so schon eine Datenbank wie Access in der Nähe. Der Nachteil bei der JDBC-OCBC-Brücke ist jedoch, das diese erst seit der Version 1.4 den neueren JDBC 2-Standard implementiert.


Galileo Computing

21.5.4 Die JDBC-ODBC Bridge  downtop

ODBC (Open Database Connectivity Standard) ist ein Standard von Microsoft, der den Zugriff auf Datenbanken über eine genormte Schnittstelle möglich macht. ODBC ist weit verbreitet und auch für Macintosh-Systeme und einige UNIX-Plattformen verfügbar. Um die Anzahl der Produkte, die JDBC nutzen, zu beschleunigen, haben JavaSoft und Intersolv eine JDBC-ODBC-Brücke entwickelt, die die JDBC-Aufrufe nach ODBC umwandelt. Leider ist diese Brücke nur für Win32- und Solaris-Systeme verfügbar. Das JDK von Sun und deren Lizenznehmern liefern diese Brücke aus und damit können Java-Entwickler Datenbank-Applikationen mit der Unterstützung einer Vielzahl von existierenden ODBC-Treibern programmieren. In diesem Tutorial wird die JDBC-ODBC-Brücke genutzt, um unter Windows über ODBC auf die Datenbank Microsoft Access zuzugreifen. Obwohl es bisher von Microsoft noch keinen reinen Java-Treiber für Access gibt, bieten doch Drittanbieter JDBC-Treiber auch für die große Datenbank MS-SQL Server an. Unter ihnen WebLogic (http://www.weblogic.com) und InterSolv (http://www.intersolv.com).

Damit wir die JDBC-ODBC-Brücke nutzen können, brauchen wir einen ODBC-Treiber für die spezielle Datenbank. ODBC ist kein Teil eines Betriebssystems, sondern muss getrennt installiert werden, zum Beispiel vom Office-Paket oder vom MS SQL-Server. Auch bei Microsofts Web Server, das Windows NT beiliegt, kann ein ODBC-Treiber installiert werden. Da ODBC von Microsoft ist, gibt es ODBC-Treiber nicht für alle Plattformen. Das heißt, die JDBC-ODBC-Bridge fällt für manche Systeme sofort flach. Microsoft liefert Versionen von ODBC für Windows, Windows 95, Windows NT und Macintosh aus. Von verschiedenen Herstellern gibt es Portierungen für einige UNIX-Plattformen, unter anderem Solaris. Ein Projekt unter dem Namen FreeODBC hat sich zum Ziel gesetzt, ODBC auch für andere Plattformen zu verbreiten. Unter anderem gibt es eine freie JDBC-ODBC-Bridge unter den Systemen OS/2, UNIX (auch Linux) und Win32.


Galileo Computing

21.5.5 ODBC einrichten und Access damit verwenden  downtop

Abbildung

Die Installation von ODBC sieht bei jedem Datenbankanbieter anders aus. Benutzen wir Microsoft Access, so werden die ODBC-Treiber während der Installation automatisch mitinstalliert. Wenn wir eine Datenquelle unter ODBC hinzufügen wollen, müssen wir nur wenige Schritte unternehmen. In den Systemeinstellungen (Start, Einstellungen, Systemeinstellungen) suchen wir nach dem Symbol ODBC-Datenquellen (32 Bit), unter Windows 2000 im zusätzlichen Verzeichnis Verwaltung. Nach dem Aktivieren öffnet sich ein Dialog mit dem Titel ODBC-Datenquellen-Administrator.

Wir gehen auf Hinzufügen, um eine neue Benuter-Datenquelle hinzuzufügen. Im Dialog mit dem Titel Neue Datenquelle erstellen wählen wir den Microsoft Access-Treiber aus, und gehen auf Fertigstellen. Ein Dialog öffnet sich und wir tragen unter Datenquellenname einen Namen für die Datenquelle ein. Darunter können wir später in Java die Datenbank ansprechen. Der Name der Datei hat nichts mit dem Namen der Datenquelle gemeinsam. Optional können wir noch eine Beschreibung hinzufügen. Wichtig ist nun die Verbindung zur physikalischen Datenbank. Im umrandeten Bereich Datenbank aktivieren wir über die Schaltfläche Auswählen einen Datei-Selektor. Hier hangeln wir uns bis zur in Access erstellten Datei durch und tragen sie ein. Nun nur noch ein paar Mal OK drücken und wir sind fertig. Wenn der Administrator nicht meckert, können wir nun ein JDBC-Programm starten.

Abbildung 21.1   Auswählen einer Datenbank
Abbildung

Die Geschwindigkeit der JDBC-ODBC-Brücke

Die Geschwindigkeit des Zugriffs über die JDBC-ODBC-Brücke hängt von vielen Faktoren ab, sodass eine pauschale Antwort nicht zu geben ist. Denn zwischen der Abfrage unter JDBC bis zur Datenbank hängen viele Schichten, bei denen unter anderem viele Hersteller beteiligt sind:

gp  Der JDBC-Treiber-Manager von JavaSoft
gp  Der Treiber der JDBC-ODBC-Bridge von JavaSoft und InterSolv
gp  Der ODBC-Treiber-Manager von Microsoft
gp  Der ODBC-Treiber zum Beispiel vom Datenbankhersteller
gp  Die Datenbank selbst

Jede der Schichten übersetzt nun die Anfragen an die Datenbank in möglicherweise völlig andere Anfragen. So muss zwischen JDBC und ODBC eine Übersetzung vorgenommen werden, dann muss das SQL-Kommando geparst werden usw. Dann geht der Weg auch wieder zurück, von der Datenbank über die Treiber bis hin zum Java-Code. Dies dauert natürlich seine Zeit. Zusätzlich kommen zum Zeitaufwand und dem benötigten Speicher, den die Konvertierung benötigt, noch Inkompatibilitäten und Fehler hinzu. Somit hängt das Gelingen der JDBC-ODBC-Brücke von vielen Schichten ab und ist in vielen Fällen nicht so performant wie eine native Implementierung.


Galileo Computing

21.5.6 Oracle8i Enterprise Edition  downtop

Einer der bekanntesten Datenbanken stammt von Oracle. Um die Verbreitung weiter zu erhöhen, ist die Firma dazu übergegangen, eine vollwertige Version der Enterprise Edition Release 3 (8.1.7) zum Download oder als CD freizugeben. Wer das Download nicht scheut, der kann unter http://otn.oracle.com/software/products/oracle8i/htdocs/winsoft.html die Version für Windows 2000 bzw. NT von rund 600 MB herunterladen.


Galileo Computing

21.6 Eine Beispiel-Abfrage  downtop

Mit einem abschließenden Beispiel wollen wir in der Einleitung die Programmkonzepte für JDBC deutlich machen. Das Programm in der Klasse Sql baut eine Verbindung zum Datenbank-Manager auf und möchte auf die Daten der Datenbank Pflanzen zugreifen. Die Datenbank ist als ODBC-Datenquelle eingetragen.

Hinweis Bisher haben wir den Aufbau der Datenbank noch nicht erläutert, sie fällt also etwas vom Himmel. Das Beispiel verdeutlicht nur die Verwendung der Klassen und Methoden. Es soll zeigen, dass mit wenigen Programmzeilen Datenbankabfragen möglich sind. Zudem setzen wir hier eine ODBC-Datenquelle mit einer Beispiel-Datenbank vorraus.

Listing 21.1   Sql.java
import java.sql.*;

public class Sql
{
public static void main( String args[] )
{
try {
Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver" );
}
catch ( Exception e ) {
System.out.println( "Fehler bei ODBC-JDBC-Bridge" + e );
return;
}

Connection conn;
Statement stmt;
ResultSet rSet;

try
{
String url = "jdbc:odbc:Pflanzen";
conn = DriverManager.getConnection( url, "User", "User" );
stmt = conn.createStatement();

String sqlQuery = "SELECT Lfr_Name, Adresse FROM LIEFERANTEN";
rSet = stmt.executeQuery( sqlQuery );
}
catch ( Exception e )
{
System.out.println( "Fehler bei Datenbankzugriff" + e );
return;
}

try
{
while ( rSet.next() )
System.out.println ( rSet.getString(1) + "\t" + rSet.getString(2) );

stmt.close();
conn.close();
}
catch ( Exception e )
{
System.out.println( "Fehler bei Tabellenabfrage" + e );
return;
}
}
}

Galileo Computing

21.7 Mit Java an eine Datenbank andocken  downtop

Die Verbindung zu einer Datenbank wird über die Klasse DriverManager und der Schnittstelle Connection aufgebaut. Alle verwendeten Pakete liegen unter java.sql.*. Vor der Ausführung der JDBC-Befehle muss ein passender Datenbanktreiber geladen werden. In unserem Beispiel verwenden wir die von JavaSoft mitgelieferte JDBC-ODBC-Bridge, deren Klasse unter dem Namen JdbcOdbcDriver verfügbar ist. Damit können wir uns also zu allen Datenquellen mit einer ODBC-Schnittstelle verbinden.


Galileo Computing

21.7.1 Der Treibermanager  downtop

Alle Datenbanktreiber müssen an einer Stelle gesammelt werden, dem Treibermanager. Dazu dient eine besondere Java-Klasse, die Klasse DriverManager. Wie wir später sehen werden, bietet der Treibermanager eine Methode getConnection() an, mit der wir eine Verbindung zur Datenbank aufbauen.

Abbildung


Galileo Computing

21.7.2 Eine Aufzählung aller Treiber  downtop

Die statische Methode getDrivers() der Klasse DriverManager liefert eine Aufzählung der angemeldeten Treiber. Alle Methoden der Klasse sind statisch, da sich ein Exemplar dieser Klasse nicht erzeugen lässt; der Konstruktor ist privat. Ein normales Programm hat in der Regel keinen angemeldeten Datenbanktreiber. Eine Aufzählung bekommen wir durch folgende Zeilen:

for ( Enumeration e = DriverManager.getDrivers();
e.hasMoreElements(); )
System.out.println( e.nextElement().getClass().getName() );

Die Elemente, die durch die Enumeration ausgelesen werden, sind Treiber-Objekte. Jeder Datenbanktreiber ist als Treiber-Objekt implementiert. Da Driver aber eine Schnittstelle ist, gibt es keine sinnvolle toString()-Methode, und wir bekommen den Klassennamen und einen Hashwert. Schöner ist der Klassename, den wir über ein Class-Objekt erfragen können. getClass() liefert das Klassenobjekt und getName() den Namen. Ohne geladenen Treiber bekommen wir keine Ausgabe. Laden wir den JDBC-ODBC-Treiber, etwa durch

Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver" 
);

dann ist die Ausgabe

sun.jdbc.odbc.JdbcOdbcDriver

Galileo Computing

21.7.3 Log-Informationen  downtop

Zu Testzwecken bietet sich eine Ausgabe der Informationen an, Informationen des Treibers und der Datenbank in einen speziellen Ausgabekanal zu schreiben. Wir können die Log-Informationen so umlenken, dass sie in den Standard-Ausgabestrom geschrieben werden. Dazu dient die statische Methode setLogWriter(). Die bekommt einen PrintWriter als Parameter. Vor Java 1.2 hieß die Methode noch setLogStream(), diese nahm als Parameter einen PrintStream. Sie ist nun veraltet.

Folgende Zeile gibt alle Informationen auf dem Bildschirm aus, die in das Logbuch geschrieben werden:

DriverManager.setLogWriter( new 
PrintWriter(System.out) );

Ladevorgang der Treiber protokolliert ausgeben

Es bietet sich eine Ausgabe an, da so interessante Aussagen über die Funktionsweise der Treiber offenbar werden. Zur Set-Methode existiert die passende getLogWriter()-Methode, die den PrintWriter zurückgibt. Eine Anfrage an getLogWriter() gibt null zurück, was verrät, dass standardmäßig keine Ausgabe stattfindet. Eine versteckte Datei wird also nicht erzeugt.

Testen wir die Ausgabe, die die beiden Programmzeilen erzeugen:

DriverManager.setLogWriter( new 
PrintWriter(System.out) );
Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver" );

Zunächst setzen wir die Ausgabe auf den Standard-Ausgabekanal. Dann laden wir die Treiberklasse für die JDBC-ODBC-Brücke. An dieser Stelle greifen wir schon etwas vor, doch zeigt die Ausgabe, was an dieser Stelle geschieht:

DriverManager.initialize: jdbc.drivers 
= null
JDBC DriverManager initialized
registerDriver: driver[className=sun.jdbc.odbc.JdbcOdbcDriver,\
sun.jdbc.odbc.JdbcOdbcDriver@1fcc69]

Die Methode setLogWriter() ist die erste Methode, die die Klasse DriverManager benutzt. Daher bekommt der Klassenlader die Aufgabe, die Klasse DriverManager zu laden. setLogWriter() speichert dann das PrintWriter-Objekt in einer privaten Variablen und macht sonst nichts.

Erst das Laden eines Treibers führt zum Aufruf der statischen initialize()-Methode. Sie führt die private Methode loadInitialDrivers() aus, die zur ersten Ausgabezeile führt. Hier sind noch keine Treiber angemeldet, da in den Eigenschaften »jdbc.drivers« nichts eingetragen ist. Diese Eigenschaft wird in der Regel dann gesetzt, wenn von außen über den Schalter »-D« eine Klasse angesprochen wird. Nach dem Suchen in den Eigenschaften folgt die Ausgabe »JDBC DriverManager initialized«. Nun hat der Treiber die DriverManager-Klasse hochgefahren und der Treibermanager kann den Treiber anmelden. Der Treiber ist vom Typ Driver. Dieser wird zusammen mit dem zugehörigen Class-Objekt und einem Namen in der internen Klasse DriverInfo in einem internen Vector gespeichert. Die Ausgabe »registerDriver:[...]« kommt von der Anmeldung des Treibers. Wir sehen genau die Informationen, die in der Klasse DriverInfo gespeichert sind. Die Ausgabe wird auch von toString() von DriverInfo generiert.

Nicht nur Treiber und SQL-Klassen nutzen den Log-Stream, auch wir können Zeichenketten ausgeben. Dazu dient die statische Methode println(), die als Parameter nur einen String annimmt. println() ist so implementiert, dass bei einem nicht gesetztem Log-Stream die Ausgabe ausbleibt.


Galileo Computing

21.7.4 Den Treiber laden  downtop

Der Datenbanktreiber ist eine ganz normale Java-Klasse, die sich bei einem Treibermanager automatisch anmeldet. Unsere Aufgabe ist es nur, ein Treiber-Objekt einmal zu erzeugen. Um eine Klasse zur Laufzeit zu laden und so ein Laufzeit-Objekt zu erschaffen, gibt es mehrere Möglichkeiten. Eine davon geht über die native statische Methode forName() der Klasse Class. Die Syntax für das Laden der JDBC-ODBC-Bridge lautet somit:

Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver" );

Um einen Oracle-JDBC-Treiber zu laden, schreiben wir folgende Zeile:

Class.forName( "oracle.jdbc.driver.OracleDriver" );

Der Klassenname des Datenbanktreibers für mSQL-JDBC heißt com.imaginary. sql.msql.MsqlDriver. Um möglichst unabhängig zu bleiben, sollte die Klasse auch nicht hart einkodiert werden. Besser ist es, den Klassennamen in eine Property zu schreiben. Dennoch bleiben wir in unseren Beispiel bei einem einkodierten Treiber.

Class.forName( "com.imaginary.sql.msql.MsqlDriver" 
);

Da wir die Klasse nur Laden, aber die Referenz auf den Klassen-Deskriptor nicht benö-tigen, belassen wir es bei einem Aufruf und beachten den Rückgabewert nicht. Diese Operation löst eine ClassNotFoundException aus, falls die Klasse nicht gefunden wurde.

Die Klasse muss nicht zwingend zur Laufzeit geladen werden. Sie kann auch in der Kommandozeile über den Schalter »-D« eingebunden werden. Dazu setzen wir mit der Eigenschaft jdbc.drivers einen Datenbanktreiber fest.

java -Djdbc.drivers=sun.jdbc.odbc.JdbcOdbcDriver 
<Javaklasse>
final class java.lang.Class
implements Serializable

gp  static Class forName( String clazz ) throws ClassNotFoundException
Sucht, lädt und bindet die Klasse mit dem qualifizierten Namen clazz ins Laufzeitsystem ein. Es wird ein Class-Objekt zurückgegeben, falls die Klasse geladen werden kann, andernfalls wird dies mit einer ClassNotFoundException quittiert.

Galileo Computing

21.7.5 Wie Treiber programmiert sind  downtop

Wenn wir selbst einen Treiber schreiben müssten, würden wir zunächst einmal die Schnittstelle Driver implementieren. Dann müssten wir alle sechs Methoden programmieren. Zur Demonstration soll einmal ein fiktiver Treiber umgesetzt werden, der Treiber usql .

Einen Konstruktor brauchen wir nicht implementieren, da Class.forName() oder ähnliche Programmanweisungen die Klasse laden, das Objekt aber erst einmal nicht erzeugen.

import java.sql.*;
import java.util.Properties;

public class UsqlDriver implements Driver
{

Die erste Methode ist acceptsURL(), nach der später der Treibermanager entscheidet, ob ein Treiber eine Datenbank ansprechen kann oder nicht. Der Treibermanager wird alle Treiber mit einer URL fragen, ob sie den Job übernehmen. Daher muss acceptsURL() nun testen, ob unser usql-Treiber sich nun für eine Datenbank verantwortlich fühlt, wenn als Protokoll »usql« in der URL steht. Die URL beginnt immer mit dem Präfix »jdbc:«. Dahinter steht der Name des Treibers. Also testen wir die Stelle 5 bis 9 und schauen, ob dort »usql« steht. Wenn ja, dann sollten wir den Job übernehmen und true zurückgeben. Dann wird der Treibermanager nicht mehr andere Treiberklassen fragen, sondern uns den Auftrag erteilen.

 public boolean acceptsURL( String 
s ) throws SQLException
{
s = s.trim();

if ( s.length() < 11 )
return false;
else
return s.substring(5, 9).equals("usql");
}

Bekommen wir den Auftrag, dann dürfen wir die Verbindung aufbauen. Da wir allerdings auch hintenherum ohne den Treibermanager mittels connect() eine Verbindung aufbauen können, testen wir noch einmal zur Sicherheit, ob die URL wirklich uns meint. Ist alles in Ordnung, erzeugen wir ein UsqlConnection-Objekt, das an anderer Stelle programmiert werden muss.

 public synchronized Connection
connect( String s, Properties properties ) throws SQLException
{
if ( !acceptsURL(s) )
return null;
else
return new UsqlConnection( this, s, properties );
}

Die Klasse DriverPropertyInfo nimmt Informationen für die Verbindung auf, die von getPropertyInfo() abgefragt werden können. Wir wollen hier keine Eigenschaften angeben und nur ein leeres Feld zurückgeben.

 public DriverPropertyInfo[]
getPropertyInfo( String s, Properties prop )
throws SQLException
{
return new DriverPropertyInfo[0];
}
Abbildung

Zwei weitere Methoden erlauben den Programmierern die Treiber-Version zu erfragen. Da sich unser Treiber am Anfang seiner Entwicklung befindet, ist die Version 0.1. So etwas wie 0.001 funktioniert leider nicht, da der Rückgabewert immer ein Integer ist.

 public int getMajorVersion() 
{
return 0;
}
public int getMinorVersion() {
return 1;
}

Da wir ein echter JDBC-Treiber sind, der das komplette JDBC-API sowie SQL-92 unterstützt, geben wir bei jdbcCompliant() selbstbewusst true zurück.

 public boolean jdbcCompliant() 
{
return true;
}

Nun ist keine Methode mehr zu implementieren, und das Spannendste wird im statischen Initialisierungsblock gemacht.

 static {
try {
DriverManager.registerDriver( new UsqlDriver() );
}
catch ( SQLException e ) { System.out.println( e ); }
}
}

Wir sehen hier, dass der Treiber sich selbstständig im DriverManager anmeldet. Dazu bildet er ein Exemplar von sicht selbst über den Standard-Konstruktor. Sollte hier etwas schief gehen, dann fangen wir eine SQLException auf. Dies müssen wir auch, da uns die Methode registerDriver() vom Treibermanager diese Ausnahmebehandlung vorschreibt.


Galileo Computing

21.7.6 Verbindung zur Datenbank  downtop

Nun können wir eine Verbindung zur Datenbank mit Hilfe des Connection-Objekts aufbauen, welches von DriverManager.getConnection() zurückgegeben wird. Eine Verbindung wird mit speziellen Optionen parametrisiert, unter anderem mit dem Treiber, der die Datenquelle anspricht.

Die Datenquelle angeben

gp  Alle Datenquellen sind durch eine besondere URL qualifiziert, die folgendes Format besitzt:
jdbc:Subprotokoll:Datenquellenname
gp  Für ODBC-Datenquellen ist das Subprotokoll mit »odbc« zu detaillieren:
jdbc:odbc:Datenquellenname
gp  Eine URL für mSQL-JDBC hat immer das Format:
jdbc:msql://host[:port]/database

Verbindung aufnehmen

Die getConnection()-Methode liefert nun ein Connection-Objekt, das mit der Quelle verbunden ist. Die nachfolgende Anweisung verbindet uns mit einer Datenbank, die den Namen Pflanzen trägt. Diesen Namen haben wir im ODBC-Datenquellen-Administrator festgelegt.

con = DriverManager.getConnection( 
"jdbc:odbc:Pflanzen",
"user",
"passwd" );

Die Methode getConnection() erwartet bis zu drei Parameter: Die URL der Datenbank, zu der die Verbindung aufgenommen werden soll, ist der Pflichtparameter. Der Anmeldename und das Passwort sind optional. Der Benutzername und das Passwort können auch leere Strings ("") sein. Dieses Vorgehen findet bei Text-Dateien, die als ODBC-Quellen eingesetzt werden, Verwendung, da Texte keine solchen Attribute besitzen. Meldet getConnection() keinen Fehler, so liefert sie uns eine geöffnete Datenbankverbindung.

class java.sql.DriverManager

gp  static Connection getConnection( String url, Properties info ) throws SQLException
Versucht eine Verbindung zur Datenbank aufzubauen. Die Klasse DriverManager sucht dabei einen aus der Liste der registrierten JDBC-Treiber passenden Treiber für die Datenbank. Im Properties-Objekt sollten die Felder »user« und »password« vorhanden sein.
gp  static Connection getConnection( String url, String user, String password )
throws SQLException
Versucht eine Verbindung zur Datenbank aufzubauen. user und password werden zur Verbindung zur Datenbank verwendet.
gp  static Connection getConnection( String url ) throws SQLException
Versucht eine Verbindung zur Datenbank aufzubauen.
Abbildung

Wie der Treiber gefunden wird

Es lohnt sich, einmal hinter die Kulissen der Methode getConnection() zu schauen. Das DriverManager-Objekt wird veranlasst, die Verbindung zu öffnen. Dabei versucht es einen passenden Treiber aus der Liste der JDBC-Treiber auszuwählen. Seine Treiber verwaltet die Klasse DriverManager in einem privaten Objekt DriverInfo. Dieses enthält ein Treiber-Objekt (Driver), ein Objekt (securityContext) und den Klassennamen (className). Während getConnection() die Liste (intern als Vector implementiert) der DriverInfo-Objekte abgeht, versucht dieser sich über die connect()-Methode anzumelden. Merkt der Treiber, dass er mit der URL nicht viel anfangen kann, gibt er null zurück, und getConnection() versucht den nächsten Treiber. Ging alles daneben und keiner der angemeldeten Treiber konnte etwas mit dem Subprotokoll anfangen, bekommen wir eine SQLException("No suitable driver", "08001").

Verbindung beenden

Die Klasse DriverManager besitzt keine close()-Methode, wie wir erwarten können. Vielmehr kümmert sich das Connection-Objekt selbst um die Schließung. Würde der Garbage-Collector also das Objekt von der Halde räumen, schließt er automatisch die Verbindung. Wollen wir selbst das Ende der Verbindung herbeiführen, rufen wir

con.close();

auf und die Verbindung wird beendet. Auch hier kann eine SQLException auftauchen.

interface java.sql.Connection

gp  void close() throws SQLException
Schließt die Verbindung zur Datenbank.

Wartezeit einstellen

Wenn wir uns später mit der Datenbank verbinden, lässt sich noch eine Wartezeit einstellen. Diese Zeit in Sekunden gibt an, wie lange der Treiber für die Verbindung mit der Datenbank warten darf. Gesetzt wird dieser Wert mit setLoginTimeout() und entsprechend ausgelesen mit getLoginTimeout(). Standardmäßig ist dieser Wert 0.

class java.sql.DriverManager

gp  static void setLoginTimeout( int seconds )
Setzt die Zeit, die maximal gewartet wird, wenn der Treiber sich mit einer Datenbank verbindet.
gp  static int getLoginTimeout()
Liefert die Wartezeit in Sekunden.

Galileo Computing

21.8 Datenbankabfragen  downtop

Mit einer gelungenen Verbindung lassen sich nun SQL-Kommandos absetzen und die Datenbank steuern.


Galileo Computing

21.8.1 Abfragen über das Statement-Objekt  downtop

Für diese Abfragen ist ein Statement-Objekt anzulegen. JDBC bietet dazu die Methode createStatement() an, die eine SQLException auslösen kann. Dies ist eine Methode des Connection-Objekts.

Statement stmt = con.createStatement();
interface java.sql.Connection

gp  Statement createStatement() throws SQLException
SQL-Anweisungen ohne Parameter werden normalerweise über das Statement-Objekt ausgeführt. Wird das gleiche SQL-Statement mehrmals ausgeführt, lohnt es sich, ein PreparedStatement zu konstruieren.

SQL-Anweisungen ausführen

Um Informationen auszulesen, benutzen wir die SELECT-Befehle aus SQL und geben sie durch die executeQuery()-Methode des Statement-Interfaces an. Der Aufruf liefert uns die Ergebnisse als Zeilen in Form eines ResultSet-Objekts. Wir benutzen executeQuery() für Abfragen und executeUpdate() bei Update-, Insert- oder Delete-Operationen. Wieder dürfen wir das Auffangen von SQLException nicht vergessen.

String query = "SELECT * FROM Tabellenname;";
ResultSet rs = stmt.executeQuery( query );

An dieser Stelle sei noch einmal darauf hingewiesen, dass JDBC nicht in die Zeichenketten reinschaut, die es an den Treiber weiterleitet. Sind die SQL-Anfragen also falsch, lassen sich Fehler schwer finden. So kann zum Beispiel schon die falsche Groß- bzw. Kleinschreibung zu Fehlern in der Datenbank führen. Solche Fehler sind natürlich schwer zu finden. Daher bietet es sich an, zum Testen erst einmal die Kommandos auf der Konsole auszugeben. Insbesondere bei zusammengesetzten Ausdrücken finden sich dann schon die Fehler.

interface java.sql.Statement

gp  ResultSet executeQuery( String sql ) throws SQLException
Führt ein SQL-Statement aus, das ein einzelnes ResultSet-Objekt zurückgibt.

Galileo Computing

21.8.2 Ergebnisse einer Abfrage im ResultSet  downtop

Das Ergebnis einer Abfrage durch executeQuery() wird in einer Ergebnistabelle vom Typ ResultSet zurückgegeben. Mit Methoden von ResultSet lassen sich die unterschiedlichen Spalten ansprechen und die Zeilen auswerten. Betrachten wir ein Beispiel aus dem Demoprogramm, wo wir aus der Lieferantentabelle den Namen und die Adresse auslesen:

SELECT Lfr_Name, Adresse FROM LIEFERANTEN

Der Datantyp für den Pflanzenname ist als Text und der Preis als Zahl angegeben. Das Interface ResultSet bietet für jeden Datentyp eine entsprechende Methode getXXX() an – XXX ist der Datentyp. Da alle Spalten zusätzlich als String ausgelesen werden können, können wir einfach getString() verwenden. Daher auch im Programmtext:

System.out.println ( rSet.getString(1) 
+ "\n" +
rSet.getString(2) + "\n" );

Mit jeder der getXXX()-Funktionen lesen wir eine bestimmte Ergebnisspalte aus. Der numerische Parameter besagt, ob Spalte 1 oder 2 anzusprechen ist. Wird der Methode getXXX() ein String übergeben, so bestimmt dieser über den Namen der Spalte.

Ist die Abfrage über alle Elemente einer Zeile formuliert, zum Beispiel

SELECT * FROM LIEFERANTEN

so muss erst über Connection.getMetaData() die Struktur der Tabelle ermittelt werden. Erst dann können wir mit den angemessenen Methoden auslesen. Dazu später mehr.

Um das ResultSet auswerten zu können, müssen wir zunächst in die erste Zeile springen. Dies geschieht mit der next()-Methode vom ResultSet. Danach sind wir mit getXXX() in der Lage, die Spalten dieser Zeile auszuwerten. Um weitere Zeilen zu erhalten, nutzen wir wieder next(). Die Methode gibt false zurück, falls es keine neue Zeile mehr gibt. Die Abfragen befinden sich somit oft in einer while-Schleife.

while ( rSet.next() )
System.out.println ( rSet.getString(1) + "\n" +
rSet.getString(2) + "\n" );
interface java.sql.ResultSet

gp  String getString( int column ) throws SQLException
Liefert aus der aktuellen Zeile den Inhalt der Spalte column als String. Die erste Spalte ist mit 1 adressiert. Ist in der Tabelle der SQL-Eintrag NULL, so ist das Ergebnis der Methode auch null.
gp  String getString( String columnName ) throws SQLException
Liefert in der aktuellen Zeile den Inhalt der Spalte mit dem Namen columnName als String.
gp  boolean next() throws SQLException
Der erste Aufruf muss next() sein, damit der Cursor auf die erste Zeile gesetzt wird. Die folgenden Aufrufe setzen den Cursor immer eine Zeile tiefer. Ist der Eingabestrom von der vorangehenden Zeile noch geöffnet, wird dieser automatisch geschlossen.

Abfragen und Reaktion der Datenbank bei mSQL

mSQL führt die verschiedenen Anfragen in verschiedenen Threads aus. Daher kehrt die Anfrage executeQuery() direkt wieder zum Aurufer zurück. Ein anderer Thread füllt im Hintergrund die Daten, die dann mit next() geholt werden können. Wenn wir eine Zeile wünschen, die es noch nicht gibt, so blockiert die Methode. Doch nur so lange, bis die Zeile gelesen wurde.


Galileo Computing

21.9 Java und SQL-Datentypen  downtop

Jeder Datentyp in SQL hat einen mehr oder weniger passenden Datentyp in Java. So konvertiert der JDBC-Treiber bei jeder getXXX()-Methode diese zu einem Datentyp, aber auch nur dann, wenn diese Konvertierung möglich ist. So lässt er es nicht zu, bei einem kommenden String eine getInteger()-Methode auszuführen. Andersherum lassen sich alle Datentypen als String auslesen. Die folgende Tabelle zeigt die Übereinstimmungen. Einige SQL-Datentypen können durch mehrere Zugriffsmethoden geholt werden: ein INTEGER lässt sich mit getInt() oder getBigDecimal() holen und TIMESTAMP mit getDate(), getTime() oder getTimestamp().

Tabelle 21.4   Datentypen in SQL und ihre Entsprechung in Java
Java-Methode SQL-Typ
getInt() INTEGER
getLong() BIG INT
getFloat() REAL
getDouble() FLOAT
getBignum() DECIMAL
getBigDecimal() NUMBER
getBoolean() BIT
getString() VARCHAR
getString() CHAR
getAsciiStream() LONGVARCHAR
getDate() DATE
getTime() TIME
getTimestamp() TIME STAMP
getObject() jeder Typ

Beispiel Befinden sich in einem ResultSet Namen als Zeichenketten und Geburtsdaten als Date, dann liefert getString() und getDate() diese Informationen.
ResultSet result = stmt.executeQuery( 
"SELECT Name, GebTag FROM Personen" );
result.next();
String name1 = result.getString( "Name" );
Date whatDay1 = result.getDate( "GebTag" );


Galileo Computing

21.9.1 Die getXXX()-Methoden  downtop

Die nun folgenden Funktionen sind die getXXX()-Methoden der Klasse ResultSet. Sie existieren in zwei Ausführungen: Bei der ersten Variante ist eine Ganzzahl als Parameter aufgeführt. Dieser gibt die Spalte der Operation an. Sie beginnt immer bei 1. Die zweite Variante erlaubt den Namen der Spalte anzugeben. Alle Methoden können eine SQLException in dem Fall auslösen, dass etwas mit der Datenbank nicht stimmt. Der throws-Ausdruck ist also nicht mehr explizit angegeben. Ist ein Eintrag in der Datenbank mit NULL belegt, so liefert die Methode null zurück.

interface java.sql.ResultSet

gp  String getString( int Spalte | String )
Liefert den Wert in der Spalte als Java String.
gp  boolean getBoolean( int | String )
Liefert den Wert in der Spalte als Java boolean.
gp  byte getByte( int | String )
Liefert den Wert in der Spalte als Java byte.
gp  short getShort( int )
Liefert den Wert in der Spalte als Java short.
gp  int getInt( int | String )
Liefert den Wert in der Spalte als Java int.
gp  long getLong( int | String )
Liefert den Wert in der Spalte als Java long.
gp  float getFloat( int | String )
Liefert den Wert in der Spalte als Java float.
gp  double getDouble( int | String )
Liefert den Wert in der Spalte als Java double.
gp  BigDecimal getBigDecimal( int | String, int scale)
Liefert den Wert in der Spalte als java.lang.BigDecimal-Objekt.
gp  byte[] getBytes( int | String )
Liefert den Wert in der Spalte als Bytefeld. Es besteht aus uninterpretierten Rohdaten.
gp  Date getDate( int | String )
Liefert den Wert in der Spalte als java.sql.Date-Objekt.
gp  Time getTime( int | String )
Liefert den Wert in der Spalte als java.sql.Time-Objekt.
gp  Timestamp getTimestamp( int | String )
Liefert den Wert in der Spalte als java.sql.Timestamp-Objekt.
gp  InputStream getAsciiStream( int | String )
Die Methode gibt über einen InputStream Zugriff auf den Inhalt der Spalte. Nützlich ist dies für den Datentyp LONGVARCHAR. Der JDBC-Treiber konvertiert mitunter die Daten in das ASCII-Format.
gp  InputStream getBinaryStream( int | String )
Die Methode erlaubt, auf den Inhalt der Spalte als InputStream zuzugreifen. Nützlich ist dies für den Datentyp LONGVARBINARY. Der JDBC-Treiber konvertiert mitunter die Daten in das ASCII-Format. Bevor aus einer anderen Spalte Daten ausgelesen werden, müssen die Daten vom Stream gelesen werden. Ein weiterer Aufruf schließt selbstständig den Strom. Der Strom liefert Null beim Aufruf von available(), falls keine Daten anliegen.

Die Verwandtschaft von java.sql.Date und java.util.Date

java.sql.Date ist eine Erweiterung der Klasse java.util.Date. Da beide Klassen in verschiedenen Paketen vorkommen, kommt es beim herkömmlichen Import und dem anschließenden Zugriff – nur über den Klassennamen – zu ungewollten Verwechslungen. Denn woher sollte der Compiler bei einer Anweisung wie

Date d = new Date( 73,2,12 );

wissen, aus welchem Paket er die Klassen nutzen soll?

Ein weiteres Problem betrifft die Konvertierung der beiden Klassen. Wollen wir zum Beispiel eine Zeichenkette aus der Eingabe in eine Datenbank schreiben, dann haben wir das Problem, dass die Konvertierung mittels DateFormat nur ein java.util.Date liefert. Das Einzige, was uns bleibt, ist von der Klasse Date aus dem util-Paket mittels getTime() die Millisekunden seit dem 1. Januar 1970, 00:00:00 GMT zu holen. (Natürlich mit der Einschränkung, dass wir zeitlich nicht vor 1970 liegen.)

java.sql.Date sqlDate = new java.sql.Date( 
utilDate.getTime() );

Der Konstruktor von java.sql.Date() mit den Millisekunden ist auch der einzige Konstruktor, der nicht veraltet ist. Daneben hat die Klasse java.sql.Date aber noch drei andere Methoden:

class java.sql.Date.Date
extends Date

gp  static Date valueOf( String s )
Wandelt einen String im JDBC (also yyyy-mm-dd) in ein Date Objekt um.
gp  String toString()
Liefert das Datum im JDBC-Datenformat.
gp  void setTime( long date )
Setzt das Datum mit den Millisekunden.

Galileo Computing

21.10 Transaktionen  downtop

Transaktionen sind für Datenbanken ein sehr wichtiges Konzept, denn nur dadurch bleibt die Integrität der Daten erhalten. Transaktionen sind vergleichbar mit einer atomaren Ausführung bei Threads, mit dem Unterschied, dass die Datenbank in mitten einer gescheiterten Transaktion die bisher veränderten Werte rückgängig macht.

In der Standard-Verarbeitung in JDBC wird jede SQL-Anweisung für sich als Transaktion abgearbeitet. Dies nennt sich Auto-Commit. Um eine Folge von Anweisungen in einer Transaktion auszuführen, muss das Auto-Commit zurückgesetzt werden. Dann werden die Datenbankmanipulationen ausgeführt und die Transaktion abgeschlossen (commit) oder zurückgesetzt (rollback).

Beispiel Operationen sollen in einer Transaktion ausgeführt werden.
con.setAutoCommit( false );
// Datenbankmanipulationen machen
con.commit();
con.setAutoCommit( true );

Tritt ein Fehler auf, können wir mit con.rollback() die gestartete Transaktion zurücksetzen. Da ist es lohnenswert eine Ausnahmebehandlung zu schreiben, und im catch das rollback() einzusetzen.


Galileo Computing

21.11 Elemente einer Datenbank hinzufügen und aktualisieren  downtop

Aus einer geglückten Datenbankverbindung mit DriverManager.getConnection() lassen sich SQL-Befehle wie INSERT, UPDATE oder DELETE verwenden. Bisher haben wir executeQuery() benutzt, um Abfragen zu verfassen. Es lassen sich jedoch auch Einfügeoperationen vornehmen, denn Tabelleninhalte bleiben nicht unveränderlich.

Das SQL-Kommando INSERT dient zum Einfügen und UPDATE zum Aktualisieren von Daten. Damit Spalten verändert werden können, müssen wir in zwei Schritten vorgehen: Eine SQL-Anweisung mit einem UPDATE aufbauen und anschließend executeUpdate() aufrufen. Damit wird die Änderung wirksam. Dies ist eine andere Statement-Methode; bisher kannten wir nur executeQuery(). Neben den Methodennamen gibt es aber noch einen anderen Unterschied. executeUpdate() liefert als Rückgabewert ein int, welcher angibt, wie viele Zeilen von der Änderung betroffen sind.

Beispiel Folgende SQL-Anweisung ändert die Adresse eines Lieferanten:
String updateString = "UPDATE LIEFERANTEN 
SET Adresse =
'Uferstra
ße 80' WHERE Adresse LIKE 'Uferstrasse 78'";
stmt.executeUpdate( updateString );

Die Methode gibt uns immer zurück, wie viele Zeilen von der Änderungen betroffen sind. Sie ist Null, falls das SQL-Statement nichts bewirkt.

interface java.sql.Statement

gp  int executeUpdate( String sql ) throws SQLException
Führt eine SQL-Anweisung aus, die Manipulationen an der Datenbank vornimmt. Die SQL-Anweisungen sind in der Regel INSERT-, UPDATE- oder DELETE-Anweisungen. Zurückgegeben wird die Anzahl der veränderten Zeilen. Null, falls eine SQL-Anweisung nichts verändert hat.

Galileo Computing

21.11.1 Batch-Updates  downtop

Das Einfügen und Ändern von vielen Daten kostet aus dem Grund viel Zeit, da für jede Modifikation ein INSERT oder UPDATE über ein Statement-Objekt abgewickelt werden muss. Eine Verbesserung stellen Batch-Updates da, die in einem Rutsch gleich eine ganze Reihe von Daten zur Datenbank transferieren. Anstatt mit execute() und deren Varianten zu arbeiten, nutzen wir die Methode executeBatch(). Damit zuvor die einzelnen Aktionen dem Statement-Objekt mitgeteilt werden können, bietet die Klasse die Methoden addBatch() und clearBatch() an. Die Datenbank führt die Anweisungen in der Reihenfolge aus, wie sie im Batch-Prozess eingefügt wurden. Ein Fehler wird über eine BatchUpdate Exception angezeigt.

Beispiel Wir fügen einige Einträge der Datenbank als Batch hinzu. Sei con unser Connection-Objekt.
int updateCounts[] = null;

try
{
Statement s = con.createStatement();

s.addBatch( "INSERT INTO Lieferanten VALUES (x,y,z)" );
s.addBatch( "INSERT INTO Lieferanten VALUES (a,b,c)" );
s.addBatch( "INSERT INTO Lieferanten VALUES (d,e,f)" );

updateCounts =
s.executeBatch();
}
catch ( BatchUpdateException e ) { }
catch ( SQLException e ) { }

Nach dem Abarbeiten von executeBatch() bekommen wir als Rückgabewert ein int-Feld mit den Ergebnissen der Ausführung. Dies liegt daran, das in der Batch-Verarbeitung ganz unterschiedliche Anweisungen gemacht werden können und jede davon einen unterschiedlichen Rückgabewert verwendet.

Soll der gesamte Ablauf als Transaktion gewürdigt werden, so setzen wir im try-Block den AutoCommit-Modus auf false, damit nicht jede SQL-Anweisung als einzelne Transaktion gewertet wird. Im Falle eines Fehlers müssen wir im catch-Block ein Rollback ausführen. Übertragen wir dies auf das obere Beispiel, dann müssen nur die beiden Anweisungen für die Transaktion eingesetzt werden.

try
{
con.setAutoCommit( false );

Statement s .....
...
}
catch ( BatchUpdateException e )
{
con.rollback();
}

Galileo Computing

21.12 Vorbereitete Anweisungen (Prepared Statements)  downtop

Die SQL-Anweisungen, die mittels execute(), executeQuery() oder executeUpdate() an die Datenbank gesendet werden, haben bis zur Ausführung im Datenbanksystem einige Umwandlungen vor sich. Zuerst müssen sie auf syntaktische Korrektheit getestet werden. Dann werden sie in einen internen Ausführungsplan der Datenbank übersetzt und mit anderen Transaktionen optimal verzahnt. Der Aufwand für jede Anweisung ist messbar. Deutlich besser wäre es jedoch, eine Art Vorübersetzung für SQL-Anweisungen zu nutzen.

Diese Vorübersetzung ist eine Eigenschaft, die JDBC unterstützt und sich Prepared Statements nennt. Vorbereitet (prepared) deshalb, da die Anweisungen in einem ersten Schritt zur Datenbank geschickt werden und dort in ein internes Format umgesetzt werden. Später verweist ein Programm auf diese vorübersetzten Anweisungen und die Datenbank kann sie schnell ausführen, da sie in einem optimalen Format vorliegen. Ein Geschwindigkeitsvorteil macht sich immer dann besonders bemerkbar, wenn Schleifen Änderungen an Tabellenspalten vornehmen. Das kann durch die vorbereiteten Anweisungen schneller gemacht werden.


Galileo Computing

21.12.1 PreparedStatement-Objekte vorbeiten  downtop

Genauso wie ein Connection-Objekt eine Methode für ein Statement-Objekt anbietet, werden PreparedStatement-Objekte angelegt. Dazu dient dann eine Methode prepare Statement(). Als Parameter wird eine SQL-Zeichenkette übergeben, die den gleichen Aufbau wie etwa ein executeUpdate() hat. Einen Unterschied werden wir jedoch feststellen: Bei den normalen Statement-Objekten können wir dynamische Einträge einfach mit in den String einbauen. Dies geht bei vorbereiteten Anweisungen nicht mehr. Woher sollte auch die Anweisung wissen, was der Benutzer in seine Eingabemaske tippt? Damit jetzt auch eine vorbereitete Anweisung Parameter enthalten kann, werden in die Zeichenkette Platzhalter mit einem Fragezeichen eingefügt.

Beispiel Aufbau eines PreparedStatement-Objekts mit einem parametrisierten String
PreparedStatement updateLieferant 
= con.prepareStatement(
"UPDATE LIEFERANTEN SET Adresse = ? WHERE Adresse LIKE ?" );

Die Zeile instruiert die Datenbank die Zeile zu interpretieren, in das interne Format umzusetzen und vorbereitet zu halten. Im nächsten Schritt muss die Anweisung für die Platzhalter Werte einsetzen.

Abbildung


Galileo Computing

21.12.2 Werte für die Platzhalter eines PreparedStatement  downtop

Bevor die executeUpdate()-Methode die vorbereitete Anweisung abarbeitet, müssen die Platzhalter gefüllt werden. Dazu bietet das PreparedStatement-Objekt für die Datentypen jeweils eine setXXX()-Methode an, die den Wert für einen angegebenen Platzhalter setzt. So wird setInt(1,100) die Zahl 100 für das erste Fragezeichen einsetzen. Nach der Zuweisung ist das Objekt für die Ausführung bereit. executeUpdate() kann aufgerufen werden.

PreparedStatement updateLieferant 
= con.prepareStatement(
"UPDATE LIEFERANTEN SET Adresse = ? WHERE Adresse LIKE ?" );
updateLieferant.setString( 1, "Uferstraße 80");
updateLieferant.setString( 2, "Uferstrasse 78");
updateLieferant.executeUpdate();

Vergleichen wir diese Zeilen mit der Lösung ohne PreparedStatement:

String updateString = "UPDATE LIEFERANTEN 
SET Adresse =
'Uferstraße 80' WHERE Adresse LIKE 'Uferstrasse 78'";
stmt.executeUpdate( updateString );

Die Anweisung ist zwar etwas kürzer, aber dadurch mit der notwendigen Übersetzungszeit verbunden, insbesondere, wenn sich die Werte ändern. In einer Schleife lässt sich jetzt nun immer wieder executeUpdate() aufrufen und die schon gesetzten Parameter werden übernommen. Ein Aufruf von clearParameters() löscht alle Parameter.

PreparedStatement updateLieferant 
= con.prepareStatement(
"UPDATE LIEFERANTEN SET Adresse = ? WHERE Adresse LIKE ?" );
updateLieferant.setString( 1, "Uferstraße 80");
updateLieferant.setString( 2, "Uferstrasse 78");
updateLieferant.executeUpdate();

updateLieferant.setString( 1, "Sommerstraße 23");
updateLieferant.setString( 2, "Sommerstrasse 23");
updateLieferant.executeUpdate();

Galileo Computing

21.13 Metadaten  downtop

Von einer Datenbank können verschiedene Informationen ausgelesen werden. Zum einen sind dies Informationen zu einer bestimmten Tabelle, zum anderen sind dies Informationen über die Datenbank selbst.


Galileo Computing

21.13.1 Metadaten über die Tabelle  downtop

Metadaten können für jede Abfrage angefordert werden. So lassen sich unter anderem leicht herausfinden

gp  wie viele Spalten wir in einer Zeile abfragen können
gp  wie der Name der Spalte ist
gp  wie der SQL-Typ der Spalte ist
gp  wie viele Dezimalzeichen eine Spalte hat

Bei der Abfrage über alle Spalten müssen wir die Struktur der Datenbank kennen, besonders dann, wenn wir eine Abfrage machen und die passenden Daten herauslesen wollen. So liefert

SELECT * FROM PFLANZEN

ein ResultSet mit der gleichen Anzahl von Zeilen wie die Pflanzen-Tabelle. Doch bevor wir nicht die Anzahl und Art der Spalten kennen, können wir nicht auf die Daten zugreifen (oder alles muss als String herausgenommen werden). Um diese Art von Informationen, so genannte Metadaten, in Erfahrung zu bringen, befindet sich die Klasse ResultSetMetaData unter den SQL-Klassen, mit der wir diese Informationen herausfinden.

Bleiben wir bei den Pflanzen. Um die Anzahl und Art der Spalten herauszufinden, befragen wir das ResultSet, das vom SQL-Kommando SELECT * FROM PFLANZEN angelegt wird. Zunächst der Aufruf von executeQuery():

ResultSet result = stmt.executeQuery( 
"SELECT * FROM PFLANZEN" );

Nun können wir vom ResultSet ein ResultSetMetaData-Objekt bekommen. Dazu wird getMetaData() verwendet:

ResultSetMetaData meta = result.getMetaData();
interface java.sql.ResultSet

gp  ResultSetMetaData getMetaData() throws SQLException
Die Eigenschaften eines ResultSet werden in einem ResultSetMetaData zurückgegeben.

Nun bietet ResultSetMetaData viele Methoden, um Aussagen über die Tabelle und über die Spalten zu machen. So fragen wir mit getColumnCount() nach, wie viele Spalten die Tabelle hat:

int columns = meta.getColumnCount();

Anschließend lässt sich durch die Liste gehen und die Namen der Spalten ausgeben:

int numerics = 0;

for ( int i=1; i <= columns; i++ )
{
System.out.println( meta.getColumnLabel(i) + "\t\t\t" +

meta.getColumnTypeName(i) );

if ( meta.isSigned(i) )
numerics++;
}

System.out.println();
System.out.println( "Spalten: " + columns + " Numerisch: " + numerics );

Anschließend finden wir die Methoden vom ResultSetMetaData-Objekt aufgelistet. Alle Methoden können wieder eine SQLException schmeißen.

Listing 21.2   TableMetaData.java
import java.sql.*;

public class TableMetaData
{
public static void main( String args[] ) throws Exception
{
Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver" );

Connection conn;
Statement stmt;

conn = DriverManager.getConnection( "jdbc:odbc:Pflanzen", "", "" );
stmt = conn.createStatement();

String sqlQuery = "SELECT * FROM PFLANZEN";

ResultSet result = stmt.executeQuery( sqlQuery );

ResultSetMetaData meta = result.getMetaData();

int columns = meta.getColumnCount();
int numerics = 0;

for ( int i=1; i <= columns; i++ )
{
System.out.println( meta.getColumnLabel(i) + "\t\t\t" +
meta.getColumnTypeName(i) );

if ( meta.isSigned(i) )
numerics++;
}

System.out.println();
System.out.println( "Spalten: " + columns + " Numerisch: " + numerics );

stmt.close ();
conn.close ();
}
}
interface java.sql.ResultSetMetaData

gp  int getColumnCount()
Anzahl der Spalten im aktuellen ResultSet. Praktisch für Anweisungen wie SELECT *.

Allen folgenden Methoden wird ein int übergeben, welches die Spalte kennzeichnet.

gp  String getCatalogName( int )
Gibt den String mit dem Katalognamen der Tabelle für die angegebene Spalte zurück.
gp  getColumnName( int )
Liefert den Spaltennamen der Tabelle.
gp  int getColumnDisplaySize( int )
Maximale Anzahl der Zeichen, die die Spalte einnimmt. So ist bei einer Spalte vom Typ VARCHAR(11) mit einer maximalen Spaltenbreite von zehn Zeichen zu rechnen. Bei numerischen Spalten variiert der Wert.
gp  String getColumnLabel( int )
Gibt einen String zurück, der den Titel der angegebenen Spalte enthält. Der Titel gibt an, was als Überschrift für die Spalte angezeigt werden soll. Einige Datenbanken erlauben die Unterscheidung zwischen Spaltenname und Spaltentitel.
gp  int getColumnType( int )
Typ der Spalte wird ermittelt. Der Spaltentyp ist dabei eine Konstante aus der Klasse java.sql.Types. Sie definiert die Konstanten nach dem XOPEN-Standard. Die Reihenfolge der Datentypen ist: ARRAY_LOCATOR, BIGINT, BINARY, BIT, BLOB_LOCATOR, CHAR, CLOB_LOCATOR, DATE, DECIMAL, DISTINCT, DOUBLE, FLOAT, INTEGER, JAVA_OBJECT (benutzerdefinierter Datentyp), LONGVARBINARY, LONGVARCHAR, NULL, NUMERIC, REAL, REF, SMALLINT, STRUCT, STRUCT_LOCATOR, TIME, TIMESTAMP, TINYINT, VARBINARY, VARCHAR. Die Konstante OTHER zeigt ein datenbankspezifisches Element an und wird auf ein Java-Objekt abgebildet, falls ein Zugriff mittels getObject() oder setObject() erfolgt.
gp  String getColumnTypeName( int )
Name der Spalte, so wie er in der Datenbank definiert ist.
gp  int getPrecision( int )
Dezimalgenauigkeit der Spalte, zurückgegeben als Anzahl der Ziffern.
gp  int getScale( int )
Liefert die Genauigkeit der Spalte. Dies ist die Anzahl der Stellen, die nach dem Dezimalpunkt verwendet werden können.
gp  String getSchemaName( int )
Name des Tabellenschemas. Wird von den Methoden des DatabaseMetaData-Objekts benutzt. Falls kein Schema vorhanden ist, wird ”” zurückgegeben.
gp  String getTableName( int)
Tabellenname der angegebenen Spalte.
gp  boolean isAutoIncrement( int )
Feststellen, ob eine Spalte eine AutoIncrement-Spalte ist. Diese nimmt dann automatisch den nächsten freien Wert an, wenn ein neuer Datensatz eingefügt wird. Ist die erste Zeile einer Tabelle mit einer AutoIncrement-Spalte eingefügt, so nimmt die Spalte den Wert 1 an. In den meisten Datenbanken ist es allerdings nicht möglich, eigene Werte in diesen Spalten einzutragen.
gp  boolean isCaseSensitive( int )
Berüchsichtigt die Spalte die Groß- bzw. Kleinschreibung?
gp  boolean isCurrency( int )
Enthält die Spalte Geldwerte? Nur einige Datenbanken bieten diesen Spaltentyp.
gp  boolean isNullable( int )
Ist ein NULL in der Spalte erlaubt?
gp  boolean isSearchable( int )
Kann die Spalte in einer SQL-WHERE-Klausel verwendet werden?
gp  boolean isSigned( int )
Enthält die Spalte vorzeichenbehaftete Datentypen? Vorzeichenbehaftete Typen sind unter anderem INT, LONGINT, SMALLINT. Vorzeichenlose Type sind unter anderem UINT, ULONG, UBYTE.
gp  boolean isReadOnly( int )
Kann auf die Spalte wirklich nicht schreibend zugegriffen werden? Ist das Ergebnis true, kann der Wert nicht aktualisiert werden.
gp  boolean isDefinitelyWritable( int )
Kann auf die Spalte definitiv schreibend zugegriffen werden?
gp  boolean isWritable( int )
Ist es möglich, auf die Spalte schreibend zuzugreifen? Der Unterschied zu isDefinitelyWritable() ist, dass isWritable() unter Umständen aktualisiert, während isDefinitelyWritable() dies fest bestimmt.

Galileo Computing

21.13.2 Informationen über die Datenbank  downtop

Metadaten sind auch für die gesamte Datenbank abfragbar. Beispiele für diese Informationen sind:

gp  Wer ist mit der Datenbank verbunden?
gp  Kann die Datenbank nur gelesen oder auch in die Datenbank geschrieben werden?
gp  Sind gepeicherte Prozeduren auf der Datenbankseite erlaubt?

Sind Informationen über die Datenbank gefragt, so lassen sich über Metadaten eines DatabaseMetaData-Objekts beispielsweise Datenbankeigenschaften des Herstellers herausfinden. Zunächst benötigen wir dazu ein DatabaseMetaData-Objekt. Dies bekommen wir über die Methode getConnection() des Treibermanagers.

Connection conn = DriverManager.getConnection( 
"jdbc:odbc:Pflanzen", "", "" );
DatabaseMetaData meta = con.getMetaData();

getMetaData() gibt ein DatabaseMetaData-Objekt zurück, das eine große Anzahl von Methoden erlaubt. So erfragt nachfolgendes Programmstück die Datenbank nach ihrem Produktnamen und einigen weiteren Attributen:

Listing 21.3   DBMetaData.java
import java.sql.*;

public class DBMetaData
{
public static void main( String args[] ) throws Exception
{
Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver" );

Connection conn =
DriverManager.getConnection( "jdbc:odbc:Pflanzen", "", "" );

DatabaseMetaData meta = conn.getMetaData();

System.out.println( "ProduktnameDatabase : " +
meta.getDatabaseProductName() );

System.out.println( "Version der Datenbank : " +
meta.getDatabaseProductVersion() );

System.out.println( "Anzahl erlaubter Verbindungen : " +
meta.getMaxConnections() );

System.out.println( "Version des DB-Treibers : " +
meta.getDriverVersion() );

System.out.println( "Update im Batchbetrieb : " +
meta.supportsBatchUpdates() );

System.out.println( "Gespeicherte Prozeduren : " +
meta.supportsStoredProcedures() );

conn.close ();
}
}

Galileo Computing

21.14 Die Ausnahmen bei JDBC  downtop

Unter JDBC sind drei Arten von Fehlern möglich:

gp  SQLExceptions
Die Klasse SQLException ist Basisklasse aller JDBC-Exceptions. Sie enthält über den Fehler die folgenden Informationen: Eine Fehlerbeschreibung sowie eine weitere Beschreibung, die den XOPEN SQL-Status (beschrieben in der SQL-Spezifikation) angibt, und zuletzt eine zusätzliche Ganzzahl, die vom Datenbanktreiber kommt.
gp  SQLWarnings
Sie Klasse SQLWarning beschreibt nicht so kritische Fehler. Es ist auch keine Exception, die geworfen wird, sondern eine Warnung, die der Programmierer explizit holen muss. Dazu dienen die Funktionen getWarnings() der Klassen Connection, ResultSet und Statement. Werden die Meldungen nicht geholt, dann werden sie mit Connection, ResultSet oder Statement überschrieben.
gp  DataTruncation
Die DataTruncation-Klasse ist ein spezieller Typ einer SQL-Warnung. Sie wird immer dann erzeugt, wenn Daten während Schreib- oder Lese-Operationen verloren gingen. Die Meldung wird genauso geholt wie SQLWarning, nur muss dann, um das Ergebnis zu erfahren, mittels instanceof DataTruncation abgeprüft werden, ob es sich um DataTruncation handelt. Dies erfordert eine Typumwandlung von SQLWarning auf DataTruncation.
Abbildung


Galileo Computing

21.15 Java Data Objects (JDOtoptop

Es stellt sich immer wieder die Frage, wie Java-Objekte in einer relationalen oder objektorientierten Datenbank untergebracht werden. Die beste Lösung wäre, eine Zwischenschicht zu besitzen, die Java-Objekten in eine beliebige Datenbank schreibt. Dabei ist Serialisierung eine Möglichkeit. Das Objekt wird persistent, indem es als serialisierter Bytestrom in die Datenbank geschrieben wird. Eine andere Möglichkeit ist das objektrelationale Mapping. Die Eigenschaften der Objekte werden auf Relationen abgebildet und Objektverweise (Referenzen) durch Schlüsselbeziehungen zwischen den Relationen.

Eine andere Möglichkeit definiert ein neuer Standard mit dem Namen JDO. Die Abkürzung steht für Java Data Objects und die Spezifikation beschreibt ein herstellerunabhängiges Framework zur persistenten Speicherung von Java-Objekten in transaktionalen Datenspeichern. Die Spezifikation wurde im Mai 2001 von bekannten Firmen wie Sun, IBM und Apple formuliert. JDO definiert eine einheitliche Schnittstelle für den Zugriff auf persistente Daten, wobei die physikalische Speicherung ziemlich egal ist. Die Objektinformationen können in Dateien, Datenbanken oder sonstigen Systemen abgespeichert werden. Mit Hilfe von JDO kann der Programmierer Datenobjekte ohne Kenntnis der Speichermechanismen bearbeiten. Dies ist für die Entwicklung großer Systeme ein deutlicher Vorteil, denn die Entwickler müssen sich nicht näher mit den Interna von Datenbanken herumärgern, sie können sich auf die reine Applikationslogik konzentrieren. Die einzelnen Herstellern, »JDO-Vendor« genannt, implementieren eine Speichermöglichkeit für ihr System. Ein Hersteller, der die JDO-Spezifikation schon umgesetzt hat, ist der Datenbankanbieter Poet mit der Fast Objects-API.






1    Microsoft und Open? Nicht schlecht.

2    Vergleich zwischen InterBase und MS-SQL unter http://www.sphere-data.com/docs/ ib_vs_ss.html.

3    Entwickler können Code modifizieren, ohne die Änderungen wieder als Open Source freigeben zu müssen. Dies verlangt z. B. die Gnu Public Licence (GPL). Eine deutsche Webseite um dieses Thema herum ist etwa http://www.interbase2000.de.

4    Weitere Informationen finden sich auf der Webseite http://www.jepstone.net/FreeODBC.

5    Ullis SQL, sprich uskel.

  

Java 2




Copyright © Galileo Press GmbH 2002
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, fon: 0228.42150.0, fax 0228.42150.77, info@galileo-press.de