20.7 Mit Java an eine Datenbank andocken
 
Die Verbindung zu einer Datenbank wird über die Klasse DriverManager und die Schnittstelle Connection aufgebaut. Alle verwendeten Pakete liegen unter java.sql.*. Vor der Ausführung der JDBC-Befehle muss ein passender Datenbanktreiber geladen werden. In unserem Beispiel verwenden wir die von JavaSoft mitgelieferte JDBC-ODBC-Bridge, deren Klasse unter dem Namen JdbcOdbcDriver verfügbar ist. Damit können wir uns zu allen angemeldeten ODBC-Datenquellen verbinden.
20.7.1 Der Treibermanager
 
Alle Datenbanktreiber müssen an einer Stelle gesammelt werden, dem Treibermanager. Dazu dient eine besondere Java-Klasse, die Klasse DriverManager. Wie wir später sehen werden, bietet der Treibermanager eine Methode getConnection() an, mit der wir eine Verbindung zur Datenbank aufbauen können.
20.7.2 Eine Aufzählung aller Treiber
 
Die statische Methode getDrivers() der Klasse DriverManager liefert eine Aufzählung der angemeldeten Treiber. Alle Methoden der Klasse sind statisch, da sich ein Exemplar dieser Klasse nicht erzeugen lässt; der Konstruktor ist privat. Ein normales Programm hat in der Regel keinen angemeldeten Datenbanktreiber. Eine Aufzählung bekommen wir durch folgende Zeilen:
for ( Enumeration e = DriverManager.getDrivers(); e.hasMoreElements(); )
System.out.println( e.nextElement().getClass().getName() );
Die Elemente, die durch die Enumeration ausgelesen werden, sind Treiber-Objekte. Jeder Datenbanktreiber ist als Treiber-Objekt implementiert. Da Driver aber eine Schnittstelle ist, gibt es keine sinnvolle toString()-Methode, und wir bekommen den Klassennamen und einen Hashwert. Schöner ist der Klassename, den wir über ein Class-Objekt erfragen können. getClass() liefert das Klassen-Objekt und getName() den Namen. Ohne geladenen Treiber bekommen wir keine Ausgabe. Laden wir den JDBC-ODBC-Treiber, etwa durch
Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver" );
dann ist die Ausgabe:
sun.jdbc.odbc.JdbcOdbcDriver
20.7.3 Log-Informationen
 
Zu Testzwecken bietet es sich an, Informationen des Treibers und der Datenbank in einen speziellen Ausgabekanal zu schreiben. Wir können die Log-Informationen so umlenken, dass sie in den Standard-Ausgabestrom geschrieben werden. Dazu dient die statische Methode setLogWriter(). Sie bekommt einen PrintWriter als Parameter. Vor Java 1.2 hieß die Methode noch setLogStream(), diese nahm als Parameter einen PrintStream. Sie ist nun veraltet.
Folgende Zeile gibt alle Informationen auf dem Bildschirm aus, die in das Logbuch geschrieben werden:
DriverManager.setLogWriter( new PrintWriter(System.out) );
Ladevorgang der Treiber protokolliert ausgeben
Eine solche Ausgabe ist hilfreich, da interessante Aussagen über die Funktionsweise der Treiber auf diese Art offenbar werden. Zur Set-Methode existiert die passende getLogWriter()-Methode, die den PrintWriter zurückgibt. Eine Anfrage an getLogWriter() gibt null zurück, was verrät, dass standardmäßig keine Ausgabe stattfindet. Es wird also keine versteckte Datei erzeugt.
Testen wir die Ausgabe, die die beiden Programmzeilen erzeugen:
DriverManager.setLogWriter( new PrintWriter(System.out) );
Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver" );
Zunächst setzen wir die Ausgabe auf den Standard-Ausgabekanal. Dann laden wir die Treiberklasse für die JDBC-ODBC-Brücke. An dieser Stelle greifen wir schon etwas vor. Die Ausgabe zeigt dann den eingetragenen Treiber.
DriverManager.initialize: jdbc.drivers = null
JDBC DriverManager initialized
registerDriver: driver[className=sun.jdbc.odbc.JdbcOdbcDriver,\
sun.jdbc.odbc.JdbcOdbcDriver@1fcc69]
Die Methode setLogWriter() ist die erste Methode, die die Klasse DriverManager benutzt. Daher bekommt der Klassenlader die Aufgabe, die Klasse DriverManager zu laden. setLogWriter() speichert dann das PrintWriter-Objekt in einer privaten Variablen und macht sonst nichts.
Erst das Laden eines Treibers führt zum Aufruf der statischen initialize()-Methode. Sie führt die private Methode loadInitialDrivers() aus, die zur ersten Ausgabezeile führt. Hier sind noch keine Treiber angemeldet, da in den Eigenschaften »jdbc.drivers« nichts eingetragen ist. Diese Eigenschaft wird in der Regel dann gesetzt, wenn von außen über den Schalter »-D« eine Klasse angesprochen wird. Nach dem Suchen in den Eigenschaften folgt die Ausgabe »JDBC DriverManager initialized«. Nun hat der Treiber die DriverManager-Klasse hochgefahren, und der Treibermanager kann den Treiber anmelden. Er ist vom Typ Driver. Dieser wird zusammen mit dem zugehörigen Class-Objekt und einem Namen in der internen Klasse DriverInfo in einem internen Vector gespeichert. Die Ausgabe »registerDriver:[...]« stammt von der Anmeldung des Treibers. Wir sehen genau die Informationen, die in der Klasse DriverInfo gespeichert sind. Die Ausgabe wird auch von toString() von DriverInfo generiert.
Nicht nur Treiber und SQL-Klassen nutzen den Log-Stream, auch wir können Zeichenketten ausgeben. Dazu dient die statische Methode println(), die als Parameter nur einen String annimmt. println() ist so implementiert, dass bei einem nicht gesetzten Log-Stream die Ausgabe unterbleibt.
20.7.4 Den Treiber laden
 
