Ausarbeitung des Referats JDBC

Werbung
..
..
..
..
..
Markus Zywitza
Matr.-Nr. 832576
Email:
[email protected]
[email protected]
[email protected]
Markus Zywitza
Ausarbeitung des Referats
JDBC
.
.
.
.
.
.
.
Datenbank-Anbindung unter Java
.
.
.
JDBC 1.1
Datenbank-Anbindung unter Java
Grundlegendes zu JDBC.................................................................................................................................. 3
X/Open SQL-CLI........................................................................................................................................ 3
JDBC-Versionen ......................................................................................................................................... 3
Kurzeinführung ............................................................................................................................................... 4
Beispiel ........................................................................................................................................................ 4
Erklärungen zum Beispiel .......................................................................................................................... 4
Ausnahmebehandlung..................................................................................................................................... 5
Die Ausnahme SQLException ..................................................................................................................... 5
Warnungen ................................................................................................................................................. 7
Die Ausnahme DataTruncation................................................................................................................... 7
Treiber und Treiberverwaltung........................................................................................................................ 7
Treibertypen ............................................................................................................................................... 7
Treiberregistrierung ................................................................................................................................. 10
Verbindungsaufbau ....................................................................................................................................... 12
JDBC-URL................................................................................................................................................ 12
Verbindungserstellung.............................................................................................................................. 13
Datenbankanweisungen................................................................................................................................. 13
Einfache Anweisungen.............................................................................................................................. 14
Vorkompilierte Anweisungen ................................................................................................................... 16
Prozeduraufrufe........................................................................................................................................ 17
Transaktionen................................................................................................................................................ 17
Metadaten...................................................................................................................................................... 18
Datentypen..................................................................................................................................................... 18
Anhang.......................................................................................................................................................... 19
Anmerkung:
Die folgenden Schreibweisen werden im Dokument benutzt:
-
Kursiv für Java-Klassen und Pakete (z.Bsp. java.lang.Object)
-
Großbuchstaben für SQL-Schlüsselwörter (z.Bsp. CREATE)
-
Courier New für Beispiele und Java-Methoden und Schlüsselwörter (z.Bsp. toString())
2
Grundlegendes zu JDBC
JDBC steht für Java Database Connectivity. Es handelt sich um ein Java-API zum Datenbankanbindung. Die Vorteile von JDBC sind:
-
JDBC basiert auf SQL-CLI (Call Level Interface) von X/Open. Dadurch ist eine weitestgehende Datenbankunabhängigkeit gewährleistet.
-
Aufgrund der Eigenschaften der Sprache Java ist JDBC plattformunabhängig. Ein Programm
kann ohne Änderung des Quelltextes auf verschiedene Datenbanksysteme auf unterschiedlichen Plattformen zugreifen.
-
JDBC unterstützt eine Mehrschichtenarchitektur. Unterstützt werden je nach Datenbanktreiber
eine zweischichtige oder eine dreischichtige Architektur. Ebenso ist es möglich,
Desktopdatenbanken zu benutzen.
X/Open SQL-CLI
Bei CLI (Call Level Interface) handelt es sich um eine Datenbankschnittstelle, die von X/Open
definiert wurde. CLI ist eine Schnittstelle auf Aufrufebene. D.h. SQL-Aufrufe werden im Klartext
an den entsprechenden Treiber übergeben. Dieser wandelt die Aufrufe in das native Datenbankprotokoll um.
Neben JDBC ist die wohl bekannteste SQL-CLI-Implementation ODBC der Fa. Microsoft. ODBC
ist mittlerweile bereits auch auf UNIX-Systemen verfügbar.
JDBC-Versionen
Die vorliegende Ausarbeitung behandelt JDBC in der Version 1.1.
JDBC 1.0 wurde zuerst mit dem Java SDK 1.1 ausgeliefert. Für das JDK 1.0 ist JDBC gesondert
erhältlich. JDBC wurde zunächst auf Basis von 1.0 ausgebaut. Diese Versionen sind mit den
Nummern 1.1 bis 1.2 bezeichnet.
Mit Erscheinen von Java2 (JDK 1.2) wurde JDBC 2.0 entwickelt. JDBC 2.0 wird in zwei Teile
aufgeteilt:
-
Die Klassen und Interfaces des Pakets java.sql werden als Core API bezeichnet. Es handelt
sich dabei um die Weiterentwicklung der JDBC 1.x API. Neu hinzugekommen sind
beispielsweise navigierbare Ergebnistabellen und Unterstützung für CLOBs und BLOBs
(Binary / Character Large Object).
-
Das Paket javax.sql enthält die Optional API. Dabei handelt es sich vor allem um
Funktionalität zur Ermöglichung verteilter Transaktionen.
Die Unterschiede zwischen den JDBC 1.x-Versionen sind marginal. Obwohl die gängigen JDKs
mittlerweile alle über das JDBC 2.x verfügen, sind viele JDBC-Treiber noch auf die 1.x-API
ausgelegt und unterstützen die Funktionen von JDBC 2 nicht.
3
Kurzeinführung
Beispiel
package example;
import java.sql.*;
public class Example1 {
public static void main(String args[]) throws Exception {
Class.forName("org.enhydra.instantdb.jdbc.idbDriver");
Connection c = DriverManager.getConnection (
"jdbc:idb:DataBaseURL");
Statement s=c.createStatement();
ResultSet r=s.executeQuery("SELECT * FROM Table");
while (r.next()) {
System.out.println(r.getString("Spalte1")+" "+
r.getString("Spalte2"));
}
c.close();
}
}
Erklärungen zum Beispiel
Laden des Datenbanktreibers
Der Befehl Class.forName("com.package.SampleDriver") lädt, instanziiert und
registriert die Instanz des Datenbanktreibers im Treibermanager. Der registrierte Datenbanktreiber
wird in einem statischen Treiberregister gehalten. Auf diese spezielle Methode der Objektinitialisierung wird in dem Abschnitt über Treiber und Treiberverwaltung näher eingegangen.
Verbindungserstellung
Die Datenbankverbindung wird mit der statischen Methode getConnection(String url)
der Klasse DriverManager erstellt. Um die Datenbank zu identifizieren, wird der Methode eine
spezielle URL übergeben. Auf diese wird im entsprechenden Abschnitt noch eingegangen. Ist die
Verbindung einmal erstellt, ist das Programm für die Verwaltung der Verbindung zuständig. Da die
Erstellung einer Verbindung sehr zeitaufwändig, sollte sie möglichst lange im Speicher gehalten
werden.
Erstellen einer Datenbankanfrage
Mit dem Befehl createStatement() wird ein Statement-Objekt instanziiert. Dieses kann
benutzt werden, um Anfragen an die Datenbank zu erstellen.
Ausführen der Anfrage
Durch die Methode executeQuery(String query) wird die Anfrage ausgeführt. Die
Methode liefert eine Instanz der Klasse ResultSet zurück. Dabei handelt es sich um eine temporäre
Tabelle mit den Ergebnissen der Anfrage.
Auswerten der Anfrage
Die Auswertung der SQL-Anfrage erfolgt innerhalb einer while-Schleife. Die Methode next()
von ResultSet verschiebt den Cursor der Ergebnistabelle jeweils eine Reihe nach unten. Die
Rückgabe ist solange true, wie der Cursor nach der Verschiebung noch auf eine gültige Reihe
zeigt. Vor dem ersten Aufruf von next() ist der Cursor noch nicht initialisiert. Ein Auslesen zu
diesem Zeitpunkt liefert eine Ausnahme.
4
Beim Umgang mit ResultSet-Objekten müssen die folgenden Regeln beachtet werden:
-
Zu jedem Statement kann es nur ein gültiges ResultSet geben.
-
Auf jede Zeile kann nur einmal zugegriffen werden. Die Navigation erfolgt stets vorwärts.
-
Die Datenelemente können nur einmal abgerufen werden. Dies muss in der Reihenfolge
geschehen, in der die Spalten in der SQL-Abfrage angegeben werden.
Schließen der Verbindung
Das Schließen einer Verbindung erfolgt mit close(). Dies kann auch von der VM übernommen
werden, es ist allerdings nicht zu empfehlen. Durch den Aufruf von close() werden alle
Resourcen geschlossen. Dies schließt noch offene Statement- und ResultSet-Objekte mit ein,
wodurch weitaus mehr Speicherplatz für die tatsächliche Anwendung zur Verfügung steht.
Da das erneute Öffnen einer Verbindung jedoch erhebliche Rechenzeit beansprucht (bei
Desktopdatenbanken) bzw. zu Verzögerungen führt (bei Datenbanken in verteilten Umgebungen),
sollte eine Verbindung nicht zu früh geschlossen werden.
Daher ist der Zeitpunkt zum Schließen einer Verbindung und der damit einhergehenden Freigabe
der Ressourcen je nach Anwendung zu entscheiden; eine allgemein gültige Regelung kann hier
nicht getroffen werden.
Ausnahmebehandlung
Im oben behandelten Einführungsbeispiel wurde bewußt die schlechteste Methode der Ausnahmebehandlung gewählt – die Weitergabe an den Aufrufer, in diesem Fall die VM.
Eine tatsächliche Anwendung sollte eine dezidierte Behandlung auftretender Ausnahmen
implementieren. Daher sollte nicht einfach die Oberklasse Exception abgefangen werden.
// Schlechtes Beispiel
// Anwendungscode...
try {
// JDBC-Anweisungen
} catch(Exception e) {
e.printStackTrace();
}
// Weiterer Anwendungscode...
Insbesondere im Fall von JDBC ist diese Vorgehensweise nicht zu empfehlen. Dies hat zwei
Gründe:
-
Die Ausnahme SQLException bietet mehr Informationen als Exception.
-
Es können Mehrfachausnahmen auftreten.
Die Ausnahme SQLException
Diese Ausnahme ist die am meisten benutzte Ausnahme im JDBC-Paket. Fast alle Methoden der
einzelnen Klassen können SQLExceptions erzeugen. Die Anzahl der verschiedenen Ausnahmen in
JDBC 1.x ist hingegen sehr gering: es existieren nur drei Klassen, die von Exception abgeleitet
sind. Die wichtigste von diesen ist SQLException. Zum Vergleich: das Paket java.io enthält 16
verschiedene Ausnahmen (Stand: jdk 1.3).
5
Informationen über SQL-Fehler
Aufgrund der geringen Mengen an Ausnahmeklassen und der Vielzahl an Fehlermöglichkeiten
beim Umgang mit Datenbanken enthält SQLException mehr Informationen als die Oberklasse
Exception:
-
Die Fehlernachricht (analog zu Exception)
-
Die SQL-Fehlernummer (nach ANSI-92)
-
Den Fehlercode des Datenbankherstellers
Der folgende Codeausschnitt zeigt, wie man diese Informationen erhält:
// Beispiel 3
// JDBC-Ausnahmebehandlung
try {
// JDBC-Anweisungen
} catch(SQLException e) {
System.err.println("Datenbank -Fehler:");
System.err.println(e.getMessage());
System.err.println("SQL -Fehlerzustand:"+e.getSQLState());
System.err.println("Hersteller -Code:"+e.getErrorCode());
}
Mehrfachausnahmen
Da vollständige SQL-Anweisungen übergeben werden, kann es zu Mehrfachausnahmen kommen.
Als Beispiel sei folgende Situation gegeben:
Eine fehlerhafte Datenbankanfrage wird gestellt. Einerseits liefert ein inneres Select keine Daten;
außerdem existiert die entsprechende Tabelle des äußeren Selects nicht. Das Datenbanksystem
erkennt beide Fehler erst bei der Ausführung der Anweisung und gibt zwei Fehler zurück – den
SQL-State 02000 (No Data) und den SQL-State 51000 (Invalid SQL Descriptor Name).
Da jedoch nur eine Ausnahme geworfen werden kann, muß es eine Möglichkeiten geben, mehrere
Ausnahmen zu behandeln. Daher werden mehrere Instanzen von SQLException erzeugt. Weitere
Ausnahmen werden in Form einer einfach verketten Liste an das zuerst erzeugte Objekt angehängt.
Dieses wird schließlich ausgeworfen.
Das folgende Beispiel zeigt die komplette Fehlerbehandlung mit JDBC:
// Beispiel 4
// JDBC-Ausnahmebehandlung
try {
// JDBC-Anweisungen
} catch(SQLException e) {
while (e!=null) {
System.err.println("Datenbank-Fehler:");
System.err.println(e.getMessage());
System.err.println("SQL -Fehlerzustand:"+e.getSQLState());
System.err.println("Hersteller -Code:"+e.getErrorCode());
e=e.getNextException();
}
}
6
Warnungen
SQL-Datenbanken kennen auch das Konzept der Warnmeldungen. Dies geschieht immer, wenn
eine Ausnahme auftritt, die nicht schwerwiegend genug ist, um den Programmablauf zu
unterbrechen. Hierbei handelt es sich um die SQL-States 01xxx.
Dies wird durch die Klasse SQLWarning realisiert. Es handelt sich um eine Unterklasse von
SQLException, die jedoch nicht geworfen wird. Statt dessen wird eine Warnung an das die
Warnung erzeugende Objekt angehängt. Diese kann mit der Methode
public SQLWarning getWarnings()
abgefragt werden.
Eine SQLWarning kann an Instanzen der folgenden Klassen angehängt werden:
-
java.sql.Connection
-
java.sql.Statement
-
java.sql.ResultSet
Alle diese Klassen verfügen über die Methode getWarnings(), um Warnungen abzufragen.
Das folgende Beispiel zeigt die Behandlung von Warnungen:
// Beispiel 5
// JDBC-Warnungen
public void printWarnings(SQLWarning w) {
while (w!=null) {
System.err.println("Datenbank -Warnung:");
System.err.println(w.getMessage());
System.err.println("SQL -Fehlerzustand:"+w.getSQLState());
System.err.println("Hersteller-Code:"+w.getErrorCode());
w=w.getNextWarning();
}
}
Diese Methode kann nun benutzt werden, um Warnungen auszugeben.
Die Ausnahme DataTruncation
Die letzte Ausnahmeklasse behandelt den Sonderfall, dass Daten abgeschnitten werden. Geschieht
dies beim Lesen eines Datensatzes, wird DataTruncation als Warnung behandelt. Kommt dies
beim Schreiben von Datensätzen vor, wird hingegen eine Ausnahme erzeugt und geworfen.
DataTruncation verfügt über weitere Methoden, um die genaue Position des Fehlers zu bestimmen.
Treiber und Treiberverwaltung
Treibertypen
JDBC-Treiber werden in vier Klassen eingeteilt:
-
JDBC-ODBC Brücke
-
Hybrid-Treiber (Java-C/C++)
-
Middleware-Treiber
-
Java-Treiber
7
Typ 1: JDBC-ODBC-Brücke
Hierbei handelt es sich um eine Implementation von Sun. JDBC-ODBC Treiber kommen vor allem
im Windows-Umfeld zum Einsatz. Perfomantere Treiber dieses Typs sind kommerziell erhältlich.
Außerhalb des Windowsumfelds sind JDBC-ODBC-Treiber praktisch bedeutungslos. Es handelt
sich um die langsamste Methode, Datenbanken unter Java anzusprechen.
Java-Programm
Datenbank
ODBC-Treiber
JDBC
ODBC
JDBC-Treiber
Typ 2: Hybrid-Treiber
Treiber diesen Typs werden unter Benutzung von JNI (Java Native Interface) implementiert. Diese
Treiber setzen die JDBC-Aufrufe in die native API des Datenbankherstellers um. Diese Treiber
sind vor allem im Unix-Umfeld verbreitet.
Java-Programm
Datenbank
JDBC-Treiber
JDBC
8
Nativer Treiber
Typ 3: Middleware-Treiber
Diese Treiber sprechen eine Middleware an, die den Zugriff auf die Datenbanken vermittelt.
Treiber diesen Typs sind meist komplett in Java implementiert. Dies bietet Vorteile, da die
Treiberdateien bspw. von Applets geladen werden können.
Ein weiterer Vorteil von Treibern diesen Typs ist, dass eine kommerzielle Middleware zumeist
mehrere Datenbanken in heterogenen Umgebungen ansprechen kann.
Java-Programm
Datenbank
Datenbank
Datenbank
JDBC
Middleware
JDBC-Treiber
Typ 4: Java Treiber
Treiber diesen Typs sind komplett in Java implementiert. Diese Treiber stehen zur Verfügung,
wenn die entsprechende Datenbank selbst in Java implementiert ist oder Java-Aufrufe selbstständig
verarbeiten kann.
Diese Treiber haben den Vorteil, dass sie zumeist die Möglichkeiten von JDBC komplett
ausnutzen. Des weiteren bieten in Java implementierte Datenbanken z.T. auch die Möglichkeit,
Java Objekte abzuspeichern.
Java-Programm
Datenbank
JDBC
JDBC-Treiber
9
Treiberregistrierung
Die ausgewählten Datenbanktreiber müssen vor der ersten Benutzung initialisiert werden. Hierfür
gibt es drei Möglichkeiten.
-
Die Klassen des Treibers können direkt initialisiert werden.
-
Es werden Interfaces benutzt und nur die Treiberklasse direkt initialisiert.
-
Es wird der Registrierungsmechanismus der Klasse DriverManager verwendet.
Direkte Initialisierung
Die Treiberklassen können direkt initialisiert werden, wie im folgenden Beispiel geziegt wird:
package example;
import java.sql.*;
import org.enhydra.instantdb.jdbc.*;
public class Example6 {
public static void main(String args[]) throws Exception {
idbDriver dr=new idbDriver();
// Erstellen der Datenbankverbindung
idbConnection c = (idbConnection) dr.connect(
"jdbc:idb:DataBaseURL ",null);
idbStatement s= (idbStatement) c.createStatement();
idbResultSet r= (idbResultSet)
s.executeQuery("SELECT * FROM Table");
while (r.next()) {
System.out.println(r.getString("Spalte1")+" "+
r.getString("Spalte2"));
}
c.close();
}
}
Durch eine solche Vorgehensweise wird die Datenbankunabhängigkeit von JDBC untergraben. Da
diese weitestgehend durch die Benutzung der Interfaces ermöglicht wird, müssen zusätzlich bei
jedem Befehl Typkonvertierungen vorgenommen werden.
Dennoch hat die Vorgehensweise in bestimmten Fälle seine Berechtigung: Beispielsweise können
JDBC-Treiber herstellerabhängige Erweiterungen enthalten. Als Beispiele seien hier Oracle- und
instantDB-Treiber genannt.
10
Nutzung der Interfaces
Ein besserer Ansatz ist jedoch zumeist, direkt mit den entsprechenden Interfaces zu arbeiten. Das
vorherige Beispiel sieht dann folgendermaßen aus:
package example;
import java.sql.*;
public class Example7 {
public static void main(String args[]) throws Exception {
Driver dr=new
org.enhydra.instantdb.jdbc.idbDriver();
Connection c = dr.connect(
"jdbc:idb:DataBaseURL",null);
Statement s= c.createStatement();
ResultSet r= s.executeQuery("SELECT * FROM Table");
while (r.next()) {
System.out.println(r.getString("Spalte1")+" "+
r.getString("Spalte2"));
}
c.close();
}
}
Dadurch entfällt zunächst die Notwendigkeit zur Typumwandlung. Wichtiger ist jedoch, dass der
Quellcode nun weitestgehend unabhängig vom verwendeten Treiber ist. Allerdings muss jedoch
noch immer die Treiberklasse im Quellcode angegeben werden. Die Benutzung der automatischen
Registrierung der Klasse DriverManager setzt an diesem Problem an.
Benutzung von DriverManager
Statt die Treiberklasse mit new zu initialisieren, verfolgt JDBC einen anderen Ansatz: mit dem
Befehl
Class.forName(“org.enhydra.instantdb.jdbc.idbDriver“);
wird die Klasse dynamisch geladen. Der obige Befehl initialisiert jedoch eigentlich nicht die
Klasse, sondern weist lediglich den Klassenlader an, die Klasse in den Speicher zu laden. Damit
der Treiber nun initialisiert und beim Treibermanager registriert wird, muss der Treiber aussehen
wie folgt:
package example;
import java.sql.*;
public class TreiberBeispiel implements Driver {
static {
try {
new TreiberBeispiel();
} catch(SQLException e) {
//...
}
}
public TreiberBeispiel(){
DriverManager.registerDriver(this);
//...
}
//...
}
11
Bei Benutzung der Registrierung sieht der vollständige Code nun aus wie in Beispiel 1:
package example;
import java.sql.*;
public class Example1 {
public static void main(String args[]) throws Exception {
Class.forName("org.enhydra.instantdb.jdbc.idbDriver");
Connection c = DriverManager.getConnection (
"jdbc:idb:DataBaseURL");
Statement s=c.createStatement();
ResultSet r=s.executeQuery("SELECT * FROM Table");
while (r.next()) {
System.out.println(r.getString("Spalte1")+" "+
r.getString("Spalte2"));
}
c.close();
}
}
Es fällt auf, dass die datenbankspezifischen Teile nunmehr lediglich zwei Strings sind, die
übergeben werden. Diese Zeichenketten können problemlos als Properties eingelesen werden.
Dadurch kann ohne Neukompilierung die Datenbank gewechselt werden.
Verbindungsaufbau
JDBC-URL
Um eine Verbindung zu einer Datenbank aufzubauen, gibt es zwei Möglichkeiten: Über die
jeweilige Treiberklasse oder den DriverManager. In jedem Fall wird aber die JDBC-URL der
Datenbank benötigt. Diese besteht aus drei Teilen:
Protokoll, Subprotokolle und Adresse der Datenquelle
Die Zusammensetzung ist:
protokoll:subprotokoll:adresse
Die folgenden Beispiele zeigen die verschiedenen möglichen Zusammensetzung der JDBC-URL:
jdbc:odbc:BeispielDB;autocommit=true
jdbc:idb:C:/dbhome/beispieldb.prp
jdbc:oracle:thin:@localhost:9000:BeispielDB
Hierbei sind die verschiedenen Subprotokolle fett dargestellt und die Adressen unterstrichen.
Festgelegt ist lediglich das Hauptprotokoll, dass immer jdbc lauten muss. Die Subprotokolle und
Adressen der Datenquellen sind abhängig vom gewählten Treiber.
Die Angabe der Subprotokolle dient dem Datenbanktreiber zur Identifikation einer JDBC-URL.
Dieser erkennt am gewählten Subprotokoll, ob er die gewünschte Datenbankverbindung herstellen
kann. Die Adresse der Datenquelle gibt an, welche Datenbank benutzt werden soll.
12
Verbindungserstellung
Verbindungen können auf zwei Wegen erstellt werden:
-
Direkt über die Treiberklasse
-
Über den Treibermanager
Die letztere Methode ist hierbei vorzuziehen, bei manchen Datenbanksystemen ist jedoch die erste
Methode notwendig.
Wird eine Verbindung direkt über die Treiberklasse erstellt, wird dessen Methode connect()
aufgerufen. Der Methode müssen die JDBC-URL und eine Objekt der Klasse java.util.Properties
übergeben werden. Die Eigenschaften, die in diesem Objekt festgelegt sein müssen, sind
treiberabhängig. Zumeist müssen zumindest die Eigenschaften „user“ und „password“ gesetzt
werden. Dies verdeutlicht das folgende Beispiel:
Driver odbcbridge=new sun.jdbc.odbc.JdbcOdbcDriver();
String url="jdbc:odbc:Beispie l";
Properties p=new Properties();
p.put("user","guest");
p.put("password","default");
Connection odbcconn=odbcbridge.connect(url,p);
Diese Methode wird vor allem dann benutzt, wenn Eigenschaften an die Datenbank übergeben
werden müssen, die nicht innerhalb der JDBC-URL gesetzt werden können.
Vorzuziehen ist jedoch die Erstellung von Verbindungen über die Klasse DriverManager. Hierzu
wird dessen statische Methode getConnection() aufgerufen. Der Methode muss die JDBCURL übergeben werden. Optional können Username und Passwort übergeben werden.
String url="jdbc:odbc:Beispiel";
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection odbcconn=DriverManager.getConnection
(url,"guest","default");
Die Benutzung von DriverManager hat einen weiteren Vorteil: Es können mehrere Treiber zur
selben Zeit registriert sein. In dem Fall wird connect() aller Treibern in der Reihenfolge ihrer
Registrierung aufgerufen. Der Treiber erkennt am übergebenen Subprotokoll, ob er diese
Verbindung aufbauen kann und gibt null zurück, wenn dem nicht der Fall ist. So können von einer
Anwendung Verbindungen zu mehreren Datenbanken aufgebaut werden.
Datenbankanweisungen
Wurde eine Datenbankverbindung erstellt, kann sie benutzt werden, um Anweisungen (Statement)
zu erstellen. Es gibt drei mögliche Arten von Anweisungen:
-
einfache Anweisungen (Statement)
-
vorkompilierte Anweisungen (PreparedStatement)
-
Prozeduraufrufe (CallableStatement)
Die Benutzung der ersten beiden Arten werden in diesem Dokument näher erläutert.
CallableStatement wird nur kurz angerissen.
13
Einfache Anweisungen
Anhand von einfachen Anweisungen lassen sich gut die Grundzüge der JDBC-Anwendungsentwicklung zeigen. Einfache Anweisungen werden vor allem dann verwendet, wenn bis zur
tatsächlichen Ausführung keine Aussage über die Datenbankanfrage getroffen werden kann.
Ein Statement-Objekt wird erstellt, in dem die Methode createStatement() von Connection
aufgerufen wird.
// Erstellung der Datenbankverbindung
Statement st=conn.createStatement();
Das erstellte Objekt kann nun benutzt werden, um eine Anfrage auszuführen. Das Interface
Statement vereinbart hierzu drei Methoden:
-
boolean execute(String sql);
-
ResultSet executeQuery(String sql);
-
int executeUpdate(String sql);
Ist vor Ausführung der Anweisung bekannt, dass es sich um eine Anfrage handelt, die eine
Ergebnistabelle liefert (z.Bsp. eine SELECT-Anweisung), kann die Methode executeQuery()
benutzt werden. Gleiches gilt für Anweisungen, die wie UPDATE oder INSERT lediglich die
Anzahl der veränderten Zeilen der Tabelle zurückliefern. In diesem Fall wird
executeUpdate() benutzt.
Wie oben bereits erwähnt, wird Statement jedoch zumeist benutzt, um Anfragen auszuführen, über
deren Natur keine Aussagen zur Kompilationszeit getroffen werden können. Hierfür ist die
Methode execute() geeignet. Sie liefert einen booleschen Wert zurück. Ist dieser wahr (true),
handelt es sich beim Ergebnis der Anfrage um ein ResultSet, ansonsten wird ein Integerwert
bereitgestellt, der der Anzahl der veränderten Tabellenzeilen entspricht.
Ein weiterer Vorteil von execute() ist, dass die Anweisung in der Lage ist, mehrere ResultSets
zu liefern. Dabei handelt es sich um ein eher ungewöhnliches Verhalten, dass jedoch bei der
Verarbeitung dynamischer SQL-Anweisungen in Betracht gezogen werden muß.
14
Das folgende Beispiel benutzt alle Möglichkeiten von execute():
package examples;
import java.sql.*;
/**
* Beispiel 10.
* Eine einfache SQL -Shell.
*/
public class Shell {
public static void main(String[] args) {
if (args.length==0) {
System.out.println("Aufruf:");
System.out.println("java examples.Shell SQL -Anweisung");
System.exit(-1);
}
String sql=new String();
for (int i=0;i<args.length;i++) {
sql += args[i]+" ";
}
try {
Class.forName("org.enhydra.i nstantdb.jdbc.idbDriver");
Connection conn=DriverManager.getConnection(
"jdbc:idb:C:/projekt/jdbcref/rsc/example.prp");
Statement st=conn.createStatement();
if (st.execute(sql)) {
do {
ResultSet rs=st.getResultSet();
int numCols=rs.getMetaData().getColumnCount();
while (rs.next()) {
for (int i=1;i<=numCols;i++) {
if (i!=numCols)
System.out.print(rs.getString(i)+", ");
else
System.out.println( rs.getString(i));
}
}
rs.close();
} while (st.getMoreResults());
}
int updateCount;
while ((updateCount=st.getUpdateCount())!= -1) {
System.out.println("Veraenderte Zeilen: "+updateCount);
st.getMoreResults();
}
st.close();
conn.close();
} catch(ClassNotFoundException cnfe) {
System.err.println("Treiber nicht gefunden!");
cnfe.printStackTrace();
} catch(SQLException sqle) {
System.out.println("Datenbankfehler: ");
System.out.println(sqle.getSQLState()+"("+sqle.getMessage()+")");
}
}
}
Zunächst wird eine Instanz von Statement erstellt. Die Ausführung erfolgt in der nächsten Zeile mit
execute(), wobei das Ergebnis mittels if abgefragt wird. Ist dieser Wert wahr, erfolgt die
Abarbeitung der übermittelten Ergebnistabellen. Dazu wird zunächst eine dieser Tabellen
angefordert und verarbeitet. Die Anweisung getMoreResults() schaltet zum nächsten
Ergebnis weiter und liefert einen boolschen Wert zurück, der genau dann wahr ist, wenn weitere
Ergebnistabellen vorhanden sind. Das Ergebnis false bedeutet, dass entweder keine Ergebnisse
mehr oder nur noch weitere Update-Zähler vorliegen.
Da es Anweisungen gibt, die sowohl Ergebnistabellen als auch Update-Zähler zurück liefern,
wurde kein else-Teil eingefügt. Der Update-Zähler wird in jedem Fall ausgelesen. Erst wenn
dieser gleich –1 ist, liegen keinerlei weitere Ergebnisse mehr vor.
15
Ergebnisauswertung
Das obige Beispiel zeigt auch die grundsätzliche Vorgehensweise zur Auswertung von
Ergebnistabellen.
Um die Tabelle zu initialisieren, wird die Methode next() des ResultSets aufgerufen. Diese
Methode schaltet bei späteren Aufrufen je eine Reihe weiter. Die Abfrage der einzelnen
Ergebnisse, erfolgt dann mit Methoden vom Typ getXXX(int spaltenNummer) bzw.
getXXX(String spaltenName) . XXX steht hierbei für eine zum SQL-Typ
korrespondierende Javaklasse bzw. Datentyp.
Da die meisten dieser Methoden skalare Datentypen zurückgeben, sollte die Methode wasNull()
von Result Set aufgerufen werden, wenn ein Erkennen des SQL-Werts null notwendig ist. Ob eine
Spalte den Wert null zuläßt, kann über das ResultSet-Metadatenobjekt abgefragt werden. Auf
dieses wird weiter unten eingegangen.
Bei der Abfrage der Spalten ist unbedingt zu beachten, dass diese in der Reihe des Aufrufs
abgefragt werden. Es ist auch nicht möglich, im ResultSet zu navigieren, Kopien des ResultSet zu
erstellen.
Bei mehreren ResultSets als Ergebnis einer Anfrage ist auch darauf zu achten, dass beim Öffnen
eines weiteren ResultSets des gleichen Statements das Schließen des ersten ResultSet automatisch
geschieht.
Vorkompilierte Anweisungen
Anweisungen können auch vorkompiliert werden, um eine bessere Ausführungsgeschwindigkeit zu
erreichen. Allerdings entsteht dadurch ein erhöhter Aufwand bei der Initialisierung. Daneben ist es
möglich, Datenfelder nach der Kompilierung dynamisch zu besetzen und so paramtrisierbare
Anweisungen zu schaffen.
Durch diese Eigenschaften ist der Einsatz von PreparedStatements vor allem bei Webapplikationen
sinnvoll. Hier werden vor allem statische Anweisungen mit variablen Daten benutzt. Beispiele sind
sogenannte Einkaufskörbe und komplexe Abfragen.
Die Benutzung von vorkompilierten Anweisungen entspricht der von einfachen Anweisungen.
Unterschiede werden im folgenden dargestellt.
Vorbereiten der Anweisung
Ein PreparedStatement wird folgendermaßen erstellt:
PreparedStatement ps=conn.prepareStatement(sql);
wobei der Parameter sql eine Anweisung (als String) ist. Dabei können Parameter verwendet
werden. Diese werden in der übergebenen SQL-Anweisung als ? (Fragezeichen) übergeben.
Beispiele hierfür sind:
SELECT * FROM Tabelle1 WHERE ID=?
INSERT INTO Tabelle1 (ID,Name,Vorname) VALUES ?,?,?
nicht möglich hingegen ist es, Tabellen- oder Spaltennamen zu parametrisieren. Folgende Beispiele
sind fehlerhaft:
SELECT * FROM ?
SELECT * FROM Tabelle1 WHERE ?<0
FALSCH
FALSCH
Setzen der Parameter
Bevor die vorbereitete Anweisung ausgeführt werden kann, müssen die vereinbarten Parameter
noch gesetzt werden. Hierfür stehen die Methoden
void setXXX(int index, XXX value)
zur Verfügung, wobei XXX für eine zum SQL-Typ kompatible Javaklasse steht. Zu beachten ist,
dass im Gegensatz zur üblichen Zählweise in Java der Index mit eins beginnt.
16
Eine Übersicht die möglichen Javaklassen sind in der JDBC-Dokumentation zu finden. Jeder SQLDatentyp kann jedoch durch eine Stringrepräsentation ersetzt werden.
Ausführen der Anweisung
Die Ausführung erfolgt wie bei einfachen Anweisungen. Jedoch wird den Methoden kein
Parameter übergeben.
Prozeduraufrufe
Wenn die Datenbank Prozeduren und Funktionen unterstützt, kann mit der Klasse
CallableStatement eine solche aufgerufen werden. Bei CallableStatements können Ein- und
Ausgabeparameter verwendet werden.
Transaktionen
JDBC unterstützt zwei Arten der Transaktionssteuerung: Auto-Commit und eine programmatische
Steuerung über Methoden-Aufrufe.
Alle Transaktionen sind an das jeweilige Connection-Objekt gebunden. Für die meisten
Anwendungen ist eine automatische Transaktionssteuerung ausreichend. Hierbei wird ein Commit
nach jeder erfolgreichen Ausführung einer SQL-Anweisung ein Commit signalisiert.
Die automatische Transaktionssteuerung wird mit folgenden Methoden aktiviert:
-
void setAutoCommit(boolean ac)
-
boolean getAutoCommit()
Sollen komplexe Transaktionen vorgenommen werden, die sich über mehrere SQL-Aufrufe
erstrecken (z.Bsp. Auslesen, Verändern und Zurückschreiben eines Datensatzes), können
Transaktionen mit folgenden Befehlen gesteuert werden:
-
void commit()
-
void rollback()
Der Beginn einer Transaktion ist stets die erste Ausführung einer SQL-Anweisung im Programm
bzw. nach einem commit(). Alle diese Methoden sind im Interface Connection vereinbart.
Wenn Transaktionen innerhalb des Java-Programms weierexistieren (z.Bsp. aufgrund einer
Interaktion mit dem Benutzer), kann dies zu Konflikten führen, weil ein Benutzer versucht auf
einen Datensatz zuzugreifen, der gerade durch einen anderen Benutzer bearbeitet wird. Ob eine
Sperrung des Datensatzes notwendig ist oder nicht, ist stark von der jeweiligen Applikation
abhängig. Daher gibt es in JDBC die Möglichkeit, verschiedene Isolationsstufen für Transaktionen
zu setzen. Diese Stufen sind im Interface Connection als Zahlkonstanten vereinbart und können mit
setTransactionIsolation(int taLevel) gesetzt werden. Folgende Stufen stellt JDBC zur Verfügung:
-
TRANSACTION_NONE: Transaktionen werden nicht unterstützt.
-
TRANSACTION_READ_UNCOMMITED: Minimale Transaktionsunterstützung, bei der es
erlaubt ist, nicht mit Commit gesicherte Daten zu lesen. Dies kann bei einem Rollback zu
Dateninkonsistenzen führen.
-
TRANSACTION_READ_COMMITED: Das Lesen nicht gesicherter Daten wird von der
Datenbank unterbunden.
-
TRANSACTION_REPEATABLE_READ: wie oben, zusätzlich wird jedoch wiederholtes
Lesen unterbunden. Bsp: Zwei Transaktionen TA1 und TA2 bearbeiten den gleichen Datensatz
im Zustand 0 (vor beiden TA). TA2 verändert den Datensatz und signalisiert Commit. Der
Datensatz hat nun Zustand 1. TA1 liest den Datensatz erneut und erhält ihn im Zustand 0 . Die
Änderung des Datensatzes wird für TA1 erst nach einem Commit sichtbar.
17
-
TRANSACTION_SERIALIZABLE: wie oben, zusätzlich wird das Verbot wiederholten
Lesens auch auf neu hinzugefügte Tabellenzeilen ausgedehnt. Die Datenbank behandelt
gleichzeitig stattfindende Transaktionen, als ob sie nacheinander stattgefunden hätten.
Beim Verändern der Isolationsstufe ist zu beachten, dass die Unterstützung für die einzelnen Stufen
abhängig von Datenbank als auch von dem verwendeten Treiber sind.
Metadaten
Die JDBC-API enthält zwei Klassen für Metadaten von Datenbankobjekten. Dabei handelt es sich
um Metadaten über die Datenbank selbst (DatabaseMetaData) und über eine zurückgegebene
Ergebnistabelle (ResultSetMetaData). Hier soll vor allem auf Letztere eingegangen werden.
Die Klasse DatabaseMetadata erhält man durch Aufruf der entsprechenden get-Methode des
Interface Connection. Sie enthält sämtliche Daten zur unterliegenden Datenbank und deren Treiber.
Kurz erwähnt sei, dass u.a. Methoden vorhanden sind, um die Unterstützung der oben erwähnten
Transaktions-Isolationsstufen abzufragen.
Die Klasse ResultSetMetaData hingegen ist von weit wichtigerer Bedeutung: Sie enthält vor allem
Methoden, die benötigt werden, um Ergebnistabellen in korrekter Weise auslesen zu können. Man
erhält das Objekt durch Aufruf der in ResultSet vereinbarten Methode getMetaData().
ResultSetMetaData verfügt über die folgenden gebräuchlichen Methoden:
-
int getColumnCount() liefert die Anzahl der Spalten des ResultSet zurück.
-
String getColumnName(int spalte) liefert den internen Namen der Spalte.
-
String getColumnLabel(int spalte) liefert den vorgeschlagenen Ausgabenamen
der Spalte.
-
String
zurück.
-
int getColumnType(int spalte) wird benutzt, um den Typ der Spalte zu
bestimmen. Sämtliche Datentypen sind in der Klasse java.sql.Types aufgeführt.
-
boolean isCurrency() und boolean isNullable() werden benötigt, um Spalten
richtig interpretieren zu können.
getColumnTypeName(int
spalte) liefert den Namen des SQL-Typs
Datentypen
Um Daten richtig weiterverarbeiten zu können, ist es meist notwendig, ein Typmapping
vorzunehmen, da Datenbanken andere Datentypen verwenden als Java (und die meisten anderen
Programmiersprachen).
Für die Ausgabe ist eine Stringrepräsentation der meist ausreichend. Diese Umwandlung wird von
allen SQL-Datentypen unterstützt.
Die ganzahligen Typen TINYINT, SMALLINT, INTEGER und BIGINT werden durch die JavaDatentypen byte, short, int und long am besten umgesetzt.
Für Fließkommazahlen besteht folgende Umsetzung: REAL wird zu float, FLOAT und DOUBLE
zu double.
Die Fixpunkttypen NUMERIC, DECIMAL und CURRENCY werden als java.math.BigDecimal
dargestellt.
BIT entspricht boolean, die verschiedenen BINARY-Typen byte[].
Lediglich für drei SQL-Datentypen konnte keine korrespondierende Javaklasse gefunden werden.
Es handelt sich um die Typen DATE, TIME und TIMESTAMP. Die Klasse java.util.Date
entspricht keinem der drei Typen; daher wurden von ihr drei weitere Klassen abgeleitet. Es handelt
18
sich um java.sql.Date, java.sql.Time und java.sql.Timestamp. Alle drei Klassen implementieren
durch die Oberklasse java.util.Date die Interfaces Serializable, Cloneable und Comparable.
Den verschiedenen SQL-Datentypen sind in der Klasse java.sql.Types Integerkonstanten
zugeordnet. So kann ein Mapping mit Hilfe des switch-Befehls dargestellt werden.
Anhang
Die für dieses Dokument benutzten Quellen sind:
-
Wolfgang Dehnhardt: Anwendungsprogrammierung mit JDBC, Hanser Verlag 1998
-
David Flanagan u.a.: Java Enterprise in a Nutshell, O’Reilly 1999
-
Java-API Dokumentation von SUN (www.java.sun.com)
19
Herunterladen