JAVA zum Zugriff auf Datenbanken JDBC

Werbung
JAVA zum Zugriff auf Datenbanken
JDBC
Sven Frimont
28. Mai 2001
Zusammenfassung
Nach einer kurzen Darstellung des JDBC-API, geht dieser Seminarbeitrag generell auf die
Kopplung von konventionellen Programmiersprachen mit Datenbanksprachen ein. Es werden verschiedene Kopplungsmöglichkeiten aufgezeigt, und festgestellt, wie sich Java in
diese Konzepte einordnen lässt. Im Anschluss daran werden grundlegende Klassen und
Interfaces des JDBC-API kurz vorgestellt.
Ein weiterer Schwerpunkt dieses Seminarbeitrages behandelt die, für die Verbindung zu
einer Datenbank notwendigen, JDBC-Treiber. Es werden die vier möglichen Treibertypen
vorgestellt und deren Vor- und Nachteile besprochen. Danach werden alle Schritte erläutert,
die notwendig sind um eine SQL-Anweisung an eine Datenbank zu senden und ggfls. das
Ergebnis dieser Anweisung (im Fall einer Anfrage) zu verarbeiten.
Zum Abschluss geht es um die Techniken, die einen Programmierer dabei unterstützen
können eine DBMS-unabhängige Anwendung zu schreiben.
1
Inhaltsverzeichnis
1 Einleitung
4
2 Was ist JDBC?
4
3 Die Kopplung von Java und einem DBMS
5
3.1
Verschiedene Kopplungsmöglichkeiten . . . . . . . . . . . . . . . . . . . . . .
5
3.2
Welche dieser Kopplungsarten betreffen Java und JDBC? . . . . . . . . . . . .
6
4 JDBC-Treiber
6
5 Grundlegende Klassen und Interfaces des JDBC-API
7
5.1
Eine wichtige Klasse des JDBC-API . . . . . . . . . . . . . . . . . . . . . . .
8
5.2
Grundlegende Interfaces des JDBC-API . . . . . . . . . . . . . . . . . . . . .
8
6 Der Aufbau einer Verbindung
6.1
Aufbau einer Verbindung über die Klasse DriverManager
. . . . . . . . . . .
9
Die Verwaltung der verfügbaren Treiber . . . . . . . . . . . . . . . . .
10
Aufbau einer Verbindung mit der treiberspezifischen Implementierung des Interfaces DataSource . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
6.2.1
Erstellen und registrieren eines neuen DataSource-Objekts . . . . . . .
12
6.2.2
Aufbau einer Verbindung zu einer durch ein DataSource-Objekt
repräsentierten Datenquelle . . . . . . . . . . . . . . . . . . . . . . .
12
6.1.1
6.2
9
7 Übermittlung einer SQL-Anweisung an das DBMS
13
8 Der Zugriff auf das Ergebnis einer SQL-Anfrage
14
8.1
Das Cursor-Konzept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
8.2
Abbildung zwischen Java- und JDBC-Datentypen . . . . . . . . . . . . . . . .
15
9 Erweiterungen des Statement-Interface
15
9.1
Das PreparedStatement-Interface . . . . . . . . . . . . . . . . . . . . . . . . .
16
9.2
Das CallableStatement-Interface . . . . . . . . . . . . . . . . . . . . . . . . .
16
10 Transaktionssteuerung
17
2
11 DBMS-Unabhängige Programmierung
17
11.1 SQL-Escapes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
11.2 Informationen über das DBMS: Das DatabaseMetaData-Interface . . . . . . .
18
12 Abschließende Anmerkungen
19
13 Zusammenfassung und Bewertung
19
3
1 Einleitung
Das Internet wird zur Beschaffung von Informationen immer wichtiger. Da in vielen Unternehmen umfangreiche Datenbanken vorhanden sind, stellt sich die Frage, wie auf diese Datenbanken über das Internet oder ein Intranet, also der Internet-Technik im unternehmensinternen
Netz, zugegriffen werden kann. Java in Verbindung mit dem JDBC-API ermöglicht einen Zugriff auf Datenbanken und kann zum Erstellen von Applikationen oder Java-Applets eingesetzt
werden.
Für ein Programm, das über das Internet auf Datenbanken zugreift, ist die Plattformunabhängigkeit eine besonders lohnenswerte Eigenschaft, da innerhalb des Internets unterschiedliche Plattformen miteinander kommunizieren und ein erhöhter Aufwand bei der Entwicklung
entsteht, wenn für jede dieser Plattformen eine eigene Programm-Version entwickelt werden
muß. Eine Programmiersprache, die es ermöglicht plattformabhängige Programme zu schreiben, ist Java. Ein Programm, das in Java geschrieben ist, läuft auf jeder Plattform, für die eine
Java Virtual Machine (JVM) verfügbar ist.
Ein weiterer Vorteil für ein solches Programm wäre eine Unabhängigkeit von einem konkreten DBMS. Für den Zugriff auf SQL-Datenbanken aus einer Programmiersprache heraus, bieten
viele DBMS-Hersteller eine Programmierschnittstelle für ihr System an. Software-Lösungen
werden oft für ein konkretes DBMS erstellt, und sind dann von diesem System abhängig. Die
Programmierschnittstellen verschiedener DBMS sind meistens nicht zueinander kompatibel, so
dass ein die Portierung des Programms auf ein anderes DBMS sehr aufwendig wäre.
JDBC bietet die Möglichkeit ein Programm so zu schreiben, das bei einem späteren Wechsel
des DBMS im Idealfall keine Änderungen des Quelltextes notwendig sind.
2 Was ist JDBC?
JDBC ist das Standard API von Java zum Zugriff auf relational gespeicherte Daten über die
Datenbanksprache SQL.
Durch das JDBC-API werden die folgenden Funktionalitäten zur Verfügung gestellt:
1. Aufbauen einer Verbindung mit einer Datenquelle
2. SQL-Anfragen und Update-Anweisungen an die Datenquelle senden
3. Zugriff auf die Ergebnisse einer SQL-Anfrage
Diese Daten sind in den meisten Fällen in einem relationalen DBMS gespeichert, können aber
auch in anderer Form, z. B. Dateien die relationale Daten enthalten, gespeichert sein. Durch
das Treiber-Konzept (vgl. Abschnitt 4) ist es bei der Benutzung von JDBC möglich von einer konkreten Datenquelle zu abstrahieren, d.h. im Idealfall läuft ein Programm das mit einer
Oracle-Datenbank arbeitet auch mit einer Sybase-Datenbank oder einer anderen Datenquelle,
für die ein JDBC-Treiber existiert .
Das JDBC-API besteht aus Klassen und Interfaces. In Kapitel 5 werden davon die grundlegenden erläutert.
4
3 Die Kopplung von Java und einem DBMS
Wenn von einer Programmiersprache aus auf eine Datenbank zugegriffen werden soll, geschieht
dies über eine Schnittstelle zwischen der Sprache und der Datenbank. Nachdem in Abschnitt
3.1 verschiedene Möglichkeiten zur Kopplung einer Programmiersprache und einer Datenbank
aufgezeigt werden, betrachtet Abschnitt 3.2 den Zugriff auf eine Datenbank über das JDBCAPI.
3.1 Verschiedene Kopplungsmöglichkeiten
Prinzipiell gibt es mehrere Möglichkeiten, eine Datenbank an eine konventionelle Programmiersprache zu koppeln [SS00]:
Der Hersteller des DBMS liefert dem Programmierer eine Bibliothek von Prozeduren,
mit welchen dieser auf die Datenbank zugreifen und die Daten manipulieren kann. Eine
Schnittstelle dieser Art wird als prozedurale Schnittstelle bezeichnet.
Zur Datenbankanfrage, oder der Manipulation von Daten im DBMS, wird eine Datenbanksprache, z. B. SQL, verwendet. Auf welche Weise ein Befehl zum DBMS gesendet
wird und wie das Ergebnis einer Anfrage im Programm weiter verwendet werden kann,
hängt von der Technik der Einbettung ab. Zwei Arten der Einbettung können unterschieden werden:
– statische Einbettung
SQL-Anweisungen werden in einer speziellen Notation in den Quelltext eingefügt.
Bevor der Compiler das eigentliche Programm übersetzt, wandelt ein Vorübersetzer
(engl. Precompiler) alle SQL-Anweisungen in Prozeduraufrufe um, welche dann
für den Compiler verständlich sind. Die Anweisungen müssen also feststehen, bevor
das Programm übersetzt wird. Mit dieser Technik der Einbettung ist es daher nicht
möglich eine Anfrage zur Laufzeit zu konstruieren. Da bei vielen Anfragen die
Suchkriterien allerdings erst zur Laufzeit feststehen, reicht diese Möglichkeit der
Einbettung oft nicht aus.
– dynamische Einbettung
Im Gegensatz zur statischen Einbettung können bei dieser Technik SQLAnweisungen zur Laufzeit konstruiert werden. Die Anweisungen werden dabei in
Form einer Zeichenkette an das DBMS übermittelt. Die richtige Syntax der SQLAnweisungen kann daher, im Gegensatz zur statischen Einbettung, vom Compiler
nicht überprüft werden. Evtuelle Syntaxfehler machen sich hier erst zur Laufzeit
bemerkbar.
Eine bestehende Programmiersprache wird um Datenbankfunktionalität erweitert, oder es
wird eine neue Sprache entwickelt. Die so entstandenen Sprachen brauchen ihre eigenen
Compiler und Werkzeuge, bereits existierende Compiler können, im Gegensatz zum Fall
der oben beschriebenen Einbettung einer Datenbanksprache, nicht eingesetzt werden.
5
Java-Anwendung
JDBC-Treibermanager
JDBC-ODBCBridge-Treiber
Native-APITreiber
ODBC-Treiber
ClientBibliothek
JDBC-NetTreiber
ClientBibliothek
Typ 1
NativeProtokollTreiber
Middleware
Typ 2
Typ 3
Typ 4
Abbildung 1: Verschiedene JDBC-Treibertypen
3.2 Welche dieser Kopplungsarten betreffen Java und JDBC?
Um über das JDBC-API auf ein DBMS zuzugreifen stellt der JDBC-Treiber dem Programmierer eine Reihe von Methoden zur Verfügung. Diese Methoden ermöglichen den Abruf von
Informationen über die Datenbank und das Senden von SQL-Anweisungen zur Anfrage und
Manipulation der Daten. Die Anweisungen werden in Form einer Zeichenkette an das DBMS
übermittelt und können daher auch dynamisch zur Laufzeit konstruiert werden.
JDBC kombiniert also eine prozedurale Schnittstelle (wobei die Methoden vom
JDBC-Treiber bereitgestellt werden) mit einer dynamischen SQL-Einbettung.
4 JDBC-Treiber
Um mit einem in Java geschriebenen Programm über das JDBC-API auf eine konkrete Datenbank zuzugreifen, wird für das dazugehörige DBMS ein sogenannter JDBC-Treiber benötigt.
JDBC-Treibertypen
JDBC kennt vier verschiedene Treibertypen (siehe auch Abb. 1) ( siehe z. B. in [EHF01]):
Typ 1: JDBC-ODBC-Bridge-Treiber
Bei diesem Treiber-Typ können die für viele DBMS bereits vorhandenen ODBC-Treiber
verwendet werden. Alle JDBC-Aufrufe werden dabei in ODBC-Aufrufe umgewandelt.
Der Vorteil ist, das auch DBMS angesteuert werden können, für die kein spezieller JDBCTreiber vorhanden ist. Dieser Treiber-Typ braucht allerdings Binärcode (externe Bibliotheken) und kann dadurch nicht in Java-Applets verwendet werden. Ein weiterer Nachteil
ist, das auf jedem Rechner, auf dem der Treiber eingesetzt werden soll, eine ODBCInstallation vorhanden sein muß.
6
Typ 2: Native-API-Treiber
Der JDBC-Treiber nutzt Bibliotheken, die vom DBMS-Hersteller angeboten werden.
Auch dieser Treibertyp kommt nicht ohne Binärcode aus und kann daher beispielsweise
nicht in Java-Applets verwendet werden. Durch den direkten Zugriff auf die vom Hersteller angebotenen Bibliotheken ist dieser Treiber in der Regel allerdings sehr effizient.
Typ 3: JDBC-Net-Treiber
Zwischen JDBC-Treiber und DBMS wird eine zusätzliche datenbankunabhängige
Komponente (Middleware) eingeführt. Der Treiber kommuniziert über ein DBMSunabhängiges Protokoll mit der Middleware, er kann also auf der Client-Seite völlig unabhängig von einem konkreten DBMS gestaltet und vollständig in Java implementiert
werden. Ein Treiber dieser Art eignet sich daher für den Einsatz in Java-Applets. Zwischen der Middleware und einer konkreten DBMS wird ein DBMS-abhängiges Protokoll
verwendet.
Weil Middlware und DBMS auf demselben Rechner laufen können, ist es auf diese Weise
möglich ein von Haus aus nicht netzwerkfähiges DBMS über das Internet anzusteuern.
Typ 4: Native Protokoll Treiber
Ein Treiber dieser Art kommuniziert direkt mit einer netzwerkfähigen Datenbank im jeweiligen, DBMS-spezifischen Protokoll. Da in diesem Fall der Client direkt mit dem
Server verbunden ist, erlaubt diese Treibervariante einen effizienten Datenbankzugriff.
Ein Treiber dieses Typs wird meistens, aufgrund des DBMS-spezifischen Protokolls, direkt von den DBMS-Herstellern angeboten.
Um auf DBMS zuzugreifen sollten möglichst Treiber des Typs 3 und 4 verwendet werden.
Treiber vom Typ 1 und 2 sind Übergangslösungen, die nur benutzt werden sollten solange
noch keine anderen JDBC-Treiber für das anzusteuernde DBMS verfügbar sind. Vor allem
die JDBC-ODBC-Bridge hat jedoch zur schnellen Verbreitung von JDBC beigetragen, da sich
viele Datenbanken über ODBC ansteuern lassen, für die noch kein spezieller JDBC-Treiber
verfügbar ist.
5 Grundlegende Klassen und Interfaces des JDBC-API
Bevor in diesem Kapitel einige grundlegende Klassen und Interfaces des JDBC-API beschrieben werden, werde ich kurz erklären was ein Interface von einer, auch aus anderen objektorientierten Programmiersprachen bekannten, abstrakten Klasse unterscheidet.
Ein Interface ist eine Sammlung von noch nicht implementierten, also abstrakten, Methoden. Es gibt allerdings einige wesentliche Unterschiede zu einer abstrakten Klasse:
Ein Interface kann keine Methoden implementieren, in einer abstrakten Klasse ist dies
möglich.
Eine Klasse kann mehrere Interfaces implementieren, aber nur von einer Klasse erben
(Mehrfachvererbung ist in Java nicht möglich).
Ein Interface gehört nicht zur Klassenhierarchie. Nicht verwandte Klassen können dasselbe Interface implementieren.
7
5.1 Eine wichtige Klasse des JDBC-API
In einer konkreten Klasse sind alle Methoden bereits implementiert. Da es mit dem JDBC-API
möglich ist auf verschiedene DBMS zuzugreifen, gehören zur JDBC-API nur Klassen, deren
Methoden nicht speziell für ein konkretes DBMS implementiert werden müssen. Z. B. die
Klasse DriverManager welche eine Liste der verfügbaren JDBC-Treiber verwaltet und einen
passenden Treiber auswählt, um eine Verbindung zu der gewünschten Datenbank herzustellen.
Für einfache Programme ist aus dieser Klasse lediglich die Methode DriverManager.getConnection von Bedeutung. Wie der Name bereits suggeriert, wird diese Methode benutzt um eine Verbindung (ein Connection-Objekt) zu der gewünschten Datenbank herzustellen.
Siehe dazu Abschnitt 6.1.
Weitere Klassen aus dem JDBC-API können zum grundlegendenVerständnis des JDBC-API
erstmal vernachlässigt werden und werden deshalb an dieser Stelle nicht weiter betrachtet.
5.2 Grundlegende Interfaces des JDBC-API
JDBC definiert eine Reihe von Interfaces und gibt damit Methoden vor, die jeweils spezifisch
für ein bestimmtes DBMS implementiert werden müssen.
Die Implementierung dieser Interfaces ist die Aufgabe des JDBC-Treibers.
Ein JDBC-Treiber ist eine Sammlung von Klassen, die unter anderem die Interfaces
des JDBC-API implementieren, zur Ansteuerung einer konkreten Datenquelle.
Eine Art Wurzelklasse für den Treiber stellt das Interface Driver dar. Es enthält, neben einer
Methode die eine Instanz der treiberspezifische Implementierung des Connection-Interfaces zurückgibt, noch fünf weitere Methoden, die Informationen über den Treiber, und Informationen
die notwendig sind um die Verbindung aufzubauen, liefern. Die Methoden dieser Klasse werden meistens von der Klasse DriverManager aufgerufen, ohne dass der Programmierer etwas
davon mitbekommt (siehe auch Abschnitt 6.1). Diese Methoden dieser Klasse sind daher nur
für denjenigen interessant, der einen neuen JDBC-Treiber entwickeln oder einen bestehenden
verändern möchte.
Das Interface Connection repräsentiert eine Verbindung mit einer Datenbank. Um SQLAnfragen an die Datenbank zu übermitteln, muß der Benutzer hier mit der Methode createStatement eine Instanz der treiberspezifischen Implementierung des Statement-Interfaces für
diese Verbindung anfordern.
Das zurückgelieferte Statement-Objekt wird benutzt um eine SQL-Anweisung an die Datenbank zu senden. Es ist dabei eindeutig der Connection zugeordnet, von der es angefordert
wurde. Wenn eine SQL-Anfrage liefert eine Instanz der treiberspezifischen Implementierung
des ResultSet-Interfaces zurück, die den Zugriff auf das Ergebnis der Anfrage ermöglicht (siehe
auch Kapitel 8).
Neben den eigentlichen Daten verwaltet ein DBMS im allgemeinen noch Informatiionen
über diese Daten, wie z. B. die definierten Tabellen und Sichten sowie deren Aufbau. JDBC
stellt zwei Schnittstellen für die Repräsentation dieser Metadaten zur Verfügung. Das Interface
8
Connection
createStatement
Statement
Statement
executeQuery
ResultSet
ResultSet
Abbildung 2: Grundlegende Objekte von JDBC
ResultSetMetaData definiert Methoden um Informationen über Datentypen und Eigenschaften der Spalten eines ResultSet-Objekts abzurufen. Das Interface DatabaseMetaData ermöglicht es, Informationen über die komplette Datenbank abzurufen, und wird vorwiegend von
JDBC-Treibern und Werkzeug-Entwicklern verwendet. Ein JDBC-Treiber implementiert dieses Interface so, dass jede Methode eine korrekte Antwort für die vom Treiber angesprochene
Datenbank gibt. Werkzeug-Entwickler benutzen die DatabaseMetaData-Methoden um herauszufinden, wie ihr Programm die Datenbank ansprechen soll. Wenn nur SQL-Befehle zu einer
bereits bekannten Datenbank gesendet werden sollen, kann dieses Interface im Allgemeinen
ignoriert werden. Ein Programmierer kann dieses Interface jedoch verwenden, wenn er ein
DBMS-unabhängiges Programm schreiben möchte. Näheres dazu in Kapitel 11.
In JDBC Version 2.0 wurde das Interface DataSource eingeführt. Eine DataSource (“Datenquelle”) repräsentiert eine Möglichkeit Daten zu speichern. Dies kann in einfacher Form
eine Datei mit Zeilen und Spalten oder, in einem aufwendigeren Fall, ein DBMS sein.
Abbildung 2 stellt den Zusammenhang der Objekte Connection, Statement und ResultSet
anschaulich dar.
6 Der Aufbau einer Verbindung
Um eine Verbindung zu einem DBMS aufzubauen gibt es prinzipiell zwei Vorgehensweisen.
Der Aufbau einer Verbindung mit Hilfe der Klasse DriverManager ist bis zur Version 2.0 von
JDBC der von den JDBC-Entwicklern empfohlene Weg (Abschn. 6.1). Seit der Version 2.0 gibt
es die Möglichkeit Objekte vom Typ DataSource (siehe auch Kapitel 5) über das JNDI (Java
Naming and Directory Interface) API an einen logischen Namen zu binden (Abschn. 6.2).
6.1 Aufbau einer Verbindung über die Klasse DriverManager
Die Klasse DriverManager wählt für eine Verbindung anhand einer übergebenen JDBC-URL
den passenden JDBC-Treiber aus und sorgt dafür, das die Verbindung hergestellt wird. Dazu
verwaltet der DriverManager eine Liste aller vorhandenen Treiber.
Obwohl es möglich ist, den passenden JDBC-Treiber von Hand auszuwählen und die Verbindung über die Methode Driver.connect herzustellen, ist es im Normalfall sinnvoller die Klasse DriverManager diese Aufgabe ausführen zu lassen.
9
Subprotokoll
Treiber-Art
Oracle Instanz
JDBC:ORACLE:THIN:@polaris:1521:ORCL
Netzadresse
Portnummer
Abbildung 3: Eine JDBC-URL
Abbildung 3 zeigt ein Beispiel einer JDBC-URL, wie sie der Methode DriverManager.getConnection( String url, “login”,”passwort”), neben dem Benutzernamen für die Datenbank und dem dazugehörigen Passwort, übergeben werden könnte. Eine JDBC-URL beginnt in
jedem Fall mit “JDBC:“, danach folgen Informationen, die der JDBC-Treiber benötigt um eine
Verbindung herzustellen.
Die Methode DriverManager.getConnection leitet die übergebene URL an den ersten im
DriverManager registrierten JDBC-Treiber weiter. Wenn dieser Treiber mit der URL eine Verbindung aufbauen kann, gibt er ein Connection-Objekt zurück. Falls der JDBC-Treiber mit
der URL nichts anfangen kann, gibt der DriverManager diese an den nächsten Treiber in der
Liste weiter, so lange bis von einem Treiber ein Connection-Objekt zurückgeliefert wird. Dieses Objekt wird dann an die Anwendung, welche die Methode DriverManager.getConnection
aufgerufen hatte, weitergeleitet.
6.1.1 Die Verwaltung der verfügbaren Treiber
Jeder JDBC-Treiber implementiert unter anderem das Interface Driver. Die Klasse DriverManager verwaltet eine Liste, deren Elemente jeweils Instanzen dieser treiberspezifischen Implementierungen sind, d. h. für jeden dem System bekannten Treiber existiert in dieser Liste eine
Instanz von dessen Driver-Implementierung.
Jede Driver-Implementierung besitzt einen Konstruktor, der eine Instanz der Klasse erstellt
und diese über die Methode DriverManager.registerDriver() der Liste der verfügbaren Treiber hinzufügt. Jeder Treiber registriert sich also auf diese Weise selbstständig bei der Klasse
DriverManager, sobald der Konstruktor seiner Implementierung des Driver-Interfaces ausgeführt wird. Um diesen Konstruktor auszuführen, könnte man den new-Operator verwenden,
allerdings würde dadurch eine Instanz der Klasse erzeugt, die nicht benötigt wird. Es gibt stattdessen zwei andere Vorgehensweisen, die dafür sorgen, das der Konstruktor der Klasse Driver
ausgeführt wird:
1. Aufruf der Methode Class.forName(String Klassenname)
Diese Methode gibt eine Instanz der als String übergebenen Klasse zurück. Ein Nebeneffekt dieser Methode ist der Aufruf des Konstruktors der übergebenen Klasse. Die
folgende Programmzeile sorgt z. B. für die Ausführung des Konstruktors der Klasse
10
acme.db.Driver:
Class.forName(“acme.db.Driver”);
2. Hinzufügen der Driver-Klasse zu der Systemeigenschaft (Property) jdbc.drivers
Alle hier eingetragenen Treiber werden beim Initialisieren der Klasse DriverManager
in die Liste der verfügbaren Treiber eingefügt. Um z. B. bei der Verwendung des
Browsers HotJava drei Treiber in diese Systemeigenschaft einzutragen ist die folgende
Zeile in ~/.hotjava/properties notwendig (Beim Starten von HotJava überträgt dieser die
Informationen aus der Datei in die Systemeigenschaften):
jdbc.drivers=foo.bah.Driver:wombat.sql.Driver:bad.test.ourDriver
Da Systemeigenschaften nicht in allen Situationen eingesetzt werden können (man denke an ein
Applet, das zur Benutzung mit einem beliebigen Webbrowser ausgeführt werden kann), ist es in
den meisten Fällen sicherer, die erstgenannte Methode zu verwenden. Weil der DriverManager
nur bei seinem ersten Aufruf die Systemeigenschaften abfragt, ist ein späteres Einfügen eines
weiteren Treibers mit der zweiten Methode nicht möglich.
Aber auch die zuerst vorgestellte Methode hat einen Nachteil: Es wird dem Programmierer
überlassen, wo und wie der Name des Treibers und die URL zur Ansteuerung der Datenbank
gesetzt werden. Unter Umständen ist eine spätere Änderung des Quelltextes erforderlich, wenn
ein anderes DBMS verwendet werden soll oder wenn sich Informationen ändern, die der Treiber
zur Ansteuerung der Datenbank benötigt.
6.2 Aufbau einer Verbindung mit der treiberspezifischen Implementierung des Interfaces DataSource
In JDBC Version 2.0 wurde das Interface DataSource eingeführt. Statt der Verwendung der
Klasse DriverManager wird seitdem von den Entwicklern die Verwendung dieses Objekts empfohlen.
Das DataSource-Objekt besitzt Eigenschaften, welche eine Daten-Quelle identifizieren und
beschreiben. Ein DataSource-Objekt kann mit dem JNDI (Java Naming and Directory Interface)1 einem logischen Namen zugeordnet werden. Nachdem diese Zuordnung von einem
Systemadministrator vorgenommen wurde, können Anwendungsprogramme das DataSourceObjekt über den ihm zugeordneten Namen ansprechen. Wenn künftig ein anderes DBMS verwendet wird, oder das verwendete DBMS künftig auf eine andere Weise angesprochen werden
soll, muß lediglich diese Namenszuordnung vom Systemadministrator geändert werden. Eine
Änderung im Quelltext des Anwendungsprogramms ist nicht notwendig.
Zu jedem Treiber, der JDBC Version 2.0 unterstützt, gehört eine Implementierung des DataSource-Interfaces. Wie ein Systemadministrator ein DataSource-Objekt einem logischen Namen zuordnen, und diese Information in einem Naming Service registrieren kann, beschreibt
Abschnitt 6.2.1. Wie ein Anwendungsprogramm eine Verbindung zu einer Daten-Quelle, unter
1 näheres
zum JNDI z. B. in [LS00]
11
der Verwendung des bei einem JNDI Naming Service registrierten DataSource-Objekts, herstellen kann beschreibt Abschnitt 6.2.2.
JDBC-Treiber-Entwickler können das DataSource-Interface so implementieren, deas es
Connection Pooling und/oder verteilte Transaktionen unterstützt. Diese Techniken werden in
diesem Seminarbeitrag jedoch nicht behandelt. Näheres dazu in [WFC 99].
6.2.1 Erstellen und registrieren eines neuen DataSource-Objekts
Ein DataSource-Objekt wird im Normalfall seperat von der Anwendung, die es benutzt, erstellt
und verwaltet.
Das folgende Programmbeispiel (übernommen aus [WFC 99] S. 471) erstellt ein neues DataSource-Objekt, setzt dessen Systemeigenschaften (Properties), und registriert es bei einem
JNDI Naming Service. VendorDataSource stellt hierbei eine treiberspezifische Implementierung des DataSource-Interfaces dar. Da ein Systemadministrator ein neues DataSource-Objekt
wahrscheinlich mit einem entsprechenden Administrationswerkzeug erstellt, entspricht das Programmbeispiel ungefähr dem Code, den ein solches Werkzeug ausführen würde.
VendorDataSource vds = new VendorDataSource();
vds.setServerName(“my_database_server”);
vds.setDatabaseName(“my_database”);
vds.setDescription(“the data source for inventory and personnel”);
Context ctx = new InitialContext();
ctx.bind(“jdbc/AcmeDB”, vds);
In den ersten vier Zeilen wird ein DataSource-Objekt erstellt und seine Eigenschaften gesetzt.
Die letzten beiden Zeilen sorgen dafür, das dieses Objekt unter dem Namen “jdbc/AcmeDB” bei
dem verwendeten Naming Service registriert wird. In der fünften Zeile wird ein neues ContextObjekt erzeugt. Welcher Naming Service dafür verwendet wird hängt von Systemeigenschaften
ab, die im Rahmen dieses Seminarbeitrags nicht weiter dargestellt werden.
In einem Naming Service gibt es einen initial naming context von dem mehrere subcontexts
ausgehen können. Die Organisation selbiger ist dabei hierarchisch organisiert, genau wie in
vielen Dateisystemen die Verzeichnisstruktur. Der initial naming context entspricht dort dem
Hauptverzeichnis und ein subcontext einem Unterverzeichnis. Das letzte Element in dieser
Hierarchie ist das Objekt, das registriert wird, in diesem Fall ein Objekt vom Typ DataSource.
In obigem Programmbeispiel wird also im subcontext “jdbc” ein DataSource-Objekt mit dem
Namen “AcmeDB” registriert.
6.2.2 Aufbau einer Verbindung zu einer durch ein DataSource-Objekt repräsentierten
Datenquelle
Im letzten Abschnitt wurde ein DataSource-Objekt eingerichtet und unter dem Namen “AcmeDB” im Naming Service eingetragen. Das folgende Programmfragment benutzt diesen Namen um das damit bezeichnete DataSource-Objekt anzufordern und mit dessen Hilfe eine Verbindung zu der, durch dieses Objekt repräsentierten, Datenbank herzustellen.
12
Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup(“jdbc/AcmeDB”);
Connection con = ds.getConnection(“genius”,”abracadabra”);
Das Objekt das in der zweiten Zeile zurückgegeben wird entspricht dabei dem DataSourceObjekt, das im letzten Abschnitt unter diesem Namen im Naming Service abgelegt wurde.
7 Übermittlung einer SQL-Anweisung an das DBMS
Um eine SQL-Anweisung an ein DBMS zu übermitteln sind folgende Schritte notwendig:
1. Eine Instanz der treiberspezifischen Implementierung des Connection-Interfaces anfordern. Dies geschieht entweder über die Klasse DriverManager, welche die Anfrage an
den jeweiligen Treiber weitergibt, oder über eine Instanz einer treiberspezifischen Implementierung des DataSource-Interfaces.
2. Über die Connection eine treiberspezifische Implementierung des Statement-Interfaces
anfordern.
3. Über das Statement eine Anweisung an das DBMS senden.
(a) Falls die Anweisung eine SQL-Anfrage ist, wird sie über die Methode excecuteQuery übermittelt und das Ergebnis wird in Form einer Instanz der treiberspezifischen
Implementierung des ResultSet-Interfaces zurückgegeben.
(b) Falls die Anweisung eine Einfüge-, Änderungs-, Lösch- oder DDL-Anweisung ist,
wird sie über die Methode excecuteUpdate übermittelt. Der Rückgabewert dieser
Methode stellt die Anzahl der Zeilen dar, die von der Anweisung bearbeitet wurden.
Das folgende Programmbeispiel sendet eine SQL-Anfrage über eine bestehende Verbindung,
die durch die Variable con repräsentiert wird, und speichert das Ergebnis in einem ResultSet:
Statement stmt=con.createStatement();
String query=“select nachname,adresse from adresse where vorname=’anna’;”
ResultSet rs=stmt.excecuteQuery(query);
Ein weiteres Programmbeispiel sendet eine SQL-Update-Anweisung:
Statement stmt=con.createStatement();
String update=”update adresse set name=’Nass’ where vorname=’anna’;”
int rows=stmt.excecuteUpdate(update);
13
Spaltennamen
Nr
Name
Vorname
1
Theke
Anna
3
Backe
Anna
6
Mussmal
Machbald
20
Übel
Ismir
Moll
Tesa
Bolika
Anna
30
33
ResultSet
Cursor
Abbildung 4: Das Cursor Konzept zur Navigation im Ergebnis einer SQL-Anfrage
8 Der Zugriff auf das Ergebnis einer SQL-Anfrage
Der mengenorientierte Datenzugriff in einem relationalen DBMS muß auf den satz- (tupel) orientierten Zugriff der objektorientierte Programmiersprache Java abgebildet werden. Um
das Ergebnis einer SQL-Anfrage in Java verarbeiten zu können, verwendet JDBC das CursorKonzept.
8.1 Das Cursor-Konzept
Ein Cursor ist ein Iterator über einer Liste von Tupeln, d. h. ein Zeiger der vor- (und seit JDBC
2.0 auch zurück-) gesetzt werden kann (siehe Abbildung 4).
Eine SQL-Anfrage in Java liefert eine Instanz der treiberspezifischen Implementierung des
ResultSet-Interfaces zurück. Das ResultSet-Objekt bietet verschiedene Methoden zur Steuerung
der Position des Cursors an, mit denen der Cursor relativ zu seiner vorherigen Position (z. B.
next()) oder absolut (z. B. first() oder absolute(int row)) positioniert werden kann.
Wenn ein bestimmtes Element aus der Ergebnisrelation gelesen werden soll, erfordert
dies zwei Schritte. Im ersten Schritt wird der Cursor, mit den vorgestellten PositionierungsMethoden, auf die Zeile gebracht, in der sich das gewünschte Element befindet. Nachdem der
Cursor positioniert ist, können im zweiten Schritt die Spaltenwerte des aktuellen Tupels ausgelesen werden. Dafür werden getXXX-Methoden benutzt, die ebenfalls vom ResultSet-Objekt
angeboten werden. XXX steht dabei für den Java-Typ, in den der Wert eingelesen werden soll.
Die Methode zum Einlesen einer Zeichenkette heisst beispielsweise getString().
Der Zugriff auf die Spalte mit Hilfe einer getXXX-Methode erfolgt dann entweder über den
Index der Spalte oder den Spaltennamen, wobei der Zugriff über den Index eine bessere Performance bietet und deswegen, wenn möglich, dem Zugriff über den Spaltennamen vorgezogen
werden sollte.
Das gesamte Ergebnis einer Anfrage, wie es in Abbildung 4 dargestellt ist, kann mit folgender while-Schleife ausgewertet werden:
while (rs.next()) {
int nr = rs.getInt(1);
14
JDBC-Typ
Java-Typ
CHAR
String
VARCHAR
String
LONGVARCHAR
String
NUMERIC
java.math.BigDecimal
DECIMAL
java.math.BigDecimal
BIT
boolean
TINYINT
byte
SMALLINT
short
INTEGER
int
BIGINT
long
REAL
float
FLOAT
double
DOUBLE
double
BINARY
byte[ ]
VARBINARY
byte[ ]
LONGVARBINARY
byte[ ]
DATE
java.sql.Date
TIME
java.sql.Time
TIMESTAMP
java.sql.Timestap
Tabelle 1: Abbildung zwischen Java- und JDBC-Typen
String name = rs.getString(“Name”);
String vorname = rs.getString(3);
System.out.println(nr+” “+name+”, “+vorname);
}
Zu Beginn der Auswertung steht der ResultSet-Cursor vor dem ersten Tupel. Die Methode
next() bewegt den Cursor auf die nächste Zeile und liefert dabei so lange den Wert true, bis das
Tabellenende erreicht ist.
8.2 Abbildung zwischen Java- und JDBC-Datentypen
Wenn eine getXXX-Methode des ResultSet-Objekts aufgerufen wird, versucht der JDBCTreiber den jeweiligen SQL-Typ in den gewünschten Java-Typ zu konvertieren. Diese Konvertierung ist jedoch nicht für alle Typkombinationen möglich, so daß hier vorzugsweise die in
Tabelle 1 dargestellte Abbildungsvorschrift verwendet werden sollte.
9 Erweiterungen des Statement-Interface
Neben dem Statement-Interface gibt es in dem JDBC-API zwei Erweiterungen dieses Interfaces, die spezielle Anweisungen darstellen und in den folgenden beiden Abschnitten kurz
dargestellt werden. Das CallableStatement-Interface erweitert dabei das PreparedStatementInterface, wie Abbildung 5 verdeutlicht.
15
Statement
PreparedStatement
CallableStatement
Abbildung 5: Verwandtschaft der verschiedenen Statement-Interfaces
9.1 Das PreparedStatement-Interface
Dieses Interface fügt zu den bereits im Statement-Interface definierten Methoden die Möglichkeit hinzu, eine SQL-Anweisung ohne konkrete Parameter an die Datenbank zu senden. Die
Parameter werden in diesem Fall durch einen Platzhalter ersetzt. Konkrete Werte können dann
mit setXXX-Methoden in die Anweisung eingesetzt werden.
Zu Verdeutlichung folgt ein kurzes Programmbeispiel:
String insStr =
“INSERT INTO Person VALUES (?,?,?)”;
PreparedStatement updStmt;
updStmt = con.prepareStatement(insStr);
updStmt.setInt(1,123);
updStmt.setString(2,”Meier”);
updStmt.setString(3,”Hans”);
updStmt.executeUpdate();
Nachdem in diesem Beispiel ein PreparedStatement erzeugt wurde, werden für die Platzhalter
an 1., 2. und 3. Stelle konkrete Werte eingesetzt. Die letzte Zeile führt die Anweisung schließlich aus. Vorteil dieser Vorgehensweise: Die Anweisung kann von dem DBMS bereits ohne
konkrete Parameter vorkompiliert werden. Dies bedeutet dann eine Verbesserung der Performance, wenn Anweisungen mehrfach mit verschiedenen Parametern ausgeführt werden.
9.2 Das CallableStatement-Interface
Ein CallableStatement erweitert ein PreparedStatement um die Möglichkeit Datenbankprozeduren aufzurufen und die Ergebnisse dieser Prozeduren von der Datenbank anzufordern. Da
der Umgang mit Datenbankprozeduren bei unterschiedlichen DBMS variert, wird der eigentliche Aufruf in Escape Syntax (siehe Abschnitt 11.1) notiert, wobei die Parameter jeweils wieder
durch Platzhalter festgelegt werden. Ein Beispiel für den Aufruf einer Datenbankprozedur findet man z. B. in [SS00].
CallableStatement-Objekte müssen, ebenso wie Statement- und PreparedStatementObjekte von einem Connection-Objekt angefordert werden.
16
10 Transaktionssteuerung
Eine Transaktion ist eine elementare Ausführungseinheit zur Überführung einer Datenbank von
einem konsistenten Zustand in einen veränderten, konsistenten Zustand unter Einhaltung des
ACID-Prinzips [HS00]. Hierbei werden 4 Eigenschaften gefordert:
Atomarität: jede Transaktion wird als Einheit, d.h. ganz oder gar nicht ausgeführt.
Konsistenz: eine Transaktion ist konsistenzerhaltend.
Isolation: mehrere parallele Transaktionen haben keine Auswirkungen aufeinander.
Dauerhaftigkeit: jedes Ergabnis einer Transaktion ist persistent.
In dem Interface Connection sind zwei Kommandos zur Steuerung des Transaktionsablaufs
definiert: commit und rollback.
Wenn alle zu einer Transaktion gehörenden Anweisungen erfolgreich über die Verbindung
gesendet wurden, signalisiert die Anwendung mit dem Aufruf der Methode commit, dass alle
Änderungen permanent in die Datenbank geschrieben werden sollen. Mit dem Aufruf der Methode rollback wird die Datenbank wieder in den Zustand gebracht, in dem sie sich vor dem
Beginn der Transaktion befand.
Wenn eine neue Verbindung zu einer Datenbank hergestellt wird, befindet sich diese im
Auto-Commit-Modus, d. h. nach jeder Anweisung wird automatisch ein Commit ausgeführt.
Dieser Modus kann mit der Methode setAutoCommit ein- (mit true als Parameter) bzw. ausgeschaltet (false) werden.
In SQL-Datenbanken ermöglichen sogenannte Isolationsebenen eine Aufweichung des
ACID-Prinzips. Über die Methode setTransactionIsolation im Connection-Objekt kann in
JDBC eine Isolationsebene ausgewählt werden, die für Transaktionen verwendet werden soll
(Eine Erläuterung hierzu findet man z. B. in [SS00] oder in [EHF01]).
11 DBMS-Unabhängige Programmierung
Nicht alle Features, die ein bestimmtes DBMS unterstützt, werden von jedem anderen DBMS
ebenfalls angeboten. Datenbankprozeduren werden z. B. nicht von allen DBMS angeboten und
diejenigen, die dieses Feature anbieten sind untereinander nicht kompatibel.
Das Interface Driver definiert die Methode jdbcCompliant. Ein JDBC-Treiber, der die
JDBC API 1.0 und den SQL-92 Entry Level unterstützt, gibt bei einem Aufruf dieser Methode
den Wert true zurück (eine genauere Darstellung der Kriterien findet man in [EHF01]). Leider
können Datenbanken, die diesen Standard unterstützen, sich noch in einigen Punkten unterscheiden, ebenso wie ein Treiber natürlich Features unterstützen kann, die nicht Bestandteil des
SQL-92 Entry Level (ISO/IEC 9075-1) sind.
Um ein DBMS-unabhängiges Programm zu schreiben, wird der Programmierer daher von
JDBC durch DBMS-unabhängige SQL-Anweisungen, sogenannten “SQL-Escapes” (Abschn.
11.1) unterstützt.
17
Programme, die für unterschiedliche DBMS entworfen werden, benötigen evtl. Informationen über das momentan angesteuerte DBMS, diese Informationen werden von JDBC durch das
DatabaseMetaData-Interface zur Verfügung gestellt (Abschn. 11.2).
11.1 SQL-Escapes
Es gibt eine Reihe von SQL-Anweisungen, die DBMS-spezifisch formuliert werden müssen.
In JDBC gibt es die Möglichkeit einige solcher Fälle mit sogenannten SQL-Escapes DBMSunabhängig zu formulieren.
Ein Beispiel: Jedes DBMS erlaubt, eine Tabellenspalte mit dem Datentyp DATE einzurichten. Die Schreibweise eines Wertes innerhalb dieser Spalte unterscheidet sich jedoch oft, je
nach verwendetem DBMS.
Mit SQL-Escapes gibt es die Möglichkeit Datumskonstanten DBMS-unabhängig zu formulieren: An der Stelle innerhalb der SQL-Anweisung, an der das Datum “02. Januar 1974” in
der datenbankspezifischen Formulierung stehen müßte, schreibt man stattdessen den String “{d
´1974-01-02’}”. Der JDBC-Treiber erkennt an den geschweiften Klammern die Escape-Syntax
und ersetzt den Ausdruck durch die DBMS-spezifische Schreibweise.
JDBC definiert SQL-Escapes für
den Aufruf von skalaren Funktionen,
Datums- und Zeitangaben,
die Formulierung eines äußeren Verbundes (outer join),
den Aufruf von in der Datenbank gespeicherten Prozeduren
und WHERE-Klauseln, in denen mittels LIKE nach Zeichenketten gesucht wird, in denen
ein ’_’ oder ’%’ vorkommt.
11.2 Informationen über das DBMS: Das DatabaseMetaData-Interface
Das DatabaseMetaData-Interface definiert über 150 Methoden, die nach der Art der Informationen die sie zur Verfügung stellen folgendermaßen eingeteilt werden können (siehe auch
[WFC 99] S. 43 ff):
Methoden, die Informationen über die Datenbank liefern
Methoden, die die unterstützten Features charakterisieren
Methoden, die Beschränkungen der Datenbank bzw. des Treibers beschreiben
Methoden, die Beschreibungen von Datenbankobjekten in Form von ResultSet-Objekten
liefern
18
Methoden, die die Zulässigkeit von DDL-Anweisungen als Teil von Transaktionen spezifizieren
Ein DatabaseMetaData-Objekt für eine bestehende Verbindung, die durch das ConnectionObjekt con repräsentiert wird, erhält man durch den Aufruf der Methode con.getMetadata().
Das folgende Programmbeispiel benutzt ein DatabaseMetaData-Objekt, um die maximal
zulässige Zeichenanzahl für den Namen einer Tabelle herauszufinden:
// con is a Connection object
DatabaseMetaData dbmd = con.getMetadata();
int maxLen = dbmd.getMaxTableNameLength();
Die Methoden des DatabaseMetaData-Objekts können einem Programmierer helfen, seine Anwendung DBMS-unabhängig zu implementieren.
12 Abschließende Anmerkungen
Dieser Seminarbeitrag vermittelt Grundkenntnisse über das JDBC-API. Weitergehende Konzepte die hier nicht besprochen wurden:
Fehlerbehandlung
Einige ResultSet-Erweiterungen
Batch-Updates
SQL99-Datentypen
Connection Pooling
Verteilte Transaktionen
RowSets
Einen Einstieg in diese Konzepte ermöglicht z. B. [Sm01].
13 Zusammenfassung und Bewertung
Ein Ziel der weltweiten und unternehmensinternen Vernetzung lautet: “Information at your
fingertips” - sämtliche Informationen, die ein Mitarbeiter benötigt, sollen am Arbeitsplatz verfügbar sein. Die in den Unternehmen existierenden Datenbanken spielen dabei eine besondere
Rolle.
In einem Unternehmen existieren meistens unterschiedliche Betriebssysteme und DBMS.
Wenn die in den DBMS gespeicherten Informationen von einem beliebigen Arbeitsplatz aus
19
verfügbar sein sollen, kann für jede Kombination dieser beiden Komponenten eine individuelle
Software-Lösung implementiert werden. Meistens werden Software-Lösungen auf verschiedenen Betriebssystemen für ein konkretes DBMS entwickelt, wodurch für das Unternehmen eine
Abhängigkeit von diesem DBMS entsteht. Die grosse Anzahl der auf diese Weise entstandenen Software-Lösungen behindert den Umstieg auf ein anderes DBMS: Jede Lösung müsste
individuell an das neue DBMS angepasst werden.
Java bietet in Verbindung mit der JDBC-API weniger aufwendige Lösungsmöglichkeiten:
Daten aus einem DBMS werden in einem Java-Applet verwendet werden, das in eine
HTML-Seite eingebettet ist.
Vorteil: Auf den Client-Rechnern braucht lediglich ein marktüblicher Webbrowser installiert zu sein.
Auf jedem Client-Rechner läuft das gleiche plattformunabhängige Anwendungsprogramm.
Vorteil: Es muss nur ein Programm (weiter-)entwickelt werden.
Bei einem Wechsel von dem verwendeten DBMS muß also nur ein Programm an das neue
DBMS angepasst werden. Im Idealfall entfällt sogar diese Anpassung.
Literatur
[EHF01] E LLIS, Jon ; H O, Linda ; F ISHER, Maydene: JDBC 3.0 Specification - Proposed
Final. Sun Microsystems, Inc., April 2001
[HS00]
H EUER, Andreas ; S AAKE, Gunter: Datenbanken: Konzepte und Sprachen. 2.,
aktualisierte und erw. Aufl. MITP-Verlag, 2000
[LS00]
L EE, Rosanna ; S ELIGMAN, Scott: JNDI API Tutorial and Reference. AddisonWesley, 2000
[Sm01]
SUN MICROSYSTEMS , inc.
http://www.java.sun.com. 2001
[SS00]
S AAKE, Gunter ; S ATTLER, Kai-Uwe: Datenbanken und Java. dpunkt.verlag, April
2000
The
Source
For
Java
Technology.
[WFC 99] W HITE, Seth ; F ISHER, Maydene ; C ATTELL, Rick ; H AMILTON, Graham ; H AP NER, Mark: JDBC API Tutorial and Reference, Second Edition. Addison-Wesley,
1999
20
Herunterladen