Der Datenbanktreiber ist eine ganz normale Java-Klasse, die sich bei einem Treibermanager automatisch anmeldet. Unsere Aufgabe ist es nur, ein Treiber-Objekt einmal zu erzeugen. Um eine Klasse zur Laufzeit zu laden und so ein Laufzeit-Objekt zu erstellen, gibt es mehrere Möglichkeiten. Eine davon nutzt die native statische Methode forName() der Klasse Class. Die Syntax für das Laden der JDBC-ODBC-Bridge lautet somit:
Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver" );
Um möglichst unabhängig zu bleiben, sollte die Klasse auch nicht hart einkodiert werden. Besser ist es, den Klassennamen in eine Property zu schreiben. Da wir die Klasse nur laden, aber die Referenz auf den Klassen-Deskriptor nicht benötigen, belassen wir es bei einem Aufruf und beachten den Rückgabewert nicht. Diese Operation löst eine ClassNotFoundException aus, falls die Klasse nicht gefunden wurde, der Treiber also nicht geladen werden konnte.
Datenbank
|
Klassenname für den JDBC-Treiber
|
Oracle
|
oracle.jdbc.driver.OracleDriver
|
mSQL
|
COM.imaginary.sql.msql.MsqlDriver
|
MySQL
|
org.gjt.mm.mysql.Driver
|
IBM DB2
|
COM.ibm.db2.jdbc.net.DB2Driver
|
Borland JDataStore
|
com.borland.datastore.jdbc.DataStoreDriver
|
Borland Interbase
|
interbase.interclient.Driver
|
Adabas D
|
de.sag.jdbc.adabasd.ADriver
|
SYBASE ASE
|
com.sybase.jdbc.SybDriver
|
IDS Server
|
ids.sql.IDSDriver
|
Die Klasse muss nicht zwingend zur Laufzeit geladen werden. Sie kann auch in der Kommandozeile über den Schalter »-D« eingebunden werden. Dazu setzen wir mit der Eigenschaft jdbc.drivers einen Datenbanktreiber fest.
java -Djdbc.drivers=sun.jdbc.odbc.JdbcOdbcDriver <Javaklasse>
final class java.lang.Class
implements Serializable
|
|
static Class forName( String clazz ) throws ClassNotFoundException
Sucht, lädt und bindet die Klasse mit dem qualifizierten Namen clazz ins Laufzeitsystem ein. Es wird ein Class-Objekt zurückgegeben, falls die Klasse geladen werden kann, andernfalls wird dies mit einer ClassNotFoundException quittiert. |
Hinweis In einigen Programmlistings findet sich die Zeile zum Laden des Treibers, die gleich mit newInstance() ein Objekt des Treibers erzeugt.
Class.forName( "org.gjt.mm.mysql.Driver" ).newInstance();
Das ist im Prinzip nicht nötig, denn Class.forName() sollte die Klasse laden. Es gibt jedoch virtuelle Maschinen in Applets, die das Klassenladen aus Geschwindigkeitsgründen vermeiden und dann keine Klassendatei vom Server beziehen. In solchen Fällen muss der Laufzeitumgebung mit dem Bilden eines Exemplars explizit der Hinweis gegeben werden, dass die Treiber-.class-Datei nötig ist, damit sich der Treiber beim Treibermanager anmelden kann.
|
20.7.5 Wie Treiber programmiert sind
 
