JDBC und Java DB - Technische Universität München

Werbung
Java für Fortgeschrittene
Proseminar im Sommersemester 2009
JDBC und Java DB
Dhyan Blum
Technische Universität München
13. Juli 2009
Zusammenfassung
Das Thema dieser Seminararbeit ist die Datenbankschnittstelle JDBC,
sowie die freie Datenbank Java DB. Schwerpunkte sind somit verschiedene
Aspekte des Zugriffs auf relationale Datenbanken aus Java-Anwendungen
heraus, sowie eine Einführung in die vollständig in Java geschriebene Datenbank Java DB. Ein weiterer Aspekt ist die nähere Betrachtung von Java DB
als eingebettete Datenbank und ihre Anwendungsmöglichkeiten als solche.
1
Inhaltsverzeichnis
1 Einleitung
2
2 Exkurs: Relationale Datenbanken
3
3 JDBC
3.1 Der Vorläufer: ODBC . . . . . . . .
3.2 Geschichte: Java und CGI . . . . .
3.3 Einführung in JDBC . . . . . . . .
3.4 Konzeptioneller Aufbau von JDBC
3.5 JDBC Treiber . . . . . . . . . . . .
3.6 Klassenstruktur von JDBC . . . . .
3.7 Einführung in die Praxis . . . . . .
3.8 Erweiterte Funktionen von JDBC .
3.9 Fazit . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
5
5
5
6
6
8
9
14
16
4 Java DB
16
4.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
4.2 Architektur von Java DB . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
4.3 Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
5 Literatur
20
1 Einleitung
Motivation
Wann immer Daten gesammelt und archiviert werden, sind wir an ihrer einfachen Verwaltung interessiert. Wir wünschen uns eine Ordnung, die auch nach längerer Zeit sicherstellt, dass wir aus einer unüberschaubaren Menge von Informationen gezielt einzelne
Daten heraussuchen, modifizieren oder auch entfernen können. Bei kritischen Daten stellen wir zudem die Forderung nach ihrer Sicherheit und Unversehrtheit. In größeren Systemen möchten wir häufig mehreren Nutzern den gleichzeitigen, den wechselseitig ausgeschlossenen oder schlicht den eingeschränkten Zugriff auf bestimmte Daten ermöglichen.
Diese und weitere Aspekte der Datenspeicherung (Schlagwörter wie Redundanz, Inkonsistenz, Integrität, usw. spielen hier eine Rolle), haben die Entwicklung und schließlich
den flächendeckenden Einsatz von Datenbankmanagementsystemen (DBMS) initiiert.
Diese Systeme versprechen die meisten Probleme in der Domäne der Datenverwaltung
auf praktikable Weise zu lösen.
Problemstellung
Zeitlich betrachtet hat Java erst spät den Zugang zur Datenbank gefunden: Die erste
Version von Suns Datenbankschnittstelle JDBC erschien im Januar 1997 und erst die
2
zweite API-Version wurde ab Mai 1998 in Form des Pakets java.sql.* mit J2SE (Java
2, Versionsnummer 1.2) ausgeliefert. Auch beim Erlernen der Sprache steht der Zugriff
auf Informationen einer Datenbank nicht unbedingt ganz oben auf der Prioritätenliste.
Im Bedarfsfall ist das naheliegendste Problem für den Java-Anwendungsentwickler also,
wie er mit seinem Programm eine Datenbank ansprechen und ihre Daten für sich nutzbar
machen kann. Verbindungen herstellen, Daten anfragen und Ergebnisse verarbeiten sind
die zentralen Probleme hierbei. Daneben stellt sich die Frage nach dem einzusetzenden
Datenbanksystem und dessen Eignung für unterschiedliche Einsatzzwecke.
Überblick
Die folgende Seminararbeit soll eine Einführung in die angesprochene Thematik bieten. Zu Beginn folgt ein kurzer Ausflug in die Welt der (relationalen) Datenbanken,
anschließend ein wenig Geschichte zur Entwicklung und zu den Vorläufern der Datenbankschnittstelle JDBC und eine praktische Einführung in selbige. Im zweiten Teil der
Arbeit wird die vollständig in Java geschriebene Datenbank Java DB eingeführt und an
ihrem Beispiel das Konzept eingebetteter Datenbanken diskutiert.
2 Exkurs: Relationale Datenbanken
Aufbau und Konzept
Relationale Datenbanken dienen zur Datenverwaltung in Computersystemen. Grundkonzept ist die Relation, die eine mathematische Beschreibung einer Tabelle darstellt.
Für das Abfragen und Manipulieren von Daten wird die Structured Query Language
(SQL) verwendet. Abseits der mathematischen Hintergründe kann man sich eine solche
Datenbank schlicht als Sammlung von benannten Tabellen (Relationen) vorstellen. Jede
Zeile einer solchen Tabelle stellt einen Datensatz (ein Tupel) dar, jeder Datensatz besteht aus einer Reihe von Werten (Attributen), die in den Spalten der Tabelle organisiert
sind. Anzahl, Name und Typ aller Spalten bestimmen die Struktur der Tabelle, das sogenannte Relationenschema. Eine reale, mit Daten gefüllte Tabelle wird als Ausprägung
bezeichnet. Schema und Ausprägung verhalten sich im übertragenen Sinne wie Klasse
und Objekt. Spaltentypen können z.B. Integer, String oder Boolean sein. Um einzelne
Datensätze identifizieren zu können, ist es möglich, eine Spalte als Schlüssel zu definieren bzw. mehrere Spalten zu einem Schlüssel zusammenzufassen, der das Tupel dann
eindeutig identifiziert. Ist kein eindeutiger Schlüssel möglich, wird häufig ein künstlicher
Schlüssel in Form einer fortlaufende Identifikationsnummer erzeugt. Tabelle 1 zeigt ein
Beispiel, in dem die Spalte mit Namen id“ der Schlüssel ist.
”
Anfragen mit SQL
Für Anfragen an relationale Datenbanken hat sich die Datenbanksprache SQL als absoluter Standard etabliert, sie wird von allen gängigen Datenbanksystemen unterstützt.
3
Tabelle 1: personen“
”
id
1
2
3
4
...
vorname
Hans
Gabriele
Erwin
Hans
...
nachname
Meier
Huber
Schuster
Meier
...
wohnort
Augsburg
München
München
Augsburg
...
Da eine Einführung in SQL den Rahmen dieser Arbeit sprengen würde, sei lediglich folgendes Beispiel gegeben: Angenommen der Name Gabriele Huber“ wäre innerhalb der
”
Tabelle eindeutig und wir wollten die id und den wohnort dieser Person herausfinden,
so müssten wir folgende SQL-Anfrage formulieren:
SELECT id , wohnort
FROM personen
WHERE vorname = ’ Gabriele ’
AND nachname = ’ Huber ’;
In der Konsole würde z.B. eine MySQL-Datenbank diese Anfrage wie folgt beantworten:
mysql > SELECT id , wohnort FROM personen WHERE ...
+ - - - -+ - - - - - - - - -+
| id | wohnort |
+ - - - -+ - - - - - - - - -+
| 2 | München |
+ - - - -+ - - - - - - - - -+
1 row in set (0.00 sec )
Hätten wir stattdessen nach Personen mit wohnort = ’München’ gefragt, so würde das
Ergebnis mindestens zwei Datensätze umfassen. Einfache SQL-Anfragen sind also recht
intuitiv. SQL kann jedoch auch für sehr viel mächtigere Fragestellungen verwendet werden und spätestens dann haben Textdateien als Speicherstruktur klar das Nachsehen.
Dieses Beispiel zeigt uns die grundsätzliche Problemstellung auf: Wie setzen wir SQLAnfragen aus Java heraus an die Datenbank ab, und wie kommen wir an die Ergebnisse?
3 JDBC
In diesem Kapitel werden die Entstehungsgeschichte, der konzeptionelle Aufbau und
der praktische Einsatz der Datenbankschnittstelle JDBC erläutert. Abschließend wird
4
ein Blick auf die erweiterten Funktionalitäten von JDBC geworfen und dann ein Fazit
gezogen.
3.1 Der Vorläufer: ODBC
Die von Microsoft entwickelte Open Database Connectivity (ODBC [7]) ist eine Programmierschnittstelle, die den einheitlichen Zugriff auf relationale und andere Datenbanken
mit SQL ermöglicht. ODBC 1.0 wurde 1992 veröffentlicht, ist seit Windows 2000 in alle
Microsoft-Betriebssysteme integriert und inzwischen auch für Unix verfügbar. Vergleichbare Abstraktionsschichten für den Datenzugriff bieten auch DBI (Database Independent) für die Programmiersprache Perl oder die Erweiterung PDO (PHP Data Objects)
für PHP.
Solange man sich auf standardisierte SQL-Konstrukte beschränkt und auf proprietäre
SQL-Erweiterungen einzelner Datenbankhersteller verzichtet, erlaubt ODBC die Anwendungsentwicklung unabhängig von der letztlich verwendeten Datenbank. Somit wird
auch das transparente Austauschen der Datenbank zu einem späteren Zeitpunkt möglich,
ohne das eigentliche Programm verändern zu müssen. Es sind sowohl Zugriffe auf lokale
wie auch auf entfernte Datenbanken möglich. Damit ODBC verwendet werden kann,
muss die eingesetzte Datenbank einen passenden ODBC-Treiber zur Verfügung stellen.
Da ODBC vielerorts als Standard etabliert ist, bieten alle größeren Datenbankhersteller
entsprechende Treiber für diverse Programmiersprachen an, darunter für C++, Perl und
PHP. Ein Nachteil von ODBC ist naturgemäß die geringere Performanz im Vergleich zu
nativen Datenbankanfragen.
3.2 Geschichte: Java und CGI
Bevor eine ähnliche Technik wie ODBC für Java existierte, mussten Datenbankanfragen zunächst an ein serverseitiges CGI-Programm weitergereicht werden, welches dann
auf die Datenbank zugriff und schließlich die Anfrageergebnisse zurück an die JavaAnwendung übergab. Dieser Ansatz war nicht nur langsam, er erforderte auch Kenntnisse zweier verschiedener Programmiersprachen und begünstigte so fehleranfällige und
schwer zu wartende Anwendungen[5].
3.3 Einführung in JDBC
1996 wurde in Anlehnung an ODBC mit einem vergleichbaren Projekt für Java begonnen,
der Java Database Connectivity (JDBC [10]). Im Januar 1997 wurde die erste Version der
JDBC API spezifiziert und über die Jahre folgten weitere Versionen, bis schließlich 2006
die heute noch aktuelle Version 4.0 veröffentlicht wurde[11]. Analog zu ODBC ist JDBC
eine Javaschnittstelle für den einheitlichen Zugriff auf relationale Datenbanken verschiedener Hersteller. Als solche erlaubt sie das Einbetten der Datenbanksprache SQL in den
Quelltext der Anwendung, obwohl SQL nicht zum eigentlichen Sprachumfang von Java
gehört und somit als Fremdkörper anzusehen ist. Die Einbettung von SQL ist deshalb
5
notwendig, weil die Sprache allein keine Entwicklung komplexer Datenbankanwendungen
gestattet. JDBC erfüllt im wesentlichen drei Aufgaben:
∙ Verbindungsaufbau zur Datenbank
∙ Absetzen von SQL-Anfragen
∙ Verarbeitung der Ergebnisse
Die Stärke der Schnittstelle liegt darin, dass eine JDBC-basierte Java-Anwendung Clienten auf einer beliebigen Plattform mit einer beliebigen Datenbank verbinden kann.
3.4 Konzeptioneller Aufbau von JDBC
Abbildung 1 illustriert den konzeptionellen Aufbau von JDBC: Die Java-Anwendung ruft
Funktionen der JDBC API auf (Aufrufschnittstelle, Call Library Interface“), die einen
”
Datenbanktreiber lädt und über diesen mit der zugehörigen Datenbank kommuniziert.
Der DriverManager ist die Basis der JDBC-Architektur und verwaltet die auf dem System installierten JDBC-Treiber (vgl. Kap. 3.5). Über die getConnection()-Methode
des DriverManagers wird die Verbindung zu einer spezifischen Datenbank hergestellt.
Die Klasse Connection repräsentiert eine solche Verbindung. In ihrem Kontext werden
SQL-Anfragen ausgeführt und Ergebnisse zurückgeliefert, wobei SQL-Anweisungen in
Form von Zeichenketten an entsprechende API-Methoden übergeben werden. Falls die
Datenbank eine Ergebnismenge zurückliefert, kann über andere API-Methoden auf diese
zugegriffen werden.
3.5 JDBC Treiber
Um eine Datenbank mit JDBC ansprechen zu können, muss diese einen Treiber bereitstellen, der die JDBC-Spezifikation implementiert. Solche Treiber werden in der Regel
vom Hersteller des Datenbanksystems zur Verfügung gestellt. Eine Liste aller 221 derzeit bekannten Treiber wird online von Sun angeboten[12]. JDBC kennt vier verschiedene
Typen von Treibern[13] (vgl. Abb. 1), die im Folgenden beschrieben werden:
Typ 1: JDBC-ODBC bridge
Datenverbindungen werden über eine ODBC-Schnittstelle aufgebaut. JDBC-Anweisungen
werden an die entsprechenden Funktionen des ODBC-Treibers übergeben, der im nativen Code des Betriebssystems auf dem Client vorliegt und die eigentliche Verbindung zur
Datenbank herstellt. Der ODBC-Treiber nutzt die Aufrufschnittstelle (CLI) des Herstellers, d.h. die nativen ODBC-Bibliotheken des Datenbanksystems müssen auf dem Client
installiert sein. Dieser Treibertyp ist für die meisten Datenbanksysteme verfügbar, kann
aber z.B. nicht von Applets genutzt werden, die standardmäßig keinen Zugriff auf lokale Ressourcen des Clients haben. Der Treiber ist daher für den realen Einsatz nicht
zu empfehlen, die Abhängigkeit von nativen Bibliotheken beeinträchtigt die Portabilität
der Anwendung.
6
Abbildung 1: Konzeptioneller Aufbau von JDBC
Typ 2: Native-API partly Java technology-enabled
Der Treiber ist teilweise in Java geschrieben und bildet JDBC-Aufrufe eigenständig auf
native Anweisungen des Datenbank-Clients ab, der die Kommunikation zur Datenbank
herstellt. Nach wie vor müssen die nativen Bibliotheken des Datenbanksystems auf dem
Client installiert sein, die Portabilität wird eingeschränkt. Der Zugriff über diesen Treibertyp ist allerdings sehr schnell.
Typ 3: Net-protocol fully Java technology-enabled
Plattformunabhängiger Treiber (reine Java-Bibliothek), der JDBC-Anfragen in ein datenbankunabhängiges Protokoll übersetzt und dieses an einen Middleware-Server überträgt.
Dort folgt die Übersetzung in spezifische Datenbankanweisungen. Ein solcher Server ist
in der Lage, alle angeschlossenen Java-Clients mit jeweils unterschiedlichen, auf dem
Middleware-Server laufenden, Datenbanken zu verbinden.
Typ 4: Native-protocol fully Java technology-enabled
Der Treiber ist vollständig in Java geschrieben und implementiert ein Netzwerkprotokoll für spezifische Datenbanksysteme. JDBC-Anfragen werden direkt in entsprechende Befehle des jeweiligen Datenbankservers übersetzt und über das Protokoll an diesen
übertragen. Der Client ist direkt mit der Datenbank verbunden, der Treiber somit schneller, aber weniger flexibel als ein Typ-3-Treiber.
7
Die meisten Datenbankhersteller bieten Treiber vom Typ 3 oder 4 an. Für diese Ausarbeitung ist ausschließlich der Typ-4-Treiber von Interesse. Da standardmäßig lediglich
JDBC-ODBC Brücken vorhanden sind, müssen andere Treiber in der Regel selbst bezogen und installiert werden. Am Beispiel von MySQL bedeutet dies, den JDBC Typ4-Treiber Connector/J“ von der Herstellerwebsite herunterzuladen und die Bibliothek
”
mysql-connector-java-[version]-bin.jar dem CLASSPATH hinzuzufügen.
3.6 Klassenstruktur von JDBC
Der Kern der JDBC API ist das Paket java.sql.*, welches mit dem JDK ausgeliefert
wird. Wichtige Teile des Pakets java.sql.* sind die Interfaces CallableStatement,
Connection, Driver, PreparedStatement, ResultSet und ResultSetMetaData. Unter den Klassen ist der bereits erwähnte DriverManager von Bedeutung. Abbildung 2
Abbildung 2: Die wichtigsten JDBC-Klassen und -Interfaces
zeigt ein reduziertes Klassendiagramm von JDBC: Das Interface Driver ist hier nicht
eingezeichnet, da sich ein Treiber nach dem Laden selbstständig beim DriverManager registriert und der Entwickler selbst keinen direkten Kontakt zu Driver hat. Die übrigen
Klassen werden in 3.7 näher beschrieben. Doch an Hand des Diagramms können wir
schon jetzt die typischen Schritte bei der Entwicklung einer einfachen Datenbankanwendung mit JDBC nachvollziehen:
1. Importieren des Pakets java.sql.* und Laden eines Treibers mit
Class.forName("Treibername")
2. Herstellen einer Verbindung mit getConnection()
3. Erstellen eines Statement-Objekts mit createStatement()
4. Ausführen einer SQL-Anfrage mit executeQuery()
8
5. Einholen der Ergebnisse mit getResultSet() und den entsprechenden get<Type>()Methoden für einzelne Spaltwerte
6. Schließen von ResultSet, Statement und Connection
3.7 Einführung in die Praxis
Die wichtigsten JDBC-Klassen und ihre praktische Verwendung werden im Folgenden
näher beschrieben.
Schritt 1: Laden des Datenbanktreibers
Der erste Schritt besteht darin, das JDBC-Paket java.sql.* zu importieren und einen
Treiber zu laden. Für gewöhnlich werden Treiber mittels Class.forName geladen, was
automatisch eine Instanz des Treibers erzeugt und beim DriverManager registriert. In
folgendem Beispiel wird der Typ-4-Treiber von MySQL geladen:
import j a v a . s q l . ∗ ;
...
C l a s s . forName ( ”com . mysql . j d b c . D r i v e r ” ) ;
...
Schritt 2: Herstellen einer Verbindung
Die Methode getConnection(String url, String user, String pass) des DriverManagers versucht eine Datenbankverbindung zur angegebenen URL mit dem Benutzer
user“ und dem Passwort pass“ herzustellen. Bei Erfolg gibt sie ein Objekt vom Typ
”
”
Connection zurück. Das Argument url hat die Form jdbc:subprotocol:subname, wobei subprotocol für den Datenbanktyp (z.B. derby“ für Java DB oder mysql“ für die
”
”
Datenbank MySQL) und subname für die Adresse der Datenbank steht, an die zusätzlich
der eigentliche Datenbankname angehängt wird. Beispiel: Der Parameter
url = "jdbc:mysql://localhost/seminardatenbank" würde eine JDBC-Verbindung
zu einer MySQL-Datenbank mit Namen seminardatenbank“ auf dem lokalen System
”
herstellen. In folgendem Ausschnitt wird eine Verbindung zu obiger Beispiel-URL hergestellt:
...
S t r i n g u r l = ” j d b c : mysql : / / l o c a l h o s t / seminardatenbank ” ;
Connection con = DriverManager . g e t C o n n e c t i o n ( u r l , ” u s r ” , ”pwd” ) ;
...
9
Schritt 3: Erzeugen eines Statement-Objekts
Das Interface Connection repräsentiert die Verbindung zu einer spezifischen Datenbank.
In ihrem Kontext werden Anfragen durchgeführt und Ergebnisse verarbeitet. Ihre wichtigsten Methoden sind createStatement(), um ein Statement-Objekt für das Absetzen
von SQL-Anfragen an die Datenbank zu erzeugen, sowie close(), um eine Verbindung
nach Verarbeiten der Ergebnisse wieder zu schließen und ihre belegten Ressourcen freizugeben. Beim schließen einer Verbindung werden automatisch alle von der Verbindung
erzeugten Datenbankobjekte, wie z.B. Statements und ResultSets, ebenfalls geschlossen. Die Methode getMetaData() erzeugt ein DatabaseMetaData-Objekt, mit dem es
möglich ist, eine ganze Reihe von Informationen über die angeschlossene Datenbank zu
erfragen. Dazu gehören z.B. eine Beschreibung der Datenbanktabellen, die von der Datenbank unterstützten Funktionen und SQL-Grammatiken oder auch Eigenschaften der
Verbindung. Man könnte beispielsweise mit DatabaseMetaData.supportsOuterJoins
herausfinden, ob die Datenbank den Outer Join (äußerer Verbund von zwei oder mehr
Tabellen) unterstützt.
Schritt 4: Anfragen durchführen
Die Klasse Statement dient dem Ausführen von SQL-Anweisungen und der Rückgabe
eventuell entstandener Ergebnisse. Ihre wichtigsten Methoden lauten wie folgt:
void close() Sobald die Arbeit mit einem Statement-Objekt abgeschlossen ist, sollte die
Methode close() aufgerufen werden. Sie gibt dem Garbage Collector explizit die
Gelegenheit, den vom Statement belegten Speicher sofort freizugeben, was in der
Regel die Performanz verbessert.
ResultSet executeQuery(String sql) Die Methode erhält als Argument eine SQL-Anweisung
in Form eines Strings, führt die gewünschte Anfrage an die Datenbank aus und
gibt ein ResultSet-Objekt zurück. Das ResultSet repräsentiert die Ergebnisse einer SQL-Anfrage in Form einer Tabelle und wird später noch näher beschrieben.
executeQuery() ist die Standardmethode, um Informationen aus der Datenbank
abzufragen.
int executeUpdate(String sql) Führt entweder SQL-Operationen durch, die schreibend
auf die Datenbank zugreifen, oder solche, die keinen Rückgabewert haben. Zu den
schreibenden Operationen gehören z.B. INSERT- (zum Einfügen von Datensätzen),
UPDATE- (zum Verändern bestehender Datensätze) und DELETE-Statements (zum
Löschen von Datensätzen). Die Methode gibt entweder die Zahl der betroffenen
Datensätze zurück, oder 0, falls das SQL-Statement keinen Rückgabewert liefert.
Operationen ohne Ergebnismenge
Wir verwenden nun die Methode executeUpdate(String sql), um eine neue Person
in unsere Tabelle personen aus Abschnitt 2 einzufügen:
10
...
Statement s t a t e m e n t = con . c r e a t e S t a t e m e n t ( ) ;
S t r i n g s q l = ”INSERT INTO p e r s o n e n ( vorname , nachname ,
wohnort ) VALUES ( ’ Maria ’ , ’ K l e i n ’ , ’ München ’ ) ” ;
int r e s u l t = s t a t e m e n t . executeUpdate ( query ) ;
...
Im SQL-Statement geben wir an, in welche Tabelle eingefügt werden soll, in welche Spalten und was für Werte. Da die Spalte id in der Struktur der Tabelle als fortlaufende
Identifikationsnummer definiert wurde, müssen wir sie nicht selbst inkrementieren und
bei Einfügeoperationen auch nicht angeben. Der Rückgabewert von executeUpdate ist
bei INSERT-Anweisungen die Anzahl eingefügter und bei UPDATE-Anweisungen die Anzahl betroffener Datensätze.
Schritt 5: Anfragen mit Ergebnismenge - Arbeiten mit dem ResultSet
Im Allgemeinen wird sehr viel häufiger lesend auf eine Datenbank zugegriffen, was dann
eine entsprechende Verarbeitung der Ergebnisse notwendig macht. Wir wollen im Folgenden die Menge aller in München wohnenden Personen erfragen und das Ergebnis auf
der Konsole ausgeben. Zunächst führen wir die Anfrage durch und legen das Ergebnis
in einem Objekt vom Typ ResultSet ab.
...
query = ”SELECT ∗ FROM p e r s o n e n WHERE wohnort = ’ München ’ ” ;
R e s u l t S e t r e s u l t = s t a t e m e n t . executeQuery ( query ) ;
...
Anstatt jede einzelne Spalte explizit anzugeben, kann man wie oben ein Sternchen verwenden, um alle vorhandenen Spalten ins Ergebnis aufzunehmen.
Ein ResultSet-Objekt repräsentiert eine Datentabelle mit den Ergebnissen einer SQLAnfrage. Es verfügt über einen Zeiger, der auf die aktuelle Zeile bzw. den aktuellen Datensatz verweist. Zu Beginn wird der Zeiger vor dem ersten Datensatz positioniert und
kann dann mittels der next()-Methode in die jeweils nächste Zeile bewegt werden. Da
die Methode false zurückgibt, sobald keine Datensätze mehr im ResultSet vorhanden
sind, kann sie dazu verwendet werden, in einer Schleife durch die Ergebnisse zu iterieren. Die Anzahl der im Ergebnis vorhandenen Spalten, ihre Namen und Typen können
über die Methode getMetaData() erfragt werden. In unserem Beispiel sind uns all diese
Daten durch die Struktur der Tabelle und der SQL-Anfrage bereits bekannt und wir
können uns ohne Umwege zwei grundlegende Methoden zum Auslesen der Spaltenwerte
ansehen:
int getInt(String columnName) Greift auf den Integer-Wert der Spalte columnName
des aktuellen Ergebnistupels zu. Statt über den Spaltennamen hätte man auch
über die Position der Spalte zugreifen können, was im Allgemeinen etwas effizienter
11
ist: int getInt(int colIndex). Die Spalten werden von 1 an durchnummeriert,
nicht von 0.
String getString(String columnName) Greift analog auf den String-Wert des aktuellen Datensatzes zu. Auch hier kann stattdessen über die Position der Spalte
zugegriffen werden: String getString(int colIndex).
Laut [9] gibt es leider keine Methode um die Daten einer gesamten Zeile auf einmal zu
erhalten. Wir iterieren nun also durch unsere Ergebnismenge und geben jeden Datensatz
als einzelne Zeile in der Konsole aus:
...
while ( r e s u l t . next ( ) ) {
System . out . p r i n t ( r e s u l t . g e t I n t ( ” i d ” ) + ” ” ) ;
System . out . p r i n t ( r e s u l t . g e t S t r i n g ( ” vorname ” ) + ” ” ) ;
System . out . p r i n t ( r e s u l t . g e t S t r i n g ( ”nachname” ) + ” ” ) ;
System . out . p r i n t l n ( r e s u l t . g e t S t r i n g ( ” wohnort ” ) ) ;
}
...
Die zugehörige Ausgabe:
2 Gabriele Huber München
3 Erwin Schuster München
5 Maria Klein München
Anmerkung: Seit JDBC 2.0 ist es unter bestimmten Umständen möglich, das ResultSet
mittels seiner set()-Methoden zu bearbeiten, wobei diese Änderungen auf die Datenbasis
zurückpropagiert werden.
Anfrageinformationen: ResultSetMetaData
Ein Objekt vom Typ ResultSetMetaData kann dazu verwendet werden, um Informationen über die Typen und Eigenschaften von Spalten in einem ResultSet zu erhalten. Das
ist insbesondere dann wichtig, wenn eine SQL-Anfrage dynamisch von der Anwendung
generiert wurde und die Struktur der Ergebnistupel somit nicht von Beginn an bekannt
ist. Einige nützliche Methoden der Klasse ResultSetMetaData lauten:
int getColumnCount() Gibt die Anzahl der Spalten im ResultSet zurück.
String getColumnName(int i) Gibt den Namen der i-ten Spalte im ResultSet zurück.
Die erste Spalte des ResultSets besitzt den Index 1.
int getColumnType(int i) Gibt den Typ der i-ten Spalte im ResultSet zurück.
int getColumnDisplaySize(int i) Gibt die Anzahl der maximal möglichen Zeichen der
i-ten Spalte im ResultSet zurück.
12
String getTableName(int i) Gibt den Namen der Tabelle zurück, zu der die i-te Spalte
im ResultSet gehört.
boolean isAutoIncrement(int i) Zeigt an ob die i-te Spalte im ResultSet eine automatisch generierte, fortlaufende Nummer (und somit nur lesbar) ist.
Wir wenden nun einige dieser Methoden auf unser erhaltenes ResultSet an:
...
ResultSetMetaData meta = r e s u l t . getMetaData ( ) ;
System . out . p r i n t l n ( ” Tabellenname : ” + meta . getTableName ( 1 ) ) ;
System . out . p r i n t l n ( ” S p a l t e n a n z a h l : ” + meta . getColumnCount ( ) ) ;
System . out . p r i n t l n ( ” i d Auto−I n k r . : ” + meta . i s A u t o I n c r e m e n t ( 1 ) ) ;
...
Die zugehörige Ausgabe:
Tabellenname: PERSONEN
Spaltenanzahl: 4
id Auto-Inkr.: true
Schritt 6: Schließen der Datenbankverbindung
Abschließend muss noch die Verbindung mittels connection.close() geschlossen werden. Falls nicht schon manuell geschehen, werden durch diesen Aufruf auch alle im Rahmen der Verbindung erzeugten Datenbankobjekte geschlossen. Nach Möglichkeit sollte
man nicht mehr benötigte Statements und ResultSets jedoch schon früher schließen, um
dem Garbage Collector die Möglichkeit zu geben, den von ihnen belegten Speicher sofort
freizugeben.
Überblick: JDBC-Spaltentypen
Beim Auslesen des ResultSets versucht JDBC die zu Grunde liegenden Ergebnisdaten aus
der Datenbank in ihre jeweiligen Rückgabetypen der get()-Methoden umzuwandeln. Seit
Version 2.0 kennt JDBC 24 Datentypen, die zusammen mit ihren entsprechenden JavaDatentypen im folgenden aufgelistet werden. Die aufgeführten Typen stellen dabei aber
lediglich die Standard-Zuordnung dar. Bei der Abfrage eines Werts aus einem ResultSetObjekt können automatisch Konvertierungen über die get<type>()-Methoden vorgenommen werden. Ebenso können beim Setzen eines Werts in einer SQL-Anweisung über
die set<type>()-Methoden des Interfaces PreparedStatement Konvertierungen durchgeführt werden.
13
JDBC-Datentyp
Java-Datentyp
JDBC-Datentyp
Java-Datentyp
CHAR
VARCHAR
LONGVARCHAR
NUMERIC
DECIMAL
BIT
TINYINT
SMALLINT
INTEGER
BIGINT
REAL
FLOAT
java.lang.String
java.lang.String
java.lang.String
java.math.BigDecimal
java.math.BigDecimal
boolean
byte
short
int
long
float
double
DOUBLE
BINARY
VARBINARY
LONGVARBINARY
DATE
TIME
TIMESTAMP
BLOB
CLOB
STRUCT
ARRAY
REF
double
byte[]
byte[]
byte[]
java.sql.Date
java.sql.Time
java.sql.Timestamp
java.sql.Blob
java.sql.Clob
java.sql.Struct
java.sql.Array
java.sql.Ref
3.8 Erweiterte Funktionen von JDBC
Im Folgenden werden einige erweiterte Möglichkeiten von JDBC vorgestellt.
Prepared Statements
Das Interface PreparedStatement leitet sich vom Statement ab und repräsentiert ein
präkompiliertes SQL-Statement. Im Gegensatz zum Statement erhält ein konkretes PreparedStatementObjekt bei seiner Erzeugung einmalig einen SQL-Befehl, der nur bei seiner ersten Ausführung
von der Datenbank kompiliert wird. In der Folge kann die Anfrage sehr effizient mehrfach wiederholt werden, da der Kompiliervorgang wegfällt. Es ist außerdem möglich, das
Statement bei jeder Wiederholung mit unterschiedlichen Parametern auszuführen. Analog zu Statements werden auch PreparedStatements von einer Connection-Methode erzeugt. Variable Werte innerhalb des SQL-Statements werden durch Fragezeichen-Platzhalter
ersetzt, an deren Stelle später bei der Ausführung die übergebenen Parameter treten. Für
jeden Platzhalter muss je nach dessen Datentyp eine passende set<type>(int i, <type> x)Methode aus dem Interface PreparedStatement aufgerufen werden, wobei i für den i-ten
Platzhalter steht und x der einzusetzende Wert ist. Sobald man allen Platzhaltern einen
Wert zugewiesen hat, kann das Statement ausgeführt werden. Anschließend genügt es
einzelne Parameter zu ändern und das Statement erneut auszuführen. Beispiel:
...
PreparedStatement u p d a t e S a l e s = c o n n e c t i o n . p r e p a r e S t a t e m e n t (
”UPDATE c o f f e e s SET s a l e s = ? WHERE c o f f e e s o r t LIKE ? ” ) ;
// Beide Parameter s e t z e n
updateSales . setInt (1 , 75);
updateSales . setString (2 , ” Excelsa ” ) ;
u p d a t e S a l e s . executeUpdate ( ) ; // Ausf ühren
14
// Zwei neue Parameter s e t z e n
updateSales . setInt (1 , 100);
u p d a t e S a l e s . s e t S t r i n g ( 2 , ” Arabica ” ) ;
u p d a t e S a l e s . executeUpdate ( ) ; // Ausf ühren
// Nur e i n e n Parameter s e t z e n
u p d a t e S a l e s . s e t S t r i n g ( 2 , ” Robusta ” ) ;
u p d a t e S a l e s . executeUpdate ( ) ; // Ausf ühren
...
Stored Procedures und Callable Statements
Stored Procedures fassen Abläufe von Anweisungen unter einem Namen zu einer Prozedur zusammen und speichern diese auf dem Datenbankserver ab. Sie stehen dort als
eigenständiger Befehl zur Verfügung und können bei Bedarf aufgerufen werden. Diese
abgespeicherten Prozeduren bieten sich für häufig verwendete Abläufe an, die andernfalls durch viele einzelne Clientbefehle ausgeführt werden müssten. Da nun alle Befehle
bereits auf dem Server liegen, müssen weniger Daten ausgetauscht werden, wodurch eine
Steigerung der Leistung möglich ist. JDBC unterstützt Stored Procedures in Form von
CallableStatements, die wiederum von Statement abgeleitet sind und in der Verwendung
den PreparedStatements ähneln. Ein Beispiel:
...
C a l l a b l e S t a t e m e n t c s = con . p r e p a r e C a l l ( ”{ c a l l doBigJob ( ? , ? ) } ” ) ;
c s . s e t S t r i n g ( 1 , ” S t r i n g parameter ” ) ; // Parameter 1
c s . s e t F l o a t ( 2 , 7 . 3 8 f ) ; // Parameter 2
c s . r e g i s t e r O u t P a r a m e t e r ( 1 , Types .INTEGER ) ;
cs . execute ( ) ;
int r e s u l t = c s . g e t I n t ( 1 ) ; // E r g e b n i s h o l e n
...
Zunächst wird der Prozeduraufruf vorbereitet, anschließend werden zwei Parameter
übergeben. Falls der Aufruf ein oder mehrere Ergebnis(se) liefert, müssen diese vor
der Ausführung des Aufrufs mit registerOutParameter() je nach Rückgabetyp registriert werden. Nach der Ausführung können eventuelle Ergebnisse mit den bekannten
get<type>()-Methoden eingeholt werden.
Transaktionen
Unter Transaktionen versteht man Folgen von Operationen, die als logische Einheit
betrachtet und entweder vollständig ( Commit“) oder überhaupt nicht ( Abort“) aus”
”
geführt werden. In JDBC ist standardmäßig jedes Statement für sich genommen eine
Transaktion, deren Auswirkungen sich erst nach erfolgreicher Ausführung manifestie-
15
ren. Hierzu wird implizit eine commit()-Methode aufgerufen, die den vorläufigen in
einen permanenten Zustand überführt. Der Entwickler merkt hiervon nichts. Ruft man
jedoch die Connection-Methode con.setAutoCommit(false) auf, werden alle darauf
folgenden Statements nicht mehr automatisch manifestiert, sondern zunächst zu einer
Transaktion zusammengefasst. Die Methode con.commit() führt schließlich alle in die
Transaktion eingeschlossenen Statements aus, oder eben keines, falls die geschlossene
Ausführung nicht möglich war. Mit setAutoCommit(true) kehrt man wieder in den
normalen Modus zurück. Ein Beispiel:
...
con . setAutoCommit ( f a l s e ) ;
PreparedStatement ps1 = con . p r e p a r e S t a t e m e n t ( s q l 1 ) ;
// S e t p a r a m e t e r s o f ps1 h e r e
ps . executeUpdate ( ) ;
PreparedStatement ps2 = con . p r e p a r e S t a t e m e n t ( s q l 2 ) ;
// S e t p a r a m e t e r s o f ps2 h e r e
ps2 . executeUpdate ( ) ;
con . commit ( ) ;
con . setAutoCommit ( true ) ;
...
3.9 Fazit
JDBC bringt alle nötigen und etablierten Funktionen mit, um produktiv Datenbankanwendungen zu entwickeln. Mit entsprechenden Treibern können JDBC-Anwendungen
bei minimalem Aufwand auf jede Plattform portiert und die darunterliegende Datenbank nach Belieben ausgetauscht werden. Nachteilig ist die geringere Performanz im
Vergleich zur Alternative SQLJ und auch die Tatsache, dass Fehler in Datenbankanfragen erst zur Laufzeit erkannt werden. Zudem ist z.B. der Zugriff auf Ergebnisspalten
nur über einzelne get()-Methoden möglich, was dem ein oder anderen Entwickler aus
anderen Bereichen umständlich erscheinen wird. In jedem Fall aber wurde mit JDBC
eine wichtige Alternative zu ODBC-Anwendungen oder gängigen Kombinationen aus
Skriptsprache und Datenbank geschaffen.
4 Java DB
4.1 Einführung
Java DB ist Suns Variante der vollständig in Java geschriebenen, relationalen Datenbank Apache Derby[2]. Sie bietet sich insbesondere als eingebettete Datenbank an und
unterstützt die Standards SQL und JDBC.
16
Entwicklung
Apache Derby wurde ursprünglich von Cloudscape entwickelt und 1997 veröffentlicht.
1999 wurde Cloudscape von Informix Software aufgekauft, deren Datenbanksparte 2001
von IBM übernommen wurde. IBM brachte die Datenbanksoftware 2004 unter dem Namen Derby“ in ein neues Open Source Projekt unter dem Dach der Apache Software
”
Foundation ein1 . 2006 wurde Derby schließlich unter dem Namen Java DB in Java 6
integriert und mit dem JDK ausgeliefert. Sun verfolgt dabei nicht das Ziel einer eigenständigen Entwicklungsschiene, sondern behält sich Erweiterungen und Aktualisierungen der Datenbank vor, die dann wieder in das ursprüngliche Open Source Projekt
einfließen. Somit bleibt Java DB eine freie Datenbank, die lediglich unter anderem Namen vertrieben wird.
Motivation
Mit der Derby Project Charter“ hat sich Apache bei der Entwicklung von Derby einige
”
für Entwickler interessante Ziele gesetzt: Reiner Javacode, Einfache Benutzung, Kleiner Fußabdruck“, Standardisierung und Sicherheit. Tatsächlich ist Derby vollständig in
”
Java geschrieben (somit uneingeschränkt kompatibel mit allen Plattformen, auf denen
eine JVM läuft), die bereits voll einsatzfähige Hauptbibliothek derby.jar ist lediglich 2,5
MByte groß und die Datenbank an sich erfordert praktisch keinen Administrationsaufwand. Zur Installation genügt es bereits, derby.jar zum Klassenpfad der Anwendung
hinzuzufügen. Anders als andere Datenbanken ist Derbys JDBC-Treiber bereits voll in
die Datenbank integriert und muss nicht zusätzlich installiert werden. Auch die Tatsache, dass die Datenbank in der gleichen JVM läuft, wie die eigentliche Anwendung,
dürfte sie von anderen Datenbanken abheben. Zudem bleiben mit der Implementierung
des JDBC- und des SQL-Standards eigentlich keine Wünsche offen.
4.2 Architektur von Java DB
Abbildung 3 zeigt den grundlegenden Aufbau und die strukturellen Einsatzmöglichkeiten
der Datenbank. Derby ist aus mehreren Modulen aufgebaut, die im rechten Teil der Abbildung dargestellt sind. Der Zugriff erfolgt über die Module JDBC und SQL. Das SQLModul ist für das Parsen und die Optimierung von SQL-Anweisungen zuständig. Das
Zugriffsmodul bestimmt unter Einbeziehung von Indizes und Statistiken den günstigsten
Zugriffspfad. Als Datenstrukturen kommen balancierte B+-Bäume, Heaps und Hashtabellen zum Einsatz. Das Speichermodul verarbeitet Daten, verwaltet den Buffer, die
Daten und die Transaktionslogs. Als Speicher wird nur das Dateisystem verwendet,
Derby kann somit nicht auf Systemen ohne Plattenspeicher eingesetzt werden. Konkrete
Datenbanken werden in einem Verzeichnis gespeichert, das den gleichen Namen wie die
jeweilige Datenbank trägt. Es enthält die drei Unterverzeichnisse seg0 (für die eigentliche
Datenbank, deren Daten, Strukturen und Indizes), log (für das Transaktionslog) und tmp
(für temporäre Arbeitsdaten). Außerdem existieren die Dateien service.properties
1
Der Hut auf Seite 1 wurde von der Community zum offiziellen Apache Derby Logo gewählt.
17
(enthält Konfigurationsparameter der Datenbank) und db.lck (eine Sperre für das einmalige Starten). Im Verzeichnis seg0 liegen die eigentlichen Daten, wobei für jede Tabelle eine eigene cXXX.dat existiert. Somit ist die Tabellengröße von der maximalen
Dateigröße des Betriebssystems abhängig. Derby unterstützt die Verteilung der Log, Tmp- und Datenbankverzeichnisse auf mehrere Festplatten, um so die Schreib- und
Leseraten zu erhöhen.
Derby-Tools
Möchte man die Datenbank für bestimmte Zwecke (z.B. Administration) nicht über
JDBC ansprechen, kann dies über die mitgelieferten Derby-Tools geschehen: Das Programm ij dient dem Ausführen fertiger Skripts oder interaktiver SQL-Anfragen über
die Konsole, dblook ist ein Schema-Extraktionstool und sysinfo dient lediglich der
Anzeige von Versionsnummern und Klassenpfad.
Prinzipielle Einsatzmöglichkeiten
In Abbildung 3 stellt Szenario 1 den Einsatz als eingebettete Datenbank dar, für den
lediglich die Hauptbibliothek derby.jar nötig ist. Für den Netzwerkbetrieb (Szenario 2 und 3), bei dem Anwendung und Datenbank in getrennten Umgebungen laufen, werden die Bibliotheken für Netzwerkserver und -client benötigt (derbynet.jar
und derbyclient.jar). Die Kommunikation zum Netzkwerkserver erfolgt über eine
TCP/IP-Verbindung und das Standardprotokoll DRDA. Über JDBC bzw. ODBC können
Clients unterschiedlicher Programmiersprachen eine Verbindung zum Server aufbauen.
Treiber
Der zu Java DB gehörende JDBC-Treiber trägt den Namen
org.apache.derby.jdbc.EmbeddedDriver“ und kann wie üblich mit Class.forName ge”
laden werden. Die URL für getConnection() lautet "jdbc:derby:dbname".
Erweiterte Funktionen von Java DB
Einige weitere erwähnenswerte Funktionen von Java DB sind Mehrbenutzerfähigkeit, die
Unterstützung von Parallelität, Transaktionsmanagement, Stored Procedures, Triggern
und Datenbankdateiverschlüsselung. Zudem können neue Versionen der Datenbank die
alten Daten einfach weiter verwenden.
Nachteile von Java DB
Zwei auffällige Nachteile bei der Verwendung der Datenbank sind Folgende:
∙ Zugriffsrechte können nur für Benutzer, nicht für Gruppen vergeben werden.
∙ Derby kennt keine LIMIT bzw. PAGING oder OFFSET Konstrukte[3]. Es ist somit
nicht ohne weiteres möglich durch Teilmengen gespeicherter Daten zu blättern“,
”
18
Abbildung 3: Architektur und Einsatzmöglichkeiten von Java DB
also z.B. die i-ten 10 Datensätze anzufragen. Dies kann unter Umständen die Leistung beeinträchtigen.
4.3 Fazit
Die Vermutung liegt nahe, dass hauptsächlich kleine und mobile Geräte von Java DB
profitieren könnten, doch der Einsatz eingebetteter Datenbanken ist gerade in traditionellen Anwendungen viel weiter verbreitet als gedacht: So verfügt z.B. Open Office über
eine (ebenfalls in Java geschriebene) integrierte Datenbank namens HSQLD und Firefox
verwaltet seine Bookmarks und die History mit SQLite. Auch das Verwalten von Logdaten, Benutzereinstellungen, Adressen, Nachrichten und anderem kann mit geringem
Aufwand von einer Datenbank erledigt werden. Zwar bietet z.B. Java ME (Java Platform, Micro Edition) zumindest für mobile Endgeräte ein Record Management System
(RMS), doch ähnelt dies einer satzorientierten Datenbank, die nur eingeschränkte Suchfunktionen und einen langsamen, insgesamt eher umständlichen Datenzugriff bietet[1].
Somit kann Java DB gerade im Bereich der eingebetteten Datenbanken als sehr gute
Alternative angesehen werden.
19
5 Literatur
[1] Al-Biladi, Yasser: Konzeption und Implementierung einer mobilen Anwendung für
J2ME-fähige Endgeräte, HTWK Leipzig, Fachbereich Informatik, Mathematik und Naturwissenschaften, Diplomarbeit, Juni 2004
[2] Apache: Apache Derby. http://db.apache.org/derby/. – [Online; Stand 8. Juli 2009]
[3] Bosanac, Dejan: Tuning Derby. http://www.onjava.com/pub/a/onjava/2007/01/31/
tuning-derby.html. Version: 2009. – [Online; Stand 3. Juli 2009]
[4] Foundation, Apache: Apache Derby logo contest. http://issues.apache.org/jira/
browse/DERBY-297. Version: 2005. – [Online; Stand 5. Juli 2009]
[5] Hobbs, Ashton: Teach Yourself Database Programming With Jdbc in 21 Days (Teach
Yourself Series). Sams Publishing, 1997. – ISBN 1575211238
[6] Kemper, Alfons ; Eickler, Andre: Datenbanksysteme. Oldenbourg Wissensch.Vlg, 2006.
– ISBN 3486576909
[7] Microsoft: Microsoft Open Database Connectivity (ODBC). http://msdn.microsoft.
com/en-us/library/ms710252(VS.85).aspx. – [Online; Stand 8. Juli 2009]
[8] Riccardi, Greg: Principles of Database Systems With Internet and Java Applications.
Addison Wesley Longman, 2000. – ISBN 020161247X
[9] Sun: Frequently Asked Questions about JDBC. http://java.sun.com/products/jdbc/
reference/faqs/index.html#11. – [Online; Stand 8. Juli 2009]
[10] Sun: Java SE Technologies - Database. http://java.sun.com/javase/technologies/
database/. – [Online; Stand 8. Juli 2009]
[11] Sun: JDBC downloads and specifications. http://java.sun.com/products/jdbc/
download.html. Version: 2006. – [Online; Stand 8. Juli 2009]
[12] Sun:
JDBC Drivers.
http://developers.sun.com/product/jdbc/drivers.
Version: 2009. – [Online; Stand 5. Juli 2009]
[13] Sun: Types of JDBC technology drivers. http://java.sun.com/products/jdbc/
driverdesc.html. Version: 2009. – [Online; Stand 3. Juli 2009]
20
Herunterladen