Titel   Inhalt   Suchen   Index   API  Go To Java 2, Zweite Auflage, Handbuch der Java-Programmierung
 <<    <     >    >>  Kapitel 42 - Datenbankzugriffe mit JDBC

42.2 Grundlagen von JDBC



42.2.1 Öffnen einer Verbindung

Bevor mit JDBC auf eine Datenbank zugegriffen werden kann, muß zunächst eine Verbindung zu ihr hergestellt werden. Dazu muß der Datenbanktreiber geladen und initialisiert und mit Hilfe des Treibermanagers ein Verbindungsobjekt beschafft werden. Es bleibt während der gesamten Verbindung bestehen und dient als Lieferant für spezielle Objekte zur Abfrage und Veränderung der Datenbank. Alle Klassen zum Zugriff auf die JDBC-Schnittstelle liegen im Paket java.sql, das am Anfang des Programms importiert werden sollte:

import java.sql.*;

Jeder JDBC-Treiber hat einen statischen Initialisierer, der beim Laden der Klasse aufgerufen wird. Seine Aufgabe besteht darin, sich beim Treibermanager zu registrieren, um bei späteren Verbindungsanfragen von diesem angesprochen werden zu können. Das Laden der Treiberklasse wird üblicherweise durch Aufruf der Methode forName der Klasse Class erledigt (siehe Abschnitt 43.2.2). Um einen Treiber zu laden, muß man also seinen vollständigen Klassennamen kennen:

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

sun.jdbc.odbc.JdbcOdbcDriver ist der Name der JDBC-ODBC-Bridge, mit der die oben erwähnten Typ-1-Treiber realisiert werden. Die Namen alternativer Treiber sind der Dokumentation des jeweiligen Herstellers zu entnehmen.

Nachdem der Treiber geladen wurde, kann er dazu verwendet werden, eine Verbindung zu einer Datenbank aufzubauen. Dazu wird an die statische Methode getConnection der Klasse DriverManager ein String und eventuell weitere Parameter übergeben, um den Treibertyp, die Datenbank und nötigenfalls weitere Informationen festzulegen. getConnection gibt es in drei Ausprägungen:

static Connection getConnection(
  String url
)

static Connection getConnection(
  String url, 
  String user, 
  String password
)

static Connection getConnection(
  String url, 
  Properties info
)
java.sql.DriverManager

Die erste Variante erwartet lediglich einen Connection-String als Argument, der in Form eines URL (Uniform Ressource Locator, siehe Abschnitt 40.1.1) übergeben wird. Der Connection-String besteht aus mehreren Teilen, die durch Doppelpunkte voneinander getrennt sind. Der erste Teil ist immer "jdbc" und zeigt an, daß es sich um einen JDBC-URL handelt. Der zweite Teil wird als Sub-Protokoll bezeichnet und gibt an, welcher Treiber verwendet werden soll. Die übrigen Teile sind treiberspezifisch. Connection-Strings für die JDBC-ODBC-Bridge beginnen immer mit "jdbc:odbc", gefolgt von einem weiteren Doppelpunkt, nach dem der Name der ODBC-Datenquelle angegeben wird:

con = DriverManager.getConnection("jdbc:odbc:DirDB");

Die zweite Variante von getConnection erlaubt es, zusätzlich den Benutzernamen und das Passwort an die Datenbank zu übergeben. Das ist bei vielen Datenbanken erforderlich, um eine Verbindung aufbauen zu können. Bei der dritten Variante können zusätzlich mit Hilfe eines Properties-Objekts weitere, treiberspezifische Informationen übergeben werden. Welche Variante zu verwenden ist, muß der jeweiligen Treiberdokumentation entnommen werden.

Falls die Datenbank nicht geöffnet werden konnte, löst getConnection eine Ausnahme des Typs SQLException aus. Diese Ausnahme wird auch von fast allen anderen Methoden und Klassen verwendet, um einen Fehler beim Zugriff auf die Datenbank anzuzeigen.

 Hinweis 