Wenn wir selbst einen Treiber schreiben müssten, würden wir zunächst einmal die Schnittstelle Driver implementieren. Dann müssten wir alle sechs Methoden programmieren. Zur Demonstration soll einmal ein fiktiver Treiber umgesetzt werden, der Treiber usql1
.
Einen Konstruktor müssen wir nicht sichtbar implementieren, da Class.forName() oder ähnliche Programmanweisungen die Klasse laden, das Objekt aber nicht erzeugen.
package com.javatutor.usql;
import java.sql.*;
import java.util.Properties;
public class UsqlDriver implements Driver
{
Würden wir unseren eigenen Datenbanktreiber jetzt aus einem anderen Programm nutzen wollen, könnten wir mit Class.forName("com.javatutor.usql.UsqlDriver“) den Treiber bekannt machen.
Die erste Methode ist acceptsURL(), aufgrund der der Treibermanager entscheidet, ob ein Treiber eine Datenbank ansprechen kann oder nicht. Der Treibermanager wird alle Treiber mit einer URL fragen, ob sie den Job übernehmen. Daher muss acceptsURL() nun testen, ob unser Usql-Treiber sich nun für eine Datenbank verantwortlich fühlt, wenn als Protokoll »usql« in der URL steht. Die URL beginnt immer mit dem Präfix »jdbc:«. Dahinter steht der Name des Treibers. Also testen wir die Stelle 5 bis 9 und sehen nach, ob dort »usql« steht. Wenn ja, dann sollten wir den Job übernehmen und true zurückgeben. Dann wird der Treibermanager keine anderen Treiberklassen mehr fragen, sondern uns den Auftrag erteilen.
public boolean acceptsURL( String s ) throws SQLException
{
s = s.trim();
if ( s.length() < 11 )
return false;
else
return s.substring(5, 9).equals("usql");
}
Bekommen wir den Auftrag, dann dürfen wir die Verbindung aufbauen. Da wir allerdings auch hintenherum ohne den Treibermanager mittels connect() eine Verbindung aufbauen können, testen wir noch einmal zur Sicherheit, ob die URL wirklich uns meint. Ist alles in Ordnung, erzeugen wir ein UsqlConnection-Objekt, das an anderer Stelle programmiert werden muss.2
public synchronized Connection
connect( String s, Properties properties ) throws SQLException
{
if ( !acceptsURL(s) )
return null;
else
return new UsqlConnection( this, s, properties );
}
Die Klasse DriverPropertyInfo nimmt Informationen für die Verbindung auf, die von getPropertyInfo() abgefragt werden können. Wir wollen hier keine Eigenschaften bestimmen und nur ein leeres Feld zurückgeben.
public DriverPropertyInfo[]
getPropertyInfo( String s, Properties prop )
throws SQLException
{
return new DriverPropertyInfo[0];
}
Zwei weitere Methoden erlauben es den Programmierern, die Treiber-Version zu erfragen. Da sich unser Treiber am Anfang seiner Entwicklung befindet, ist die Version 0.1. So etwas wie 0.001 funktioniert leider nicht, da der Rückgabewert immer ein Integer ist.
public int getMajorVersion() {
return 0;
}
public int getMinorVersion() {
return 1;
}
Da wir ein echter JDBC-Treiber sind, der das komplette JDBC-API sowie SQL-92 unterstützt, geben wir bei jdbcCompliant() selbstbewusst true zurück.
public boolean jdbcCompliant() {
return true;
}
Nun ist keine Methode mehr zu implementieren, und das Spannendste wird im statischen Initialisierungsblock gemacht.
static {
try {
DriverManager.registerDriver( new UsqlDriver() );
}
catch ( SQLException e ) { System.out.println( e ); }
}
} // Ende der Klasse UsqlDriver
Wir sehen hier, dass der Treiber sich selbstständig im DriverManager anmeldet. Dazu bildet er ein Exemplar von sicht selbst über den Standard-Konstruktor. Sollte hier etwas schief gehen, dann fangen wir eine SQLException auf. Dies müssen wir auch, da uns die Methode registerDriver() vom Treibermanager diese Ausnahmebehandlung vorschreibt.
20.7.6 Verbindung zur Datenbank
 
Nach dem Laden des Treibers können wir eine Verbindung zur Datenbank mit Hilfe des Connection-Objekts aufbauen, welches von DriverManager.getConnection() zurückgegeben wird. Eine Verbindung wird mit speziellen Optionen parametrisiert, unter anderem mit dem Treiber, der die Datenquelle anspricht.
Die Datenquelle angeben
Alle Datenquellen sind durch eine besondere URL qualifiziert, die folgendes Format besitzt:
jdbc:Subprotokoll:Datenquellenname
Datenbank
|
Subprotokoll
|
Beispiel
|
ODBC-Datenquellen
|
odbc
|
jdbc:odbc:Pflanzen
|
mSQL
|
msql
|
jdbc:msql://host:1234/database
|
Interbase
|
interbase
|
jdbc:interbase://localhost/dabase.gdb
|
Oracle Thin
|
oracle:thin
|
jdbc:oracle:thin:@:1243:database
|
IBM DB2
|
db2
|
jdbc:db2://database
|
Sybase
|
sybase:Tds
|
jdbc:sybase:Tds:host:9234/database
|
MySQL
|
mysql
|
jdbc:mysql://host/database
|
Die URL für die Usql-Demo-Verbindung hätte das Format:
jdbc:usql:Datenquellenname
Verbindung aufnehmen
Die getConnection()-Methode liefert nun ein Connection-Objekt, das mit der Quelle verbunden ist. Die nachfolgende Anweisung verbindet uns mit einer Datenbank, die den Namen Pflanzen trägt. Diesen Namen haben wir im ODBC-Datenquellen-Administrator festgelegt und er hat nichts mit dem Dateinamen zu tun.
con = DriverManager.getConnection( "jdbc:odbc:Pflanzen",
"user",
"passwd" );
Die Methode getConnection() erwartet bis zu drei Parameter: Die URL der Datenbank, zu der die Verbindung aufgenommen werden soll, ist der Pflichtparameter. Der Anmeldename und das Passwort sind optional. Der Benutzername und das Passwort können auch leere Strings ("") sein. Dieses Vorgehen findet bei Textdateien, die als ODBC-Quellen eingesetzt werden, Verwendung, da Texte keine solchen Attribute besitzen. Meldet getConnection() keinen Fehler, so liefert sie uns eine geöffnete Datenbankverbindung.
class java.sql.DriverManager
|
|
static Connection getConnection( String url, Properties info ) throws SQLException
Versucht eine Verbindung zur Datenbank aufzubauen. Die Klasse DriverManager sucht dabei einen passenden Treiber aus der Liste der registrierten JDBC-Treiber für die Datenbank. Im Properties-Objekt sollten die Felder »user« und »password« vorhanden sein. |
|
static Connection getConnection( String url, String user, String password )
throws SQLException |
|
Versucht eine Verbindung zur Datenbank aufzubauen. user und password werden für die Verbindung zur Datenbank verwendet. |
|
static Connection getConnection( String url ) throws SQLException
Versucht eine Verbindung zur Datenbank aufzubauen. |
Wie der Treiber gefunden wird
Es lohnt sich, einmal hinter die Kulissen der Methode getConnection() zu sehen. Das DriverManager-Objekt wird veranlasst, die Verbindung zu öffnen. Dabei versucht es, einen passenden Treiber aus der Liste der JDBC-Treiber auszuwählen. Sein Treiber verwaltet die Klasse DriverManager in einem privaten Objekt DriverInfo. Dieses enthält ein Treiber-Objekt (Driver), ein Objekt (securityContext) und den Klassennamen (className). Während getConnection() die Liste (intern als Vector implementiert) der DriverInfo-Objekte abgeht, versucht dieser, sich über die connect()-Methode anzumelden. Merkt der Treiber, dass er mit der URL nicht viel anfangen kann, gibt er null zurück, und getConnection() versucht es mit dem nächsten Treiber. Ging alles daneben und keiner der angemeldeten Treiber konnte etwas mit dem Subprotokoll anfangen, bekommen wir eine SQLException("No suitable driver", "08001").
Verbindung beenden
Die Klasse DriverManager besitzt keine close()-Methode, wie wir erwarten würden. Vielmehr kümmert sich das Connection-Objekt selbst um die Schließung. Würde der Garbage-Collector also das Objekt von der Halde räumen, schließt er automatisch die Verbindung. Wollen wir selbst das Ende der Verbindung herbeiführen, rufen wir
con.close();
auf, und die Verbindung wird beendet. Auch hier kann eine SQLException auftauchen.
interface java.sql.Connection
|
|
void close() throws SQLException
Schließt die Verbindung zur Datenbank. |
Wartezeit einstellen
Wenn wir uns mit der Datenbank verbinden, lässt sich noch eine Wartezeit in Sekunden einstellen, die angibt, wie lange der Treiber für die Verbindung mit der Datenbank warten darf. Gesetzt wird dieser Wert mit setLoginTimeout() und entsprechend ausgelesen mit getLoginTimeout(). Standardmäßig ist dieser Wert 0.
class java.sql.DriverManager
|
|
static void setLoginTimeout( int seconds )
Setzt die Zeit, die maximal gewartet wird, wenn der Treiber sich mit einer Datenbank verbindet. |
|
static int getLoginTimeout()
Liefert die Wartezeit in Sekunden. |
1
Ullis SQL, sprich uskel.
2
Damit UsqlConnection als Connection-Objekt durchgeht, muss es fast 40 Methoden der Schnittstelle Connection implementieren. Das macht nur Sinn bei einer wirklichen Datenbank, die wir natürlich nicht haben.
|