Kurzübersicht JDBC

Werbung
Kurzübersicht JDBC
Marc Monecke
[email protected]
15. Januar 2003
Zusammenfassung
Über JDBC-Schnittstellen können Anwendungsprogramme auf Datenbanken
zugreifen, ohne daß dabei hersteller- und datenbankspezifische Besonderheiten
berücksichtigt werden müssen. In diesem Lehrmodul werden die nötigen Grundlagen von JDBC 1.0 erläutert, um Anfragen auf einer Datenbank ausführen und
die Anfrageergebnisse verarbeiten zu können.
Inhaltsverzeichnis
1 Einleitung und Motivation
2
2 Datenbankzugriff mit JDBC
2
3 JDBC verwenden
3.1 JDBC-Treiber laden . . . . . . . . . . . .
3.2 Verbindung zur Datenbank aufbauen . . .
3.3 SQL-Anweisung erzeugen und ausführen .
3.4 Ergebnis verarbeiten . . . . . . . . . . . .
3.4.1 Struktur des Ergebnisses ermitteln
3.4.2 Spezielle SQL-Anweisungen . . . .
3.5 Meta-Daten . . . . . . . . . . . . . . . . .
3.6 Fehler und Warnungen . . . . . . . . . . .
3.6.1 Warnungen . . . . . . . . . . . . .
3.7 Transaktionen . . . . . . . . . . . . . . . .
1
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
5
5
6
6
7
7
8
8
9
9
1
Einleitung und Motivation
Relationale DBMS verschiedener Hersteller haben viele Gemeinsamkeiten: Den
gleichen Einsatzzweck, gleiche Datenbankmodelle und (weitgehend) kompatible Anfragesprachen. Anwendungsprogramme nutzen allerdings Programmierschnittstellen, um auf die Datenbank zuzugreifen. Diese Schnittstellen unterscheiden sich bei verschiedenen DBMS, so daß bei der Entwicklung DBMS-spezifische
Besonderheiten berücksichtigt und beim Wechsel des DBMS die Anwendungen
angepaßt werden müssen.
Die von Microsoft definierten ODBC-Schnittstellen (Open Database Connectivity) machen die Anwendung unabhängig vom verwendeten DBMS. Allerdings
funktioniert dieser Ansatz nur auf einer Plattform.
JDBC hingegen ist plattformunabhängig und definiert Schnittstellen, über die
in Java geschriebene Programme auf eine große Zahl von SQL-Datenbanken zugreifen können. JDBC besteht aus einer kleinen Anzahl Java-Klassen, die grundlegende DBMS-Funktionen kapseln. Sie ermöglichen es, Anfragen und Änderungsoperationen auszuführen, Anfrageergebnisse zu verarbeiten und Informationen über die Konfiguration der verwendeten Datenbank zu ermitteln. Als Anfragesprache wird eine Teilmenge von SQL (normalerweise SQL-92) verwendet.
Die beschriebenen Klassen sind im Paket java.sql zusammengefaßt.
2
Datenbankzugriff mit JDBC
Abbildung 1 zeigt, wie eine Java-Anwendung per JDBC auf Datenbanken zugreift. Der jeweilige Treiber (driver ) implementiert die Schnittstelle zu einem
liefert
Statement
ResultSet
führt aus
Connection
Java−Anwendung
stellt bereit
DriverManager
verwaltet
Postgres Driver
MySQL Driver
...
Abbildung 1: Datenbankzugriff per JDBC
bestimmten DBMS. JDBC-Treiber werden von den Datenbank-Herstellern selbst
oder von Dritten angeboten.
2
Der DriverManager verwaltet die geladenen Treiber und leitet Anfragen
an den jeweils richtigen weiter. Er stellt Connection-Objekte bereit, über die
Anwendungen auf die Datenbank zugreifen. Statement-Objekte kapseln Anfragen oder Änderungsoperationen, die in SQL formuliert werden. Das Ergebnis
einer Anfrage wird in einem ResultSet gekapselt. Über die Operationen des
ResultSet kann die Anwendung auf seine Bestandteile zugreifen.
3
JDBC verwenden
Um von einem Java-Programm aus auf eine JDBC-Datenbank zuzugreifen, sind
folgende Schritte nötig:
1. JDBC-Treiber laden: Welcher Treiber zu verwenden ist, hängt vom verwendeten DBMS ab. Soll das DBMS gewechselt werden können, ohne das Programm
zu ändern, kann der Name des Treibers per Parameter übergeben oder in einer
Ressourcen-Datei gespeichert werden.
2. Verbindung zur Datenbank aufbauen: Hier wird das Connection-Objekt erzeugt, über das die Anwendung mit der Datenbank kommuniziert.
3. SQL-Statement erzeugen: Ein SQL-Statement enthält den auszuführenden
SQL-Code. Neben einfachen Anweisungen können auch vorübersetzte Anweisungen verwendet werden. Über einen dritten Typ kann die Anwendung direkt
auf gespeicherte Prozeduren in der Datenbank zugreifen.
4. Statement ausführen und Ergebnis verarbeiten: Das Ergebnis wird als ResultSet zurückgeliefert. Zur Ermittlung der Struktur des Ergebnisses und für den
Zugriff auf seine Bestandteile stehen Operationen zur Verfügung.
Die einzelnen Schritte werden im folgenden erläutert. Abbildung 2 enthält ein
Beispielprogramm, das eine SQL-Anfrage als Kommandozeilen-Parameter akzeptiert und das Ergebnis ausgibt. Der Quelltext zur Aufbereitung des Ergebnisses
ist in Abbildung 3 dargestellt.
3
import
java.sql.*;
public class JDBCDemo {
final static int FIXEDWIDTH = 15;
final static int COMMANDROWS = 8;
final static int RESULTROWS = 25;
final
final
final
final
final
final
static
static
static
static
static
static
String
int
String
String
String
String
private static
Connection
Statement
ResultSet
FILL = "
";
DISPLAY_SIZE = 15;
DRIVER_CLASS = "org.postgresql.Driver";
URL_PREFIX = "jdbc:postgresql://";
REMOTEHOST = "pi53.informatik.uni-siegen.de/dbs_1";
USERNAME = "cgi";
void execute(String
con;
st;
result;
sql_string) {
try {
con = establishConnection();
st = con.createStatement();
result = st.executeQuery(sql_string);
showResult(result);
st.close ();
con.close ();
} catch ( SQLException e ) {
System.err.println("Error: " + e.getMessage());
}
}
private static Connection establishConnection() {
Connection con = null;
try {
Class.forName(DRIVER_CLASS);
} catch (ClassNotFoundException e) {
System.err.println("JDBC driver " + DRIVER_CLASS +
" not found!");
}
try {
con = DriverManager.getConnection(URL_PREFIX + REMOTEHOST,
USERNAME, "");
} catch (SQLException e) {
System.err.println("Error " + e.getErrorCode() + " " +
e.getMessage());
}
return con;
}
private static void showResult ( ResultSet
{ /* ... siehe Abbildung 3 ... */ }
result ) throws SQLException
public static void main(String args[]) {
execute(args[0]);
}
}
Abbildung 2: Beispielprogramm: Verbindung zur Datenbank aufbauen und SQLAnweisung ausführen
4
private static void showResult ( ResultSet
{
ResultSetMetaData rsm;
int
num_cols;
int
i;
int
row_count;
result ) throws SQLException
rsm = result.getMetaData();
num_cols = rsm.getColumnCount();
row_count = 0;
for(i = 1; i <= num_cols; i++)
printColumn(rsm.getColumnName(i));
System.out.println ();
for(i = 1; i <= num_cols; i++)
printColumn("==========");
System.out.println ();
while ( result.next() ) {
for(i = 1; i <= num_cols; i++)
printColumn(result.getString(i));
System.out.println ();
row_count++;
}
System.out.println("----------------------------"); +
System.out.println(row_count + " rows retrieved");
}
private static void printColumn(String s) {
if(s.length() <= DISPLAY_SIZE )
s += FILL.substring(0, DISPLAY_SIZE-s.length());
else
s = s.substring(0, DISPLAY_SIZE-1);
System.out.print(s + " ");
}
Abbildung 3: Beispielprogramm: Anfrageergebnis verarbeiten
3.1
JDBC-Treiber laden
Der zu ladende Datenbank-Treiber wird über seinen Paket- und Klassennamen
identifiziert. In Abbildung 2 ist dies der Treiber für PostgreSQL:
org.postgresql.Driver
Damit der Treiber geladen werden kann, muß natürlich der Klassenpfad passend gesetzt sein. Es können auch mehrere Treiber geladen werden. Beim Aufbau
der Verbindung zur Datenbank wird dann geprüft, welcher Treiber benutzt werden soll.
3.2
Verbindung zur Datenbank aufbauen
Die Datenbank wird durch eine JDBC-URL identifiziert, die grundsätzlich die
Form
jdbc: driver : databasename
hat. Die URL kann aber datenbankspezifisch variieren. Die Operation getConnection der Klasse DriverManager akzeptiert neben der URL auch den Benutzernamen und das Paßwort als Parameter. Es gibt Varianten dieser Operation
mit anderen Parametern.
5
Connection con;
con = DriverManager.getConnection(
"jdbc:postgresql://pi53.informatik.uni-siegen.de/dbs_1",
"Meier", "geheim");
...
con.close();
Der DriverManager prüft nun nacheinander, welcher der geladenen Treiber
die URL versteht und ein Connection-Objekt zurückliefert. Da diese Prüfung
Zeit benötigt, sollten jeweils nur die benötigten Treiber geladen werden.
Offene Verbindungen belegen Speicher und andere Datenbank-Ressourcen.
Sie sollten daher mit close() geschlossen werden, sobald sie nicht mehr benötigt
werden.
3.3
SQL-Anweisung erzeugen und ausführen
SQL-Anweisungen werden in Objekten gekapselt. Diese Objekte müssen Operationen der Schnittstelle Statement oder einer davon abgeleiteten Schnittstelle
implementieren. Anweisungen werden von der Connection erzeugt:
Statement stmt = con.createStatement();
Der SQL-Code wird beim Aufruf von executeQuery() übergeben. Die Operation liefert das Ergebnis in einem ResultSet zurück.
ResultSet result = stmt.executeQuery("SELECT...");
Beim Aufruf von Änderungsoperationen wie INSERT, UPDATE und DELETE wird
die Operation executeUpdate() verwendet. Sie liefert die Zahl der geänderten
Sätze zurück.
int num = stmt.executeUpdate("DELETE...");
Ist der Typ der Anweisung nicht bekannt, etwa wenn der Benutzer die SQLAnweisung eingibt, oder wird eine Anfrage ausgeführt, die mehrere ResultSets
liefert, wird die Operation execute() verwendet. Über weitere Operationen der
Klasse Statement kann die Struktur des Ergebnisses ermittelt und auf seine
Bestandteile zugegriffen werden.
3.4
Ergebnis verarbeiten
Das Ergebnis einer SQL-Anfrage hat üblicherweise eine Tabellenform, die schlecht
im Programm verarbeitet werden kann. Bei Verwendung von JDBC wird das Ergebnis in einem ResultSet gekapselt, das Operationen zur Navigation durch das
Ergebnis anbietet.
Die Operation next() positioniert einen Zeiger auf der jeweils nächsten Zeile des Ergebnisses. Auf die einzelnen Spalten dieser Zeile wird über getXXX()Operationen zugegriffen, denen der Spaltenname oder der Spaltenindex (beginnend bei 1) übergeben wird:
6
String name;
int
plz;
while(result.next()) {
name = result.getString("Name");
plz = result.getInt(5);
...
}
Nach der Verarbeitung können ResultSet und Statement jeweils mit close()
geschlossen werden, um die belegten Ressourcen freizugeben.
Um herauszufinden, ob eine Spalte einen (SQL-) Nullwert enthält, wird wasNull() verwendet. Allerdings muß zunächst mit einer getXXX()-Operation auf
die Spalte zugegriffen werden:
name = result.getString("Name");
if(!result.wasNull())
System.out.println("Name: " + name);
3.4.1
Struktur des Ergebnisses ermitteln
Häufig ist die Struktur des Anfrage-Ergebnisses dem Anwendungsprogramm nicht
bekannt, etwa, wenn die Anfrage vom Benutzer eingegeben wird oder das Programm mit beliebigen Datenbankschemata arbeiten soll.
Für diese Fälle liefert die Operation getMetaData() Informationen über die
Struktur eines ResultSet zurück:
ResultSetMetaData rsm;
rsm = result.getMetaData();
for(int i = 0; i < rsm.getColumnCount(); i++) {
System.out.println("Spalte " +
rsm.getColumnName(i) +
", Typ: " +
rsm.getColumnTypeName(i));
}
3.4.2
Spezielle SQL-Anweisungen
Neben dem einfachen SQL-Statement gibt es zwei weitere Formen:
– Ein PreparedStatement ist eine vorübersetzte Anweisung. Sie wird bei SQLAnfragen mit Parametern verwendet. In der Anfrage werden die Parameter
durch den Platzhalter ? dargestellt. Sie können einzelnen mit set-Operationen
gesetzt werden (z.B. setInt bei einem integer -Wert). Die Position wird als
erster Parameter der set-Operation übergeben.
PreparedStatement pstmt = con.prepareStatement(
"INSERT INTO Kunden (Name, Plz) VALUES(?, ?)");
7
pstmt.clearParameters();
pstmt.setString(1, "Müller-Wattenscheid");
pstmt.setInt(2, 57068);
pstmt.executeUpdate();
– Mit einem CallableStatement können in der Datenbank gespeicherte Prozeduren (stored procedures) direkt ausgeführt werden. Ein CallableStatement
wird mit der Operation prepareCall() erzeugt:
CallableStatement cstmt = con.prepareCall(...);
...
cstmt.execute();
3.5
Meta-Daten
Die Operation getMetaData von Connection liefert Informationen über die Datenbank zurück, etwa über die vorhandenen Tabellen, gespeicherten Prozeduren
und die unterstützten SQL-Befehle.
DatabaseMetaData db_meta_data;
db_meta_data = con.getMetaData();
System.out.println("DBMS: " +
db_meta_data.getDatabaseProductName());
System.out.println("JDBC-Treiber: " +
db_meta_data.getDriverName());
3.6
Fehler und Warnungen
Alle Datenbankzugriffe müssen in try/catch-Blöcken gekapselt werden, um mögliche Ausnahmen behandeln zu können. Die Zugriffsoperationen werfen exceptions
vom Typ SQLException.
Gegenüber normalen exceptions bieten SQLExceptions folgende zusätzliche
Operationen:
– getErrorCode() liefert einen Hersteller-spezifischen Fehler-Code zurück.
– getSQLState() liefert einen Fehlercode gemäß ANSI-92-Standard zurück.
– Oft führt eine SQL-Anweisung zu mehreren Fehlern. Mehrere Fehler können
einer SQLException zugeordnet und dort in einer Liste verwaltet werden.
Mit getNextException() wird auf die jeweils nächste zugegriffen; setNextException() fügt eine SQLException an das Ende der Liste an.
Wenn eine Datenbank-Operation fehlschlägt, muß die Verarbeitung nicht
gleich abgebrochen werden. Manche Ereignisse können eher als Hinweise verstanden werden, daß etwas nicht so funktioniert hat, wie es sollte.
8
3.6.1
Warnungen
In JDBC können solche Hinweise als Warnungen (warnings) vom Anwendungsprogramm abgefragt werden. Objekte der Klassen Connection, Statement und
ResultSet können Warnungen liefern. Sie bieten dazu die Operation
public SQLWarning getWarnings() throws SQLException
an. getWarnings() liefert die erste Warnung zurück, sofern welche vorhanden
sind. Die Klasse SQLWarning ist ein Subklasse von SQLException, sie bietet also
die gleichen Operationen zum Abfragen der Fehlercodes an. Sind mehrere Warnungen vorhanden, können diese mit getNextWarning() abgefragt werden. Zu
beachten ist dabei, daß beim Ausführen der nächsten Anweisung die Warnungen
gelöscht werden – sie müssen also sofort verarbeitet werden.
Der folgende Code-Ausschnitt zeigt, wie die Warnungen einer Connection
und eines Statements verarbeitet werden können:
Statement stmt = con.createStatement();
ResultSet result = stmt.executeQuery("SELECT...");
SQLWarning warning;
while (result.next()) {
String name = result.getString("Name");
System.out.println("Name: " + name);
warning = stmt.getWarnings();
if (warning != null) {
System.out.println("---Warning---");
while (warning != null) {
System.out.println("Message: " + warning.getMessage());
//...
warning = warning.getNextWarning();
}
}
warning = result.getWarnings();
if (warning != null) {
System.out.println("---Warning---");
while (warning != null) {
System.out.println("Message: " + warning.getMessage());
//...
warning = warning.getNextWarning();
}
}
3.7
Transaktionen
JDBC enthält auch Schnittstellen zur Steuerung von Transaktionen. Das Connection-Objekt ist für die Verwaltung der Transaktionen zuständig.
Wird kein spezieller Modus angegeben, wird der auto commit-Modus verwendet. Er führt alle SQL-Anweisungen als separate Transaktionen aus. Sollen
mehrere Anweisungen zu einer Transaktion zusammengefaßt werden, kann der
auto commit-Modus ausgeschaltet werden:
con.setAutoCommit(false);
stmt.executeUpdate(...);
stmt.executeUpdate(...);
con.commit();
Die Transaktion muß dann explizit mit commit() (oder rollback()) abgeschlossen werden – andernfalls gehen alle Änderungen verloren.
9
Eine Connection bietet verschiedene Modi zur Isolation von Transaktionen.
Sie werden mit setTransactionIsolation() durch Angabe einer der folgenden
Konstanten gesetzt:
– TRANSACTION NONE: Transaktionen werden deaktiviert oder nicht unterstützt.
– TRANSACTION READ UNCOMMITTED: Andere Transaktionen können auf geschriebene Werte auch vor dem commit der schreibenden Transaktion zugreifen –
dirty reads sind also möglich.
– TRANSACTION READ COMMITTED: Transaktionen können keine Werte lesen, die
von einer Transaktion geschrieben wurden, die noch kein commit() ausgeführt
hat – dirty reads werden verhindert.
– TRANSACTION REPEATABLE READ: Dirty reads werden verhindert, eine Transaktion erhält bei aufeinanderfolgenden Lesezugriffen stets den gleichen Attributwert, auch wenn das Attribut von einer parallelen Transaktion geändert
und committed wurde.
– TRANSACTION SERIALIZABLE: Transaktionen werden vollständig isoliert, scheinen also nacheinander ausgeführt zu werden. Der Modus schließt die Eigenschaften des vorhergehenden ein und sichert zusätzlich, daß eine Transaktion
stets die gleiche Menge von Sätzen einliest, auch wenn diese durch parallele
Transaktionen geändert wurde.
10
Herunterladen