Wenn die Verbindung erfolgreich aufgebaut werden konnte, liefert getConnection ein Objekt, das das Interface Connection implementiert. Dieses Verbindungsobjekt repräsentiert die aktuelle Datenbanksitzung und dient dazu, Anweisungsobjekte zu erzeugen und globale Einstellungen an der Datenbank zu verändern. Das Connection-Objekt kann durch Aufruf von close explizit geschlossen werden. Die Verbindung wird automatisch geschlossen, wenn die Connection-Variable vom Garbage Collector zerstört wird.

42.2.2 Erzeugen von Anweisungsobjekten

Alle Abfragen und Änderungen der Datenbank erfolgen mit Hilfe von Anweisungsobjekten. Das sind Objekte, die das Interface Statement oder eines seiner Subinterfaces implementieren und von speziellen Methoden des Connection-Objekts erzeugt werden können:

Statement createStatement()

PreparedStatement prepareStatement(String sql)

CallableStatement prepareCall(String sql)
java.sql.Connection

Die einfachste Form ist dabei das von createStatement erzeugte Statement-Objekt. Es kann dazu verwendet werden, unparametrisierte Abfragen und Änderungen der Datenbank zu erzeugen. Seine beiden wichtigsten Methoden sind executeQuery und executeUpdate. Sie erwarten einen SQL-String als Argument und reichen diesen an die Datenbank weiter. Zurückgegeben wird entweder ein einfacher numerischer Ergebniswert, der den Erfolg der Anweisung anzeigt, oder eine Menge von Datenbanksätzen, die das Ergebnis der Abfrage repräsentieren. Auf die beiden übrigen Anweisungstypen werden wir später zurückkommen.

Statement-Objekte sind bei manchen Treibern kostspielige Ressourcen, deren Erzeugen viel Speicher oder Rechenzeit kostet. Das Erzeugen einer großen Anzahl von Statement-Objekten (beispielsweise beim Durchlaufen einer Schleife) sollte in diesem Fall vermieden werden. Viele JDBC-Programme legen daher nach dem Öffnen der Verbindung eine Reihe von vordefinierten Statement-Objekten an und verwenden diese immer wieder. Obwohl das im Prinzip problemlos möglich ist, kann es in der Praxis leicht dazu führen, daß ein Statement-Objekt, das noch in Gebrauch ist (beispielsweise, weil seine Ergebnismenge noch nicht vollständig abgefragt ist), erneut verwendet wird. Das Verhalten des Programms ist dann natürlich undefiniert. Wir werden später in Abschnitt 42.4.5 eine Lösung für dieses Problem kennenlernen.

 Warnung 

42.2.3 Datenbankabfragen

Hat man ein Statement-Objekt beschafft, kann dessen Methode executeQuery verwendet werden, um Daten aus der Datenbank zu lesen:

public ResultSet executeQuery(String sql)
  throws SQLException
java.sql.Statement

Die Methode erwartet einen SQL-String in Form einer gültigen SELECT-Anweisung und gibt ein Objekt vom Typ ResultSet zurück, das die Ergebnismenge repräsentiert. Als Argument dürfen beliebige SELECT-Anweisungen übergeben werden, sofern sie für die zugrunde liegende Datenbank gültig sind. Die folgende SQL-Anweisung selektiert beispielsweise alle Sätze aus der Tabelle dir, deren Feld did den Wert 7 hat:

SELECT * FROM dir WHERE did = 7

Das zurückgegebene Objekt vom Typ ResultSet besitzt eine Methode next, mit der die Ergebnismenge schrittweise durchlaufen werden kann:

boolean next()
java.sql.ResultSet

Nach dem Aufruf von executeQuery steht der Satzzeiger zunächst vor dem ersten Element, jeder Aufruf von next positioniert ihn auf das nächste Element. Der Rückgabewert gibt an, ob die Operation erfolgreich war. Ist er false, gibt es keine weiteren Elemente in der Ergebnismenge. Ist er dagegen true, konnte das nächste Element erfolgreich ausgewählt werden, und mit Hilfe verschiedener get...-Methoden kann nun auf die einzelnen Spalten zugegriffen werden. Jede dieser Methoden steht in zwei unterschiedlichen Varianten zur Verfügung:

