Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage
<< | < | > | >> | API | Kapitel 42 - Datenbankzugriffe mit JDBC

## 42.2 Grundlagen von JDBC

- 42.2 Grundlagen von JDBC
  - 42.2.1 Öffnen einer Verbindung
  - 42.2.2 Erzeugen von Anweisungsobjekten
  - 42.2.3 Datenbankabfragen
  - 42.2.4 Datenbankänderungen
  - 42.2.5 Die Klasse SQLException

### 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:

- Wird ein numerischer Wert n als Argument übergeben, so wird dieser als Spaltenindex interpretiert und der Wert der n-ten Spalte zurückgegeben. Wichtig: Anders als bei Arrays hat die erste Spalte den Index 1.
- Wird ein `String` als Argument übergeben, so wird er als Name interpretiert und der Wert der Spalte mit diesem Namen zurückgegeben. Diese Variante soll zwar marginal langsamer als die erste sein, ist aber weniger fehlerträchtig. Da der Aufruf nicht mehr von der Spaltenreihenfolge der Abfrage abhängt, ist ihr normalerweise der Vorzug zu geben (das klappt leider nicht immer, wie die Warnung am Ende von Abschnitt 42.4.5 deutlich macht).

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 | DOC | Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0
<< | < | > | >> | API | © 1998-2002 Guido Krüger, http://www.javabuch.de