a.) Driver / DriverManager
Die Schnittstelle java.sql.Driver müssen nur Entwickler von Treibern verstehen. Die Schnittstelle umfaßt sechs Methoden, wobei eine für die Verbindung zwischen Treiber und Datenbank sorgt, und die anderen Methoden Informationen über den Treiber oder den Aufbau der Verbindung liefern. Die Driver-Methoden arbeiten normalerweise im Hintergrund, da sie durch die Klasse java.sql.DriverManager aufgerufen wird. Ein Benutzer baut üblicherweise eine Verbindung zu einer Datenbank durch den Aufruf der Methode DriverManager.getConnection auf. Der Treibermanager übernimmt dann den Rest für den Benutzer. Er ruft dann die Driver-Methode connect auf jedem registrierten Treiber der Reihe nach auf, und der erste Treiber, der die Verbindung aufbauen kann, wird ausgewählt.
Die Methoden von Driver
Diese Klasse ist die Verwaltungsebene von JDBC, die zwischen dem Treiber
und Benutzer agiert. Sie ist, wie schon in der Klasse Driver erwähnt,
zuständig für den Verbindungsaufbau zwischen Treiber und Datenbank.
Außerdem verwaltet sie die Treiber (2.1) und kümmert sie sich
um Zeitschranken beim Anmelden und die Ausgabe von Logbuch- und Protokollmeldungen.
Für leichtere Anwendungen braucht man nur eine Methode, nämlich
die für den Verbindungsaufbau : DriverManager.getConnection.
Man kann natürlich die Methoden getDriver, getDrivers
und registerDriver von DriverManager und connect von Driver
selber aufrufen, allerdings ist es fast immer besser es der Klasse DriverManager
zu überlassen.
2.1) Treiberverwaltung
Diese Klasse verwaltet eine Liste von Driver-Klassen, die sich durch Aufruf der Methode DriverManager.registerDriver selbst registriert haben. Diese Methode sollte vom Treiber automatisch aufgerufen werden, sobald der Treiber geladen ist. Durch eine der beiden folgenden Methoden ist eine Driver-Klasse geladen und automatisch registriert :
1. Aufruf der Methode Class.forName : Hier wird die Treiberklasse explizit geladen, und da es nicht von der Umgebung abhängt, ist es die empfohlene Methode. Quelltextbeispiel lädt Klasse acme.db.Driver : Class.forName(“acme.db.Driver“). Jetzt wird acme.db.Driver in der Treiberliste von DriverManager stehen, wenn es so implementiert wurde, daß durch das Laden eine Instanz erzeugt und die Methode DriverManager.registerDriver mit dieser Instanz als Parameter aufgerufen wurde.(Dies sollte der normale Fall sein.)
2. Man kann auch einen Treiber zu der java.lang.System-Eigenschaft jdbc.drivers hinzufügen. Wird die Klasse DriverManager initialisiert, sieht sie sich die Systemeigenschaft jdbc-drivers an, und wenn ein Treiber dort eingetragen wurde, versucht die Klasse DriverManager diese zu laden. Ein Quelltextbeispiel : jdbc.drivers=foo.bah.Driver. Hierfür wird eine voreingestellte,persistente Umgebung benötigt, gibt es daran allerdings Zweifel, sollte man die erste Methode wählen.
2.2) Aufbau einer Verbindung
Soll durch Aufruf der Methode DriverManager.getConnection eine
Verbindung zu einer Datenbank aufgebaut werden, so wird durch den DriverManager
der Reihe nach jeder Treiber aus der Treiberliste getestet, ob er eine
Verbindung aufbauen kann. Gibt es mehrere Treiber, die eine Verbindung
aufbauen können, wird der erste, der eine erfolgreiche Verbindung
aufgebaut hat, aus der Liste genommen. Das folgende Quelltextstück
zeigt, was man benötigt, um eine Verbindung mit einem Treiber (hier
z.B.:JDBC-ODBC-Brückentreiber) aufzubauen :
Class.forName(“jdbc.odbc.JdbcOdbcDriver“); //lädt den
Treiber
String url = “jdbc:odbc:fred“;
Connection con = DriverManager.getConnection(url,“uID“,“passwd“);
2.3) wichtige Methoden von DriverManager
Ein Connection-Objekt stellt eine Verbindung zu einer Datenbank dar. Wie in Klasse DriverManager beschrieben, wird eine Verbindung zu einer Datenbank durch DriverManager.getConnection zu einer Datenbank hergestellt.
1.) Senden von SQL-Anweisungen
Wenn eine Verbindung aufgebaut wurde, wird diese dazu benutzt eine SQL-Anweisung zu senden. Dabei wird durch JDBC jegliche Art von SQL-Anweisungen unterstützt. Allerdings muß der Benutzer sicherstellen, daß die Datenbank die SQL-Anweisung (z.B.: Aufruf einer gespeicherten Prozedur) verarbeiten kann, da sonst eine Ausnahme erzeugt wird. Es gibt drei Arten von Statements (SQL-Anweisung) : Statement, PreparedStatement und CallableStatement, die im Abschnitt c.) genauer erklärt werden. Wobei Statement durch die Connection-Methode createStatement aufgerufen wird, PreparedStatement durch prepareStatement und CallableStatement durch prepareCall.
2.) Transaktionen
Eine Transaktion besteht aus einer oder mehreren Anweisungen. Nach dem
Aufbau einer Verbindung ist der Auto-commit-Modus gesetzt, d.h. bei Beendigung
einer Anweisung wird die Methode commit automatisch aufgerufen.
Bei dieser Methode besteht eine Transaktion nur aus einer Anweisung. Ist
der Auto-commit-Modus deaktiviert, kann eine Transaktion aus einer oder
mehreren Anweisungen bestehen. Man muß hierbei beachten, daß
man das Ende einer Transaktion nur durch den expliziten Aufruf von commit
markiert. Will also ein Benutzer eine Änderung auf einer Datenbank
nur mit einer anderen Änderung abschicken, muß er erst den Auto-commit-Modus
ausschalten und sind dann beide Änderung erfolgreich, wird mit commit
die Änderung in der Datenbank festgeschrieben. Sollte eines der beiden
Updates fehlschlagen, kann man mit der Methode rollback die Änderungen
in der Datenbank durch die Updates verwerfen.
Es kann noch ein anderes Problem bei Transaktionen auftreten. Wenn
zwei Transaktionen gleichzeitig auf eine Datenbank zugreifen und dann die
Erste einen Wert ändert und die Zweite diesen Wert liest, bevor die
Erste die Änderung bestätigt, kann es zu Problemen kommen, wenn
die Erste die Änderung durch rollback zurück nimmt.
Dann benutzt also die zweite Transaktion einen falschen Wert(„Dirty Reads“).
Um dies zu verhindern, kann man Isolationsgrade für Transaktionen
festlegen. Je höher der Isolationsgrad, desto langsamer wird die Anwendung
wegen des erhöhten Sperraufwands und der verringerten Nebenläufigkeit
zwischen Benutzer. Man muß sich also zwischen Performanz- und Datenkonsistenzanforderungen
entscheiden. Hier die fünf Isolationsgrade, die man durch setTransactionIsolation(int
level)throws SQLException, setzen kann (vom Schwächsten bis zum
Stärksten) :
int TRANSACTION_NONE = 0;
int TRANSACTION_READ_UNCOMMITTED = 1;
int TRANSACTION_READ_COMMITTED = 2;
int TRANSACTION_REPEATABLE_READ = 4;
int TRANSACTION_SERIALIZABLE = 8;
Um den zweit höchsten Isolationsgrad zu setzen, ist folgender Aufruf nötig : con.setTransactionIsolation(TRANSACTION_REPEATABLE_READ)
3.) weitere wichtige Methoden von Connection
1.) Statement
Mit einem Statement-Objekt werden einfache SQL-Anweisungen ohne Para-meter ausgeführt. Ein kurzes Beispiel für ein Statement-Objekt, nachdem eine Verbindung mit getConnection aufgebaut wurde :
Statement stmt = con.createStatement(); erzeugt ein Statement-Objekt
ResultSet rs = stmt.executeQuery(“SELECT a,b,c FROM Table2“);
Hier wurde die an die Datenbank zu sendende SQL-Anweisung einer execute- Methode eines Statement-Objekts als Argument übergeben. Die Schnittstelle Statement stellt drei verschiedene Methoden zur Ausführung von SQL-Anweisungen zur Verfügung:
a.) Methode executeQuery
Diese Methode wurde für Anweisungen entwickelt, die eine einzige Ergebnismenge liefern, wie z.B. SELECT-Anweisungen
b.) Methode executeUpdate
Diese Methode gilt für Anweisungen wie INSERT,UPDATE und DELETE sowie für SQL DDL(Data Definition Language) wie CREATE TABLE oder DROP TABLE. Der Rückgabewert ist eine ganze Zahl int, die angibt, wie viele Zeilen betroffen waren, oder bei CREATE TABLE und DROP TABLE immer null, da diese nicht auf Zeilen arbeiten.
c.) Methode execute
Diese Methode dient zur Ausführung von Anweisungen, die mehr als eine Ergebnismenge, mehr als einen Update-Zähler oder eine Kombination von beiden zurückgeben. Dies ist eine weitergehende Funktion, die von Programmierern nie verwendet wird.
Es gilt zu beachten, daß die Verarbeitung
eines ResultSet-Objektes muß vor einer erneuten Ausführung eines
Statements-Objekts abgeschlossen sein !!!
Wichtige Methoden von Statement
2.) PreparedStatement (vorbereitete Anweisung)
Diese Schnittstelle erbt von der Schnittstelle Statement und unterscheidet
sich von dieser auf zweifache Weise :
Folgendes Beispiel zeigt eine Erzeugung eines PreparedStatement und die Übergabe der IN-Parameter :
PreparedStatement pstmt=con.prepareStatement(“UPDATE table4 SET m=? WHERE x = ?“);
Objekt pstmt enthält nun diese Anweisung, die bereits zum DBMS gesendet und für die Ausführung vorbereitet worden ist. Jetzt müssen die Platzhalter durch Werte ersetzt werden. Hierfür benutzen wir eine der set-Methoden. Nehmen wir an, die beiden Parameter sind vom Java-Typ long, dann müssen wir die Methode setLong benutzen. Das erste Argument dieser Methode gibt die Position des zu setzenden Parameters an, wobei die Numerierung mit 1 beginnt. Mit den folgenden Zeilen setzen wir den 1.Parameter auf 12304585 und den 2.Parameter auf 10303033 :
pstmt.setLong(1,12304585);
pstmt.setLong(2,10303033);
Ein Parameter bleibt solange gesetzt, bis er entweder durch einen neuen Wert ersetzt wird oder durch clearParameters gelöscht wird. Hierbei ist zu beachten, daß der Programmierer dafür verantwortlich ist, daß der Java-Typ des IN-Parameter in einem JDBC-Typ abgebildet wird, der mit dem erwarteten JDBC-Datentyp der Datenbank kompatibel ist. Die Tabelle in Abschnitt 4.) von Statement gibt an, welche der set-Methode man benutzen sollte. SetByte und SetString können Datenmengen beliebiger Größe übergeben. Es ist allerdings manchmal besser diese großen Datenmenge in kleinere Teile zu zerlegen, dafür kann man den IN-Parameter auf einen Eingabestrom setzen. Bei Ausführung der Anweisung führt der JDBC-Treiber wiederholte Aufrufe des Eingabestroms aus, um diesen zu übertragen. Es gibt drei Methoden zum Set-zen der IN-Parameter auf Eingabeströme : setBinaryStream, setAsciiStream und setUnicodeStream. Diese drei set-Methoden unterscheiden sich gegenüber den anderen set-Methoden darin, daß sie noch ein Argument mehr erwarten, da einige Datenbanksysteme vor der Übertragung der Daten deren Gesamtgröße benötigen.
Bei PreparedStatements ist zu beachten, daß manche Datenbanksysteme vorbereitete Anweisung nicht über Commits hinweg behält, so daß der Treiber in diesem Fall die vorbereitete Anweisung nach jedem Commit neu übersetzen muß. Bei diesem DBMS ist die Benutzung von PreparedStatement-Objekts statt eines wiederholt ausgeführten Statement-Objekt weniger effizient.
wichtige Methoden
bei PreparedStatement
CallableStatement-Objekte ermöglichen den für alle RDBMS standardisierten
Aufruf gespeicherter Prozeduren. Diese Prozeduren sind in der Datenbank
abgelegt und werden über ein CallableStatement-Objekt aufgerufen.
Hierfür ist die SQL-Escape-Syntax interressant. ( SQL-Escape-Syntax
Exkurs ). Normalerweise weiß jemand der ein CallableStatement-Objekt
benutzt, ob das DBMS gespeicherte Prozeduren unterstützt und was diese
Prozeduren sind. Man bekommt die Informationen allerdings auch über
die DatabaseMetaData-Methoden, wozu in Abschnitt f.) noch etwas gesagt
wird. CallableStatement erbt die Methoden von Statement, die sich mit SQL-Anweisungen
im allgemeinen beschäftigen, und es erbt die Methoden von PreparedStatement,
die IN-Parameter behandeln.
Man erzeugt ein CallableStatement-Objekt mit der Connection-Methode
prepareCall, was folgendes Beispiel zeigt :
CallableStatement cstmt=con.prepareCall(“{call getTestData(?,?)}“);
Die Variable cstmt enhält nach dieser Zeile einen Aufruf der gespeicherten Prozedur getTestData, die zwei Argument- und keinen Ergebnisparameter verwendet. Ob die beiden Platzhalter IN-, OUT- oder INOUT-Parameter darstellen, ist abhängig von der Prozedur getTestData.
Die IN-Parameter werden bei den CallableStatements genauso behandelt wie bei den PreparedStatements, also man setzt sie durch eine der set-Methoden.
Gibt eine gespeicherte Prozedur OUT-Parameter zurück, muß der JDBC-Typ jedes OUT-Parameters vor Ausführung des CallableStatement-Objekts registriert werden. Der JDBC-Typ wird mit der Methode registerOutParameter registriert. Wurde die Anweisung ausgeführt, kann man die get-Methoden dazu benutzen,die OUT-Parameterwerte zu lesen. Das folgende Beispiel zeigt die Behandlung der OUT-Parameter :
CallableStatementc stmt=con.prepareCall(“{call getTestData(?,?)}“);
cstmt.registerOutParameter(1,java.sql.Types.TINYINT);
cstmt.registerOutParameter(2,java.sql.Types.DECIMAL,3);
ResultSet rs = cstmt.executeQuery();
byte x = cstmt.getByte(1);
java.math.BigDecimal n = cstmt.getBigDecimal(2,3);
In diesem Beispiel werden also zuerst beide JDBC-Typen registriert, wobei der erste Wert ein int die Stelle des Platzhalters bezeichnet, für den ein JDBC-Typ registriert werden soll.(die Nummerierung beginnt auch hier bei 1; es ist also genauso wie bei den set-Methoden von PreparedStatement). Das zweite Argument gibt den JDBC-Typ an. Bei dem zweiten Aufruf von der registerOutParameter gibt es ein drittes Argument, dieser Aufruf wird nur verwendet bei JDBC Numeric und Decimal, denn er gibt die Anzahl der Stellen nach dem Komma an.
INOUT-Parameter sind Parameter, die Eingaben bereitstellen und Ausgaben entgegennehmen. Wie schon zu erahnen ist, setzt man den Parameterwert als Eingabeparameter mit einer set-Methode und außerdem registriert man mit der Methode registerOutParameter den JDBC-Typ als Ausgabeparameter. Der JDBC-Typ des IN-Werts und der der Methode registerOutParameter übergebene JDBC-Typ müssen gleich sein. Um einen Ausgabewert zu lesen, muß dann noch die entsprechende get-Methode gewählt werden. Das folgende Beispiel zeigt den Aufruf einer gespeicherten Prozedur reviseTotal, die als einzigen Parameter einen INOUT-Parameter besitzt :
CallableStatementc stmt=con.prepareCall(“{call reviseTotal(?)}“);
cstmt.setByte(1,25); setzen des IN-Parameter
cstmt.registerOutParameter(1,java.sql.Types.TINYINT); registriern
des JDBC-Typs
cstmt.executeUpdate(); ausführen der Anweisung
byte x = cstmt.getByte(1); lesen des OUT-Parameters
Noch ein Hinweis zu den OUT-Parameter : Man sollte die OUT-Parameter erst auslesen, nachdem man alle Werte aus einer Ergebnismenge gelesen worden sind (wie das geht, steht in Abschnitt d.)). Wegen der durch einige DBMS bedingten Einschränkungen ist dies für maximale Portabilität empfehlenswert.
Weitere wichtige Methoden bei CallableStatement
4.) Tabelle für die Abbildung von Java-Typen auf JDBC-Typen
Diese Tabelle zeigt also an, welche Konvertierungen für IN-Parameter
vor dem Absenden an das DBMS verwendet werden sollen.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Ein ResultSet-Objekt ist eine Tabelle, die die Ergebnisse nach Ausführung einer SQL-Anweisung enthält. Sie besteht also aus Zeilen, die die Bedingung der Anfrage erfüllen. Der Zugriff auf die verschiedenen Spalten der aktuellen Zeile erfolgt über eine der get-Methode, und den Sprung auf die nächste Zeile erreicht man mit der Methode ResultSet.next. Das folgende Beispiel zeigt, wie so eine Tabelle aussieht und wie sie ausgelesen wird, wenn die Anfrage SELECT a,b,c FROM Table1 lautet :
a
b
c
1256 Test
2354.56
42378 Hamburg 12.12
42 Herbst
4.453
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(“SELECT a,b,c FROM Table1“);
while (rs.next()) {
int i = rs.getInt(“a“);
string s = rs.getString(“b“);
float f = rs.getFloat(“c“); }
In diesem Beispiel wurde jetzt auf eine Ausgabe verzichtet. Man sieht
an diesem Beispiel aber schon, daß der Cursor, der auf die aktuelle
Zeile zeigt, anfangs vor die erste Zeile positioniert ist und erst nach
einem Aufruf von next auf die erste Zeile zeigt. Außerdem sieht man,
daß es hier neue Aufrufe der get-Methoden gibt. Es ist also möglich
nicht nur über die Spaltennummer, die hier von links nach rechts mit
1 beginnend numeriert sind, sondern auch über den Spaltennamen, wobei
hier nicht die Groß- und Kleinschreibung unterschieden wird, auf
die Werte zugreifen zu können. Die ist sinnvoll, wenn ein Benutzer
in der Anfrage Spaltennamen verwendet und dadurch dieselben Namen als Argument
in einer get-Methode verwenden kann. Falls man die Spaltennamen nicht genau
weiß, sollte man in der get-Methode lieber die Spaltennummer angeben.
Es kann auch vorkommen, daß eine Ergebnismenge mehr als eine Spalte
mit denselben Name zurückgibt. Benutzt man jetzt eine get-Methodenaufruf
mit Spaltennamen, wird auf die erste Spalte mit diesem Namen zugegriffen.
Hier ist also empfehlenswert mit einer Spaltennummer zu arbeiten. Ist zum
Beispiel der Spaltenname bekannt, und man möchte gerne die Spaltennummer
bekommen, bekommt man diese durch den Aufruf der Methode findColumn.
Über ResultSet.getMetaData bekommt man weitere Informationen
über die Spalten. Mit den ResultSetMetaData wird sich aber im Abschnitt
f.) genauer beschäftigt.
Ein ResultSet-Objekt wird automatisch geschlossen, wenn das Statement-Objekt,
das es erzeugt hat, geschlossen, erneut ausgeführt oder benutzt wird,
um das nächste Ergebnis aus einer Folge mehrerer Ergebnisse zu erhalten.
Es gibt auch die Methode close ,um das ResultSet-Objekt explizit
zu schließen.
wichtige Methoden von
ResultSet
e.) Ausnahmeklassen
Diese Klasse ist von java.lang.Exception abgeleitet und stellt Informationen
über aufgetretende Fehler beim Zugriff auf eine Datenbank zur Verfügung.
Folgende Information stehen beim Auftreten eines Fehlers zur Verfügung
:
Weitere Methoden in SQLException sind die aus java.lang.Exception geerbten
Methoden : fillInStackTrace, getMessage, printStackTrace
und toString. synchronized void setNextException(SQLException
next) stellt next als nächstes Element in die Ausnahmekette ein.
Diese Methode wird nur beim Schreiben von Werkzeugen und Treibern angewendet.
Diese Klasse stellt Informationen über Warnungen beim Zugriff auf
eine Datenbank zur Verfügung. Nach Aufruf der Methode getWarnings()
wird die erste Warnung geliefert. Existieren allerdings mehrere Warnungen,
kann man mit der SQLWarning-Methode getNextWarning() die nächste
Warnung abrufen. Bei den SQLWarnings ist zu beachten, daß die Ausführung
einer neuen Anweisung die Warnungen einer vorangegangenen Anweisung entfernt
werden. Die Informationen, die beim Auftreten einer Warnungen zur Verfügung
gestellt werden, sind dieselben wie bei den SQLExceptions. Sie auch mit
den gleichen Methoden abgefragt, also mit : getSQLState(), getMessage(),
getErrorCode() und getNextWarning(). Ähnlich wie bei den
SQLExceptions gibt es hier eine Methode void setNextWarning(SQLWarning
next). Diese fügt ein SQLWarning-Objekt am Ende der Kette ein.
Diese Klasse ist eine Unterklasse von SQLWarning. Von dieser Klasse
werden Informationen zur Verfügung gestellt, wenn von JDBC unerwartet
Datenwerte abgeschnitten werden (engl. truncate). Es kann unter bestimmten
Umständen passieren, daß nur ein Teil eines Datenfeldes in eine
Datenbank geschrieben oder aus ihr gelesen wird. Beim unerwarteten Abschneiden
von Daten während des Lesens aus einer Datenbank wird eine SQLWarning
ausgegeben, beim unerwartetem Abschneiden beim Schreiben in eine Datenbank
wird allerdings eine SQLException ausgelöst. Es gibt allerdings auch
Datenabschneidungungen ohne Warnhinweis oder Ausnahmeauslösung. Dies
geschieht dann, wenn von einer Anwendung einem Datenfeld eine Begrenzung
seiner Größe auferlegt wird und dann versucht wird ein Datenfeld,
das größer als das Limit ist, zu schreiben oder zu lesen, so
werden die Daten auf das Limit abgeschnitten, ohne daß eine SQLWarning
oder SQLException ausgelöst wird.
Bei der Datenabschneidung beim Schreiben ist es möglich, daß
mehr Daten gesendet werden, als der Treiber oder die Datenbank aufnehmen
können. In diesem Fall sollte ein DataTruncation-Objekt als eine SQLException
ausgelöst werden. Hierbei ist zu beachten, daß trotz des Auslösens
einer Ausnahme wegen der Datenabschneidung die Daten an die Datenbank gesendet
worden sind und abhängig vom Treiber vielleicht schon geschrieben
worden sind.
Folgende Information enthält jedes DataTruncation-Objekt :
1.) MetaData
Die Schnittstelle java.sql.DatabaseMetaData stellt Informationen über die Datenbank als Ganzes bereit. Man erzeugt eine Instanz von DatabaseMetaData und ruft dann Methoden auf ihr auf, um Informationen über die Datenbank zu erlangen. Man erzeugt ein DatabaseMetaData-Objekt mit der Methode getMetaData von Connection, was folgender Quelltext zeigt :
DatabaseMetaData dbmd = con.getMetaData();
Über dieses DatabaseMetaData-Objekt lassen sich Informationen über
die Datenbank abfragen. Da sich über dieses Objekt sehr viele Informationen
abfragen lassen, sollte man sich die Schnittstelle java.sql.DatabaseMetaData
in der JDBC-API anschauen und dann die für einen geeigneten Methoden
heraussuchen.
Die Schnittstelle ResultSetMetaData stellt Informationen über die Typen und Eigenschaften der Spalten in einem ResultSet-Objekt zur Verfügung. Man erzeugt ein ResultSetMetaData-Objekt wie folgt (stmt ist ein Statement-Objekt) :
ResultSet rs = stmt.executeQuery(“SELECT a,b,c FROM Table1“);
ResultSetMetaData rsmd = rs.getMetaData();
Diese Schnittstelle ist bei der execute-Methode besonders nützlich, da hier die Anzahl und Art der Ergebnisse zur Übersetzungszeit unbekannt sind.
wichtige Methoden von ResultSetMetaData