Um dem Entwickler lästige Typkonvertierungen zu ersparen, gibt es alle getXXX-Methoden in unterschiedlichen Typisierungen. So liefert beispielsweise getString das gewünschte Feld als String, während getInt es als int zurückgibt. Wo es möglich und sinnvoll ist, werden automatische Typkonvertierungen durchgeführt; getString kann beispielsweise für nahezu alle Typen verwendet werden. Tabelle 42.1 gibt eine Übersicht über die wichtigsten get-Methoden der Klasse ResultSet. In Tabelle 42.4 findet sich eine Übersicht der wichtigsten SQL-Datentypen.

Rückgabewert Methodenname
boolean getBoolean
byte getByte
byte[] getBytes
Date getDate
double getDouble
float getFloat
int getInt
long getLong
short getShort
String getString
Time getTime
Timestamp getTimestamp

Tabelle 42.1: get-Methoden von ResultSet

Soll festgestellt werden, ob eine Spalte den Wert NULL hatte, so kann das nach dem Aufruf der get-Methode durch Aufruf von wasNull abgefragt werden. wasNull gibt genau dann true zurück, wenn die letzte abgefragte Spalte einen NULL-Wert als Inhalt hatte. Bei allen Spalten, die NULL-Werte enthalten können, muß diese Abfrage also erfolgen. Bei den get-Methoden, die ein Objekt als Ergebniswert haben, geht es etwas einfacher. Hier wird null zurückgegeben, wenn der Spaltenwert NULL war.

 Hinweis 

42.2.4 Datenbankänderungen

Datenbankänderungen werden mit den SQL-Anweisungen INSERT INTO, UPDATE oder DELETE FROM oder den SQL-DDL-Anweisungen (Data Definition Language) zum Ändern der Datenbankstruktur durchgeführt. Im Gegensatz zu Datenbankabfragen geben diese Anweisungen keine Ergebnismenge zurück, sondern lediglich einen einzelnen Wert. Im Falle von INSERT INTO, UPDATE und DELETE FROM gibt dieser Wert an, wie viele Datensätze von der Änderung betroffen waren, bei DDL-Anweisungen ist er immer 0.

Um solche Anweisungen durchzuführen, stellt das Interface Statement die Methode executeUpdate zur Verfügung:

public int executeUpdate(String sql)
  throws SQLException
java.sql.Statement

Auch sie erwartet als Argument einen String mit einer gültigen SQL-Anweisung, beispielsweise:

INSERT INTO dir VALUES (1, 'x.txt', 0)

Könnte diese Anweisung erfolgreich ausgeführt werden, würde sie 1 zurückgeben. Andernfalls würde eine SQLException ausgelöst.

42.2.5 Die Klasse SQLException

Wenn SQL-Anweisungen fehlschlagen, lösen sie normalerweise eine Ausnahme des Typs SQLException aus. Das gilt sowohl, wenn keine Verbindung zur Datenbank zustande gekommen ist, als auch bei allen Arten von Syntaxfehlern in SQL-Anweisungen. Auch bei semantischen Fehlern durch falsche Typisierung oder inhaltlich fehlerhafte SQL-Anweisungen wird eine solche Ausnahme ausgelöst. SQLException ist eine Erweiterung der Klasse Exception und stellt folgende zusätzliche Methoden zur Verfügung:

int getErrorCode()

String getSQLState()

SQLException getNextException()
java.sql.SQLException

Mit getErrorCode kann der herstellerspezifische Fehlercode abgefragt werden, getSQLState liefert den internen SQL-Zustandscode. Etwas ungewöhnlich ist die Methode getNextException, denn sie unterstützt die Verkettung von Ausnahmen. Jeder Aufruf holt die nächste Ausnahme aus der Liste. Ist der Rückgabewert null, gibt es keine weiteren Ausnahmen. Code zum Behandeln einer SQLException könnte also etwa so aussehen:

001 ...
002 catch (SQLException e) {
003   while (e != null) {
004     System.err.println(e.toString());
005     System.err.println("SQL-State: " + e.getSQLState());
006     System.err.println("ErrorCode: " + e.getErrorCode());
007     e = e.getNextException();
008   }
009 }
Listing 42.1: Behandeln einer SQLException


 Titel   Inhalt   Suchen   Index   API  Go To Java 2, Zweite Auflage, Addison Wesley, Version 2.0
 <<    <     >    >>  © 2000 Guido Krüger, http://www.gkrueger.com