THEMA: JDBC/SQL

Werbung
THEMA: JDBC/SQL
Vortrag: Pascal Gbodogbe am 14.6.2001
TEIL_1: SQL &
TEIL_2: JDBC
Einführung:
Durch die Einführung der JDBC-Vorlesung können wir
merken:
*JDBC stellt eine standarisierte Methode zum Zugriff auf
Datenbanken aus Java-Programmen zur Verfügung.
*JDBC enthält die Möglichkeiten:
– Um Verbindungen zu Datenbanken zu öffnen und zu
schließen,
– Um SQL-Anweisungen an Datenbanken zu schicken
– und um die Antworten entgegenzunehmen und deren
Auswertung im Java-Programm zu ermöglichen.
JDBC baut vollständig auf SQL auf. SQL "Standard Query
Language". Die Kommunikation zwischen dem Java-Programm und der Datenbank erfolgt über SQL-Anweisungen.
Diese werden als Text an die Datenbank geschickt, die
daraufhin als Antwort die Ergebnisse zurückschickt. Die SQL-Anweisungen werden nicht vom Java-Compiler
auf Korrektheit geprüft. Fehler können also erst durch die
Datenbank festgestellt werden.
Bevor wir in Detail bei JDBC gehen , wollen wir SQL
in Überblick sehen.
TEIL_1: SQL
SQL - ein Überblick
Unser Überblick ist unterteilt in :
- Tabellendeklaration in SQL,
- Anfragen in SQL,
- Änderungsoperationen in SQL, und
- Definition von Sichten in SQL
Tabellendeklaration in SQL:
Bei der Deklaration einer Tabelle müssen folgenden Punkte
festgelegt werden:
* Attribute als Namen der Spalten,
* Wertebereiche als Angaben von Datentypen für die
Einträge in Spalten,
* Relationenschemata als Typdefinitionen von Tabellen,
* Schlüssel zur Festlegung eindeutiger Identifikatoren
für Tupel in Tabellen, und
* die Deklaration von Fremdschlüssel zur Erzwingung
der referentiellen Integrität bei Verweisen auf
Einträge in anderen Tabellen.
Die Sprache zur Festlegung von Datenbankbeschreibungen
nennt man database definition language, kurz DDL. Die
Tabellendeklaration in SQL sieht der Definition von
Record-Strukturen in Programmiersprachen
nicht
unähnlich, erweitert diese aber um die RDBMSspezifischen Aspecte. Folgende Beispieldeklaration zeigt
den prinzipiellen Aufbau:
CREATE TABLE book (
isbn VARCHAR ( 30 ) NOT NULL,
title VARCHAR ( 50 ) ,
price FLOAT );
Wertebereiche für Attribute: Die Möglichen Wertebereiche für Attribute sind die Datentypen:
* integer (oder auch integer4, int ),
* smallint (oder auch integer2),
* float (p) (oder auch kurz float),
* decimal ( p , q ) und numeric ( p , q ) mit jeweils q
Nachkommastellen,
* character (n) (oder kurz char(n), bei n = 1 auch char
) für Strings fester Länge n,
* character varying (n)(oder kurz varchar(n) ) für Strings
variabler Länge bis zur Maximallänge n,
* bit (n) oder bit varying (n) analog für Bitfolgen und
* date, time bzw. timestamp für Datums-, Zeit- und
kombinierte Datums-Zeit-Angaben.
BLOB und CLOB: Eine weitere Neuerung im Standard
sind die sogenannten large object-Datentypen BLOB und
CLOB. BLOB steht für binary large object; CLOB für
charater large object. Die Werte entsprechender Daten-
typen sind ( in der Regel sehr lange ) Folgen von Binärwerten ( bei einem BLOB ) oder von Character-Zeichen
(CLOB), wie sie zum Beispiel bei der Abspeicherung eines
digitalisierten Photos oder eines unstrukturierten Textes
entstehen.
Nullwerte: Mit der Klausel NOT NULL können in
bestimmten Spalten Nullwerte als Attributwerte ausgeschlossen werden.
Primärschlüssel: Das folgende Beispiel zeigt die
Definition von identifizierenden Schlüsseln ( in SQL als
Primärschlüssel bezeichnet: derjenige von mehreren
möglichen Schlüsseln, der vom System genutzt und
kontrolliert wird) und Fremdschlüsselbeziehung:
CREATE TABLE book (
isbn VARCHAR ( 30 ) NOT NULL,
title VARCHAR ( 50 ) ,
price FLOAT
publ_id INTEGER ,
PRIMARY KEY ( isbn ) ,
FOREIGN KEY publ_id REFERENCES (publisher) );
Anfragen in SQL: Eine erste SQL-Anfrage an eine
einzelne Tabelle könnte wie folgt lauten:
SELECT title , price
FROM book
WHERE isbn = ‘ 3 - 929821 - 31 - 1 ‘ ;
Diese Anfrage gibt die Relation an, aus der Daten
gewonnen werden sollen ( hinter FROM ), sowie eine
Selektionsbedingung und die Attribute einer Projektion.
Die Angaben der Selektionsbedingung können wie
gewohnt durch boolesche Operatoren verbunden werden,
die als englische Worte notiert werden (AND, OR und
NOT). Als Basisprädikate sind insbesondere die bekannten
Vergleichsprädikate erlaubt.
Duplikatunterdrückung:
SQL hat eine Multimengensemantik; die Duplikatunterdrückung muß also explizit erzwungen werden, wenn sie
erwünscht ist:
SELECT
DISTINCT lastname
FROM author;
Kreuzprodukt: Als wesentliche Möglichkeit, Daten aus
zwei Relationen miteinander zu verknüpfen, nutzt SQL das
Kreuzprodukt, und nicht den natürlichen Verbund. Die
folgende Anfrage berechnet also das Kreuzprodukt zweier
Relationen, wobei die Angabe * die Ausgabe aller
Attribute erwirkt:
SELECT *
FROM book, publisher;
Verbundbedingungen in SQL:
Der ( eigentlich erwünschte) Verbund muß durch die
Angabe einer Verbundbedingung erzwungen werden.
Hierzu müssen gleichlautende Attribute des Kreuzproduktes unterschieden werden - einen expliziten Umbenennungsoperator für Zwischenergebnisse wie in der
Relationenalgebra kennt SQL nicht. Dies erfolgt über
sogenannte Tupelvariablen, die im Standardfall aus den
Namen der Relationen abgeleitet werden:
SELECT *
FROM book , publisher
WHERE book . Publ_id = publisher . Publ_id;
Selbstverbund: Natürlich können auch neue Variablennamen generiert werden - bei Selbstverbunden ist dies
sogar notwendig, wie folgende Anfrage zeigt, die die ISBN
von Büchern berechnet, die von zwei Autoren gemeinsam
geschrieben wurde:
SELECT ba1 . isbn
FROM book_author ba1 , book_author ba2
WHERE ba1 . isbn = ba2 . isbn AND
ba1 . a_id = 3 AND
ba2 . a_id = 4;
In der letzten Standardisierungsversion SQL-92 wurden
auch explizite Verbundoperatoren eingeführt. So berechnet
die folgende Anfrage tatsächlich den natürlichen Verbund:
SELECT *
FROM book NATURAL JOIN publisher;
Üblich ist aber in SQL-Anwendungen noch die vorher
beschriebene Variante, die dem ersten SQL-Standard
entspricht. Die erste Variante wird auch von allen RDBMS
unterstützt, was beim SQL-92-Standard nicht automatisch
im vollem Umfang der Fall ist.
Vereinigung und Differenz in SQL: Der Algebraoperator
Vereinigung wird in SQL explizit mit UNION notiert. Der
Differenzbildung hingegen wird in der Regel durch
geschachtelte Anfragen realisiert, wie folgendes Beispiel
zeigt:
SELECT *
FROM customer
WHERE customer . cust_id NOT IN (
SELECT cust_id
FROM book_order );
Da geschachtelte Unteranfragen etwas komplexer in der
Semantik und den Syntaktischen Varianten sind, belassen
wir es an dieser Stelle mit diesem einfachen Beispiel.
Aggregierung:
* Die Aggregatfunktionen COUNT, SUM, AVG, MAX
und MIN erlauben es, aggregierte Werte für Spalten zu
bilden.
Gruppierung:
* Die Gruppierung mittels GROUP BY und HAVING
gruppiert Tupel in einer Relation und erlaubt die Bildung
eines aggregierenden Resultats für jede Gruppe.
Sortierung: Die Angabe von ORDER BY sortiert ein
Anfrageergebnis.
Änderungsoperationen in SQL
In SQL werden die folgenden Änderungsoperationen
unterstützt:
* Die INSERT-Anweisung ermöglicht das Einfügen eines
oder mehrerer Tupel in eine Basisrelation oder Sicht.
* Mittels UPDATE können ein oder mehrere Tupel in eine
Basisrelation oder Sicht geändert werden.
* Die DELETE-Anweisung realisiert das Löschen eines
oder mehrerer Tupel aus einer Basisrelation oder Sicht.
Die INSERT-Anweisung: Für das Einfügen mittels
INSERT kann man zwei Varianten unterscheiden. In der
ersten Variante werden konkrete Werte in eine Tabelle
eingetragen:
INSERT
INTO publisher (publ_id , name)
VALUES (1 , ‘ dpunkt - Verlag ‘ ) ;
Die Angabe der Attribute hinter dem Tabellenname ist
notwendig, wenn nicht alle Attribute gesetzt werden sollen
und die restlichen mit Nullwerten gefüllt werden sollen.
In der zweiten Variante werden die einzufügenden Tupel
mittels einer Anfrage berechnet:
INSERT INTO publisher (publ_id , name)
( SELECT id, name FROM company ) ;
Die UPDATE-Anweisung: Die Attributwertänderung
mittels UPDATE entspricht dem Zuweisungsoperator aus
Programmiersprachen ( so kann der alte Wert zur
Berechnung des neuen genutzt werden).
UPDATE book
SET price = price * 0.9
WHERE stock < 10;
Mehrere Attribute einer Tabelle können gleichzeitig gesetzt
werden.
Die DELETE-Anweisung: Bei der Löschoperation kann
neben der Angabe der Tabelle eine qualifizierende Bedingung angegeben werden, die die zu löschenden Tupeln
festlegt:
DELETE FROM book_order
WHERE status = 1;
Definition von Sichten in SQL:
Sichtdefinition in SQL: In SQL wird die Sichtdefinition
durch die Schlüsselwörter CREATE VIEW, gefolgt vom
Namen der Sicht, eingeleitet. Nach dem Sichtnamen steht
im einfachsten Fall das Schlüsselwort AS und eine SQLAnfrage, die die Sicht definiert (optionale Teile sind in
eckige Klammern gesetzt):
CREATE VIEW SichtName
[ SchemaDeklaration ]
AS SQLAnfrage
[ WITH CHECK OPTION ]
Das Relationenschema der Sicht, also Name und Datentyp
der einzelnen Spalten, wird aus der Anfrage abgeleitet und
muß nicht explizit angegeben werden. Allerdings kann
optional eine SchemaDeklaration hinter dem Sichtnamen
eingefügt werden, in der die Namen der Spalten festgelegt
werden können. Dieses Sprachkonstrukt erlaubt auch die
Umbenennung von Spalten.
WITH CHECK OPTION: Ein weiterer optionaler Teil
einer Sichtdeklaration ist die Angabe der Schlüsselwörter
WITH CHECK OPTION am Ende der Sichtdefinition.
Diese Angabe legt fest, ob Änderungen der Sicht, die den
in ihr nicht sichtbaren Teil der Datenbank beeinflussen, in
einem Test erkannt und abgewiesen werden sollen.
Das folgende Beispiel zeigt eine Sichtdeklaration in SQL:
CREATE VIEW book_info AS
SELECT b . isbn, b . title, b . price ,
a . lastname , a . firstname , p . name
FROM book b , book_author ba ,
author a , publisher p
WHERE b . isbn = ba . isbn AND
ba .a_id = a . a_id AND
b . publ_id = p . publ_id;
Änderungsoperationen auf Sichten: Soll eine Änderung
auf einer Sicht in Änderungen auf der Basisdatenbank
umgesetzt werden, müssen eine Reihe von notwendigen
oder wünschenswerten Kriterien eingehalten werden:
* Effektkonformität: Der Benutzer, der die Änderung auf
der Sicht formuliert, soll nach der ausgeführten Änderung
auf der Basisdatenbank im nächsten Zustand eine
Ausprägung der Sicht erhalten, die dem Effekt entspricht,
als wäre die Änderung auf der Sichtrelation direkt ausgeführt worden. Dieses Kriterium fordert in gewissem Sinne
die Korrektheit der Transformation.
* Minimalität bei Sichtäderungen: Die Basisdatenbank
sollte nur minimal geändert werden, um den erwähnten
Effekt zu erhalten.
* Konsistenzerhaltung bei Sichtänderungen:
Die Änderung einer Sicht darf zu keinen Integritätsverletzungen der Basisdatenbank führen.
* Zugriffskontrolle bei Sichtänderungen: Wird die Sicht
aus Gründen der Zugriffskontrolle eingeführt, darf der
bewußt ausgeblendete Teil der Basisdatenbank von
Änderungen der Sicht nicht betroffen werden.
* Zugriffsrechte: In SQL-Datenbanken wird eine einfache
Modellierung von Zugriffsrechten auf Datenbestände
unterstützt. Dabei haben Zugriffsrechte den folgenden
Aufbau (Subjekt, Objekt, Aktion):
(AutorisierungsID, DB-Ausschnitt, Operation)
Die einzelnen Punkte haben die folgende Bedeutung:
* Autorisierungsidentifikator: (kurz AutorisierungsID) ist
eine interne Kennung eines>>Datenbankbenutzers<<. Eine
Typische AutorisierungsID kann die Benutzerkennung des
Betriebssystems sein. Andere Beispiele wären Kennungen
von Zugreifenden Softwaresystemen oder auch Kennungen
von Benutzergruppen.
* Datenbank-Ausschnitte sind im Relationenmodell
gespeicherte Relationen und Sichten, aber auch ganze
Datenbanken oder Schemainformationen.
* Unter den Operationen sind insbesondere die klassischen
Operationen des Lesens, Einfügens, Änderns und Löschens
von Datensätzen zu verstehen. Je nach konkret realisiertem
System kommen weitere Operationen beispielsweise zur
Verwaltung von Systeminformationen (etwa der Datenverteilung) hinzu.
* Rechtevergabe mittelsGRANT: Im SQL-Standard werden
Rechte mittels der GRANT-Anweisung vergeben, die nach
folgendem Muster notiert wird:
GRANT Rechte
ON Tabelle
TO BenutzerListe
[WITH GRANT OPTION]
Ein Spezialfall der Angabe eines AutorisierungsIDs ist die
Angabe PUBLIC.
* Trigger: Ein Trigger besteht im wesentlichen aus der
Angabe eines Auslösers und der Angabe von auszuführenden Folgeactionen:
CREATE TRIGGER . . .
ON Opreation : ( Anweisungen );
Als Beispiel für den Einsatz von Triggern betrachten wir
die Realisierung eines berechneten Attributs durch Trigger.
Das Beispiel basiert auf zwei Relationen, der Relation
customer mit einem zusätzlichen Attribut num_orders für
die Anzahl der Aufträge und einer zweiten Relation
book_order. Der Wert des Attributs num_orders soll vom
System verwaltet werden. Hierzu definieren wir einen
Trigger wie folgt:
CREATE TRIGGER OrderCounter
ON INSERTION OF book_order o:
UPDATE customer
SET num_orders = num_order + 1
WHERE cust_id = NEW o . Cust_id;
Neben dem vorgestellten syntaktischen Grundgerüst der
Definition von Triggern sind weitere Angaben möglich,
die insbesondere den Zeitpunkt der Triggeraktivierung
betreffen:
* Zeitpunkt der Aktivierung von Trigger:
Mittels IMMEDIATE bzw. DEFERRED kann der Zeitpunkt
der Aktivierung desTriggers festgelegt werden: sofort nach
der aktivierenden Operation oder am Ende der Transaktion.
Aktuelle Implementierungen unterstützen oft nur den
IMMEDIATE-Modus.
Die Angabe FOR EACH ROW aktiviert den Trigger für
alle Einzeländerungen einer mengenwertigen Änderung
separat.
Die Angaben BEFORE und AFTER steurn etwa in der
aktuellen Version des kommerziellen Systems Oracle, ob
der Trigger direkt vor oder nach einer Änderung aktiviert
wird.
Mit REFERENCING NEW AS bzw. REFERENCING
OLD AS kann eine Tupelvariable an die neu eingefügten
bzw. gerade gelöschten (>>alten<<) Tupel einer Relation
gebunden werden. Diese >>neuen<< bzw. >>alten<<
Tupel werden auch als Elemente der sogenannten
Differenzrelation bezeichnet.
TEIL_2: JDBC
JDBC: Java DataBase Connectivity
Das JDBC-Package stellt eine vollständige Schnittstelle
zu SQL-Datenbanken zur Verfügung und erlaubt damit
den Einsatz von Java-Programmen und Java-Applets
(Applet ist eine Bezeichnung für ein Java-Progamm, das
in eine HTML-Seite integriert ist.) als Front-Ends für
kommerzielle Applikationen.
JDBC erfordert die direkte Nutzung von SQL-Anweisungen (Low-Level-API (Application Programmer Interface) )
JDBC besteht einer Menge von Klassen und Schnittstellen,
die im Java-Package java . sql zusammengefaßt sind. Die
wichtigsten Klassen und Schnittstellen sind hierbei:
java . sql . DriverManager: bildet den Einstiegspunkt, indem über diese Klasse Treiber registriert und Verbindungen zur Datenbank aufgebaut werden können.
Java . sql . Connection: repräsentiert eine Datenbankverbindung.
Java . sql . Statement: ermöglicht die Ausführung von
SQL-Anwendungen über eine gegebene Verbindung.
Java . sql .ResultSet: verwaltet die Ergebnisse einer Anfrage in Form einer Relation und unterstützt den Zugriff auf
einzelne Spalten.
JDBC am Beispiel
Ablauf einer JDBC-Anwendung
Um die verschiedenen Aspekte der JDBC-Programmierung einzugehen, wollen wir uns mit einigen kurze Beispiele befassen. Diese Programmausschnitte sollen die Anwendung der wichtigsten Klassen demonstrieren und
gleichzeitig illustrieren, wie die Benutzung von JDBC ist.
Der prinzipielle Ablauf einer JDBC-Datenbankanwendung umfaßt die folgenden Schritte:
1. Aufbau einer Verbindung zur Datenbank
2. Senden einer SQL-Anweisung
3. Verarbeiten der Anfrageergebnisse
Explizites Laden
Voraussetzung für den Aufbau einer Datenbankverbindung
ist das Laden eines geeigneten Treibers. Hierzu werden in
JDBC zwei Varianten unterstützt. Zum einen kann der
Treiber (d.h. die Java-Klasse des Treibers) explizit im Programm geladen werden:
Class . forName( “ oracle . jdbc . driver .OracleDriver “);
Automatisches Laden
Bei der zweiten Variante wird eine Liste von Treibern, jeweils getrennt durch doppelpunkt, in der Systemeigenschaft ( Property ) sql . drivers festgelegt, die vom Treibermanager beim Start automatisch geladen werden. In beiden Fällen müssen sich die Treiber nach dem Laden selbständig beim Treibermanager registrieren.
Verbindungsaufbau
Der nächste Schritt ist der Verbindungsaufbau. Hierfür
stellt der Treibermanager, d. h. die Klasse Java . sql . DriverManager, eine eigene Methode getConnection bereit.
Als Argument dieser Methode muß eine URL (Uniform
Resource Locator)angegeben werden, die den Verbindungs
mechanismus und damit den zu verwendenden Treiber bezeichnet. Zusätzlich sind noch Benutzername und Passwort zu übergeben. Der Aufruf der Methode liefert im
Erfolgsfall ein Connection-Objekt.
Connection con;
String url =
“ jdbc:oracle:thin:@antarctica:1521:mydb “ ;
con = DriverManager . getConnection (url , “tux“ ,
“ pingus “ );
Mit dem Connection-Objekt kann nun eine SQL-Anweisung erzeugt werden. Wir wollen zunächst eine einfache
Anfrage betrachten, die mit executeQuery ausgeführt wird.
String query = “ SELECT title, price, “ + “ stock
FROM book “;
Statement stmt = con.createStatement ( ) ;
ResultSet rs = stmt .executeQuery ( query);
Ausführung von Anfragen
Die executeQuery-Methode liefert ein ResultSet-Objekt, das
die Ergebnisse der Anfrage verwaltet. Die Navigation über
die Ergebnismenge erfolgt nach dem Cursor-Prinzip. Die
Ergebnismenge kann als eine Tabelle angesehen werden,
auf die zunächst zeilenweise und dann spaltenweise zuge-
griffen werden kann. In JDBC existiert jedoch kein expliziter Cursor, vielmehr wird die aktuelle Position in der Ergebnismenge vom ResultSet intern verwaltet. Zum Weitersetzen des Cursors wird die Methode next verwendet. Diese
Methode liefert so lange den Wert true, bis das Tabellenende erreicht ist. Außerdem ist zu beachten, daß der
ResultSet -Cursor zu Beginn vor dem ersten Tupel positioniert ist, d.h., bevor ein Tupel gelesen werden kann,
muß die Methode next aufgerufen werden.
Zugriff auf Spalten
Nachdem der Cursor positioniert ist, können die Spaltenwerte des aktuellen Tupels ausgelesen werden. Hierfür stehen eine Reihe von getXXX-Methoden für die verschiede-
nen Datentypen und die korrespondierenden Java-Typen
zur Verfügung. So heißt die Methode zum Lesen einer Zeichenkette beispielsweise getString, wobei der Attributtyp
der Relation hier VARCHAR und der Java-Typ des Ergebnisses java .Lang . String ist. Der Zugriff auf eine konkrete Spalte der Ergebnisrelation mit Hilfe der getXXX-Methoden erfolgt entweder über den Index der Spalte oder den
Spaltennamen. Wichtig ist dabei, daß der Spaltenindex mit
1 beginnt, d. h., die erste Spalte wird durch 1 bezeichnet,
die zweite mit 2 usw. Insgesamt kann das Ergebnis der
Anfrage wie folgt ausgewertet werden:
while (rs . next ( ) ) {
String s = rs . getString (1);
double d = rs .getDouble (2);
int
i = rs .getInt (3);
System . out .println(s + “ “ + d + “ “ + i );
}
Ressourcenfreigabe
Zum Abschluß werden die benutzten Ressourcen durch
Aufruf der close-Methoden von ResultSet und Statement
freigegeben:
rs .close ( );
stmt .close ( );
Fehlerbehandlung
Fehler werden in JDBC grundsätzlich als Ausnahmen
(Exception ) der Klasse SQLException signalisiert und sind
daher in geeigneter Weise mit einem try…catch-Block ab-
zufangen und zu behandeln.Details zu einem aufgetretenen
Fehler können über die Methode getMessage ermittelt werden, die eine Zeichenkette mit der Beschreibung des
Fehlers liefert. Der entsprechende Programmausschnitt zur
Fehlerbehandlung ist danach wie folgt zu formulieren:
try {
/ / Aufruf von JDBC-Anweisungen,
/ / die Exception generieren
} catch ( SQLException exc) {
System . out . println ( “ SQLException: “ +
exc . getMessage ( ) );
}
Mit Hilfe der bisher vorgestellten Klassen und Methoden
können wir nun die erste vollständige JDBC-Anwendung
erstellen.
Beispiel 1. JDBC-Programm
import java . sql .*;
public class JdbcDemo1 {
public static void main (String [ ] args) {
String driverClass =
“ oracle . jdbc . driver . OracleDriver “;
try { Class . forName ( driverClass );
} catch ( ClassNotFoundException exc ) {
System . out . Println ( exc . getMessage ( ) );
System .exit ( 1 );
}
try {
String url =
“jdbc : oracle : thin:@antarctica:1521:mydb“;
String query = “ SELECT title, price, “ +
“ stock FROM book “;
Connection con =
DriverManager . getConnection ( url , “ tux“ ,
“ pingus “ );
Statement stmt = con .createStatement ( );
ResultSet rs = stmt . executeQuery (query);
while ( rs . next ( ) ) {
String s = rs . getString (1);
double d = rs . getDouble (2);
int i = rs . getInt (3);
System . out . println ( s + “ , “ + d +
“ DM, “ + i );
}
} catch ( SQLException exc ) {
System . out . println (“ SQLException: “
+ exc . getMessage ( ) );
}
}
}
Nachdem wir einen ersten Überblick zur Anwendungsentwicklung mit JDBC gewonnen haben, werden wir nun auf
wichtige Aspekte im Detail eingehen.
JDBC im Detail
Verbindungsinformationen
Datenbank: mydb; Server: antarctica ;
Benutzer: tux ;
Paßwort: pingus.
Treiber und Datenbankverbindung
– Treibermanager
Der Treibermanager implementiert in der Klasse java .sql .DriverManager und bildet die Vermittlungsschicht
zwischen Anwendung und Datenbanktreiber. Da es in
jeder Java-Anwendung nur einen Treibermanager geben
kann, sind alle Methoden statisch. Die Methoden lassen
sich grob in drei Gruppen einteilen:
–
–
–
Registrieren und Laden von Treibern,
Herstellen einer Verbindung zur Datenbank,
Konfigurieren der Verbindung (Protokollierung, Login-
Timeout).
Laden von Treibern
-Explizit durch Laden der Treiber
Hierbei wird der JDBC-Treiber über den Aufruf von
Class . forName direkt geladen. Einzige Voraussetzung
für diese Methode ist ein Java-Klassenpfad, der die
Klassen des benötigten JDBC-Treibers enthält. Da keine
weiteren Konfigurationen notwendig sind, ist dies wohl
die Methode, die am häufigsten eingesetzt wird.
-Über die Systemeigenschaft sql . Driver
Die Systemeigenschaft ( Property) umfaßt eine Liste von
Treibern, die durch Doppelpunkt getrennt sind. Bei der Initialisierung des Treibermanagers, d. h. beim ersten Aufruf
einer Klassenmethode, werden alle dort angegebenen Treiber geladen. Die Eigenschaft kann z. B. beim Aufruf des
Java-Interpeters angegeben werden:
java -Djsql . drivers = foo . bar . Driver JdbcDemo1
Eine weitere Möglichkeit sind Property-Files, wie z. B.
durch den Browser HOTJAVA oder den Applet-Viewer
ausgewertet werden.
In beiden Fällen ist der Name der Klasse der Dokumentation des Herstellers zu entnehmen. Diese Tabelle zeigt
einige wichtige Treiberklassen.
Klassennamen einiger JDBC - Treiber
DBMS
Oracle 8
DB2
MiniSQL
MySQL
ODBC
Treiberklasse
oracle . jdbc . driver . OracleDriver
COM . ibm . db2 . jdbc . app .DB2Driver
com . Imaginary . sql . msql . MsqlDriver
org .gjt . mm . mysql . Driver
sun . jdbc . odbc . JdbcOdbcDriver
Treiber
Registrierung von Tribern
Wird ein JDBC-Treiber geladen, so muß dieser sich beim
Treibermanager registrieren. Hierzu existieren die Methode
/ / java . sql . DriverManager
static void registerDriver (Driver driver)
throws SQLException;
Die Methode wird normalerweise in einem Initialisierungsblock ( ein static {…. }-Block) der Treiberklasse aufgerufen, der beim Laden der Klasse über Class . forName ausgeführt wird und die Treiberregistrierung vornimmt.
Für die Deregistrierung gibt es eine entsprechende Methode deregisterDriver. Beide Methoden sind aber nur für
Entwickler für Datenbanktreibern interessant.
Weiterhin kann man explizit den Treiber für eine gegebenen JDBC-URL anfordern oder mittels eines Iterators über
alle geladenen Treiber navigieren:
/ / java . sql . DriverManager
static Driver getDriver (String url)
throws SQLException;
static java . Util . Enumeration getDrivers ( )
* Iteraor (Enumeration)
Ein für Container-Klassen ( wie Listen, Bäume, Felder,
usw. ) sehr nützliches Interface, das eine Möglichkeit
bereitstellt, alle Elemente des Containers nacheinander zu
referenzieren.
Das folgende Beispiel zeigt die Typische Anwendung
einerEnumeration e:
for (Enumeration e = v . elements ( ) ;
e . hasMoreElements ( ) ; ) {
System . out .println (e . nextElement ( ) ) ;
}
Aufbau einer Datenbankverbindung
Verbindungsaufbau
Nach dem Laden eines Treibers kann die Verbindung zur
Datenbank aufgebaut werden. Der Treibermanager stellt
hierfür drei verschiedene Methoden bereit, die sich nur in
der Form der Parameterlisten unterscheiden:
/ / java . sql . DriverManager
static Connection getConnection ( String url )
throws SQLException;
static Connection getConnection ( String url ,java . util .
Properties info ) throws SQLException;
static Connection getConnection (String url ,String user ,
String password) throws SQLException;
JDBC-URL
Alle Methoden erwarten einen Uniform Ressource
Locator (URL) als Parameter und liefern ein ConnectionObjekt. Die JDBC-URLs sind im Aufbau an die aus dem
Web bekannten URLs angelehnt. Sie haben grundsätzlich
folgende Form:
jdbc : < subprotocol > : < subname >
Hierbei bezeichnen < subprotocol > den Mechanismus zur
Datenbankverbindung (z. B. ein konkretes Protokoll) und
< subname > ein Identifikator für die Datenbank. Der
<subname >-Teil ist außerdem abhängig vom Protokoll
und kann auch eine Netzwerkadresse beinhalten.
Dagegen müssen für eine Verbindung zu einer OracleDatenbank die Netzwerkadresse (Hostname und Port) des
Datenbankservers bzw. Des Listener-Prozesses sowie der
Name der Datenbank angegeben werden :
jdbc : oracle : thin : @antarctica : 1521 : mydb
Beim Aufruf der Methode getConnection versucht der Treibermanager einen Treiber zu finden, der die angegebene
URL akzeptiert. Dazu werden alle geladenen Treiber nach
einander getestet, d. h ., es wird die Methode acceptsURL-
von java . sql .Driver aufgerufen. Mit dem ersten gefundenen Treiber wird die Verbindung zur Datenbank hergestellt. Als weitere Parameter für den Verbindungsaufbau
sind insbesondere Benutzername und Paßwort anzugeben,
entweder direkt oder als Schlüssel-Wert-Paare in Form
eines Property-Objektes.
Timeouts und Protokollierung
Timeout : Mit den weiteren Methoden der Klasse DriverManager kann die maximale Wartezeit für das Anmelden
bei der Datenbank (Timeout) abgefragt bzw. geändert werden. Die Zeitangabe erfolgt dabei jeweils in Sekunden:
/ / java . sql . DriverManager
static int getLoginTimeout ( )
static void setLoginTimeout (int seconds)
Tracing : Schließlich kann noch ein Ausgabestrom für die
Protokollierung (Tracing) vom Treibermanager sowie den
Treibern angegeben werden:
/ / java . sql . DriverManager
static java . io . PrintStream getLogStream ( )
static void setLogStream ( java . io . PrintStream out )
Als Parameter muß hier ein Stream-Objekt eingesetzt werden, wie z. B. System . out oder ein Strom auf einer geöffneten Datei. Auf diese Weise lassen sich Fehlermeldungen
protokollieren. Durch Übergabe von null als Parameter
kann die Protokollierung wieder abgeschaltet werden.
Diese Methoden sind in JDBC2 durch setLogWriter bzw.
getLogWriter ersetzt, die einen PrintWriter-Strom als Parameter bzw. Ergebnis verwenden.
Connection
Ein Connection-Objekt repräsentiert eine Verbindung zur
Datenbank. Eine Applikation kann dabei mehrere Verbindungen zu einer oder zu verschiedenen Datenbanken
öffnen. Wie bereits beschrieben, wird eine Verbindung
durch Aufruf der getConnection-Methode des Treibermanagers erzeugt. Über eine geöffnete Verbindung können
SQL-Anweisungen zur Datenbank gesendet und Transaktionsabläufe gesteuert werden.Weiterhin lassen sich Informationen über die Datenbank abfragen.
Katalogzugriff: Der Bereich der Datenbank, auf den über
die Verbindung zugegriffen werden soll, kann mit den
Methoden getCatalog und setCatalog festgelegt bzw.
abgefragt werden:
/ / java . Sql . Connection
void setCatalog (String Catalog) throws SQLException;
String getCatalog ( ) throws SQLException;
Jedes Datenbankobjekt (Relationen, Sichten, Prozeduren,
usw.) ist eindeutig über das Tripel (Katalogname, Schemaname, Objektname) identifizierbar, wobei im Normalfall
der Katalogname dem Datenbanknamen und der Schemaname dem Benutzernamen entspricht. Für eine Verbindung
ist der Katalog auf den Default-Katalog des angemeldeten
Benutzers initialisiert, so daß diese Methoden nur selten
benötigt werden. Die exakte Bedeutung des Katalognamens ist jedoch vom DBMS abhängig.
Mit der Methode setReadOnly kann angegeben werden,
daß nachfolgend nur Leseoperationen ausgeführt werden
sollen.
/ / java . sql . Connection
void setReadOnly ( boolean readOnly )
throws SQLException;
boolean isReadOnly ( ) throws SQLException;
Allerdings bedeutet das nicht, daß Schreiboperationen
damit verboten sind. Vielmehr ist es ein Hinweis für den
Treiber und das DBMS und z. B. Optimierungen vorzuneh
men. Der aktuelle Modus der Verbindung kann über
isReadOnly abgefragt werden. Für einen Treiber, der keine
Schreibinformationen unterstützt, wird hier entsprechend
auch true zurückgegeben.
Verbindungsabbau: Eine aktive Verbindung zur Datenbank wird mittels der close-Methode beendet.
/ / java . sql . Connection
void close ( ) throws SQLException;
boolean isClosed ( ) throws SQLException;
Obwohl das Schließen auch automatisch bei der Freigabe
des Objektes durch den Garbage Collector erfolgt, ist das
explizite Schließen die empfohlene Variante. So können
Ressourcen des DBMS sofort freigegeben werden, wenn
sie nicht mehr benötigt werden. Das automatische Schließen wird dagegen unter Umständen (z. B. Hauptspeicher)
erst viel später erfolgen. Der Zustand einer Verbindung ist
wiederrum über die Methode isClosed abgefragbar.
Transaktionssteuerung
Der Begriff der Transaktion ist als elementare Ausführungseinheit zur Überführung der Datenbank von einem konsistenten Zustand in einen veränderten, konsistenten Zustand
unter Einhaltung des ACID-Prinzips vorgestellt. Unter
ACID versteht man:
Atomarität = Ununterbrechbarkeit.
Konsistenz = Integritätserhaltung.
Isolation = Isolation.
Dauerhaftigkeit = Persistenz der Ergebnisse.
Die Kommandos zur Steuerung des Transaktionsablaufes
sind als Methoden der Schnittstelle Connection definiert:
/ / java . Sql . Connection
void commit ( ) throws SQLException;
void rollback ( ) throws SQLException;
Mit der Methode commit wird angezeigt, daß die Transaktion erfolgreich abgeschlossen werden soll und alle Än
derungen permanen t in die Datenbank zu schreiben sind.
Mit rollback wird die aktive Transaktion abgebrochen alle Änderungen, die im Rahmen dieser Transaktion durchgeführt wurden, werden rückgängig gemacht.Ein explizites
>> Begin of Transaction << existiert dagegen nicht. Transaktionen werden - wie in relationalen Datenbanksystemen
üblich - automatisch mit dem ersten Datendefinitions- bzw.
Datenmanipulationsbefehl bzw. mit dem Ende oder Abbruch der vorangegangenen Transaktion gestartet.
Auto-Commit: Wird eine neue Verbindung zur Datenbank
hergestellt, so befindet sich diese im Auto-Commit-Modus,
d. h., nach jeder Anweisung wird automatisch ein Commit
ausgeführt und eine Transaktion umfaßt jeweils nur eine
Anweisung. Dieser Modus kann mit der Methode
setAutoCommit ein- (mit true als Parameter) bzw.
ausgeschaltet (false) werden:
/ / java . Sql . Connection
void setAutoCommit (boolean enable)
throws SQLException;
boolean getAutoCommit ( ) throws SQLException;
Ohne Auto-Commit wird die Transaktion erst mit einem
expliziten Commit bzw. Rollback abgeschlossen. Durch
das Aabschalten des Auto-Commit-Modus lassen sich meh
-rere Anweisungen zu einer Transaktion zusammenfassen.
Außerdem können so Änderungen bis zum Ende der Transaktion rückgängig gemacht werden. Das folgende Beispiel
zeigt die Nutzung von Transaktionen beim Einfügen mehrerer Datensätze, wobei beim Auftreten eines Fehlers alle
Änderungen zurückgenommen werden.
Die Fehlermeldung wird außerdem durch Weiterleiten der
Ausnahme an den Aufrufer propagiert:
try {
stmt = con . createStatement ( );
con .setAutoCommit ( false) ;
stmt . executeUpdate ( “ INSERT INTO book “ +
“ VALUES ( ‘ 3-608--93421-9 ‘ , ‘ Otherland ‘ , “ +
“ 2, 1, 49.90, 20 ) “ );
stmt . executeUpdate ( “ INSERT INTO author “ +
“ VALUES (1, ‘Tad‘ , ‘Williams‘ )“ );
stmt . executeUpdate ( “ INSERT INTO
book_author “ + “ VALUES (‘3- 608-93421-9‘ , 1)“);
con . Commit ( );
} catch (SQLException exc) { con . Rollback ( );
throws exc;
}
Für eine Reihe von Anwendungen ist die strenge Einhaltung
der ACID-Eigenschaften jedoch zu restriktiv und zu Laufzeiteinbußen.
Ausführung von SQL-Anfrage
In JDBC wird jede SQL-Anweisung durch ein StatementObjekt gekapselt. Dieses Objekt sendet die Anweisung zur
Datenbank, liefert das Ergebnis zurück und verarbeitet
Parameter, die der Anweisung übergeben werden. Es werden drei Formen von Statements unterstützt:
Arten von Statements:
java . sql .Statement als Basisschnittstelle für alle anderen
Formen erlaubt die Verarbeitung einfacher Anweisungen
ohne Parameter.
Java . sql .PreparedStatement kapselt eine vorkompilierte
Anweisung und wird insbesondere dann eingesetzt, wenn
eine Anweisung mehrfach und mit verschiedenen IN-Parametern ausgeführt werden soll.
Java . sql .CallableStatement ermöglicht den Aufruf von
gespeicherten Prozeduren mit IN- und OUT-Parametern.
Erzeugen von Anweisungen
- Erzeugen von Statements
Ein Statement-Objekt wird grundsätzlich über die Connection-Schnittstelle erzeugt. Hierzu ist für jede Anweisungsform eine eigene Methode definiert:
/ / java . sql . Connection
Statement createStatement ( ) throws SQLException;
PreparedStatement preparedStatement (String sql)
throws SQLException;
CallableStatement prepareCall ( String sql)
throws SQLException;
Ausführung von Anweisungen
Die Statement- Schnittstelle definiert als Basisschnittstelle
die Methoden für alle Anweisungsformen. Zur Ausführung
der Anweisungen stehen drei execute-Methoden zur Verfügung.
/ / java . sql . Statement
ResultSet executeQuery ( String sql)
throws SQLException;
int executeUpdate (String sql) throws SQLException;
boolean execute (String sql) throws SQLException;
-Ausführung von SELECT-Anweisungen
Mit der Methode executeQuery wird eine SELECT-Anweisung ausgeführt. Diese Methode liefert ein ResultSet-Objekt mit dem Anfrageergebnis:
Statement stmt = con . createStatement ( );
ResultSet rs = stmt . executeQuery (“ SELECT * FROM
book“);
Ausführung von DDL- und DML-Anweisungen
DDL-(Database Definition Language) und DML-(Database
Manager Language) Anweisungen, d. h. CREATE TABLE
oder INSERT, UPDATE und DELETE, werden über die
Methode executeUpdate zur Datenbank gesendet. DMLAnweisungen manipulieren Tupel einer Relation, das Ergebnis ist demnach die Anzahl der betroffenen Tupel. Für
DDL-Anweisungen ist dagegen der Rückgabewert immer
0.
Int nrows = stmt . executeUpdate (“ DELETE FROM
customer WHERE cust_id = 256 “);
Anweisungen mit mehreren Ergebnisse
Eine besondere Behandlung erfordert Anweisungen, die
mehrere Ergebnisse liefern. Dies kann z.B. bei gespeicherten Prozeduren auftreten, die mehrere UPDATE-Operationen oder mehrere Anfragen ausführen. Für diese (eher selte
nen) Fälle wird die Methode execute verwendet. Diese Methode liefert den Wert true, wenn das Ergebnis der Anfrage
ein ResultSet-Objekt ist ( z. B. nach einem SELECT ) und
false, wenn es ein Integer-Wert ist (z. B. die Anzahl der
betroffenen Tupel nach einem UPDATE). Das entsprechende Ergebnis wird mit den folgenden Methoden ermittelt:
/ / java . sql .Statement
ResultSet getResultSet ( ) throws SQLException;
int getUpdateCount ( ) throws SQLException;
boolean getMoreResults ( ) throws SQLException;
Ein ResultSet-Objekt wird danach durch getResultSet geliefert, die Anzahl der betroffenen Tupel mit getUpdateCount
Ob weitere vorliegen und von welcher Art diese sind, kann
mit getMoreResults erfragt werden. Diese Methode liefert
true, wenn das nächste Ergebnis ein ResultSet-Objekt ist,
und false für einen Integer-Wert. Das jeweils nächste
Ergebnis wird durch getResultSet bzw. geUpdateCount
ermittelt, wobei getResultSet den Wert null zurückgibt,
wenn das Ergebnis ein Integer-Wert ist und getUpdateCount einen der folgenden Werte liefert:
>0 für eine UPDATE-, INSERT- oder DELETE-Operation
0 für eine DDL-Operation bzw. eine DML-Operation, die
keine Tupel verändert hat, und
-1, wenn das Ergebnis ein ResultSet-Objekt ist oder keine
weiteren Ergebnisse vorliegen.
Damit sind alle Ergebnisse ermittelt, wenn die Bedingung
gilt: (getMoreResults ( ) = = false) & &
(getUpdateCount ( ) = = -1)
Das folgende Beispiel demonstriet die verwendung dieser
Methoden beim Aufruf einer gespeicherten Prozedur, die
mehrere ResultSets zurüchgeben kann.:
CallableStatement stmt = con. prepareCall (“
{ call MultipleResultsProc } “); stmt. execute ( ) ;
while (stmt. getMoreResults ( ) )
{ResultSet rs = stmt. getResultSet ( );
/ / ResultSet auswerten
}
Parameter für Ausführung von Anweisungen
Weiterhin lassen sich die Ausführung von Anweisungen
sowie die Ergebnisgröße beeinflussen. Mit setQueryTimeout wird die maximale Zeit des Wartens auf die Ausführung in Sekunden festgelegt, wobei der Default-Wert von
0 für unbegrenztes Warten steht. Ein Überschreiten des
gesetzten Limits wird durch eine Exception signalisiert.
Die Maximale Anzahl von Tupeln im Anfrageergebnis
wird mit der Methode setMaxRows vordefiniert. Auch hier
ist der Default-Wert 0 und bedeutet keine Einschränkung
der Anzahl. Wird dagegen ein anderer Wert angegeben, so
werden nur die entsprechende Anzahl von Tupeln ausgelesen, der Rest wird ignoriert. Die Maximale Größe einer
Spalte in Bytes kann mit setMaxFieldSize festgelegt werden, wobei dies nur für die SQL-Typen VARBINARY,
LONGVARBINARY, CHAR, VARCHAR und LONGVARCHAR relevant ist. Überschreiten die Daten das festgelegte
Limit, so wird der Rest abgeschnitten. Der Default-Wert
von 0 bedeutet auch hier keine Beschränkung.
/ / java . sql . Statement
int getMaxFieldSize ( ) throws SQLException;
void setMaxFieldSize (int max) throws SQLException;
int getMaxRows ( ) throws SQLException;
void setMaxRows (int max) throws SQLException;
int getQueryTimeout ( ) throws SQLException;
void getQueryTimeout (int secs ) throws SQLException;
Abbruch von Transaktionen
Schließlich läßt sich die Ausführung einer Anweisung über
die Methode cancel durch einen anderen Thread (Ausfüh
rungsfaden)abbrechen, wenn Treiber und DBMS dies unterstützen.
/ / java . sql . Statement
void cancel ( ) throws SQLException;
void close ( ) throws SQLException;
Nachdem eine Anweisung ausgeführt und die Ergebnisse
ermittelt wurden, sollten damit verbundene Ressourcen
durch Aufruf der close-Methode freigegeben werden. Auch
hier gilt die bereits beim Connection-Objekt gegebene Emp
-fehlung der expliziten Freigabe gegenüber dem automatischen Schließen durch den Garbage Collector („ Speicherfreigeber “).
Abbildung von SQL-Typen in java
Da SQL und Java jeweils eigene Typsysteme besitzen, muß
eine Abbildung zwischen beiden definiert werden. Das Pro
-blem der Typabbildung tritt in JDBC an drei Stellen auf:
beim Zugriff auf Spaltenwerte eines Tupels mit den
getXXX-Methoden der Schnittstelle ResultSet,
bei der Übergabe von Parametern an ein PreparedState
-ment mit den setXXX-Methoden sowie
beim Zugriff auf die OUT-Parameter eines CallableStatements.
SQL - Typbezeichner: Darüber hinaus existiert noch eine
weitere Schwierigkeiten: Einige SQL-Typen werden von
den DBMS-Herstellern teilweise unterschiedlich bezeichnet, insbesondere die Datentypen zum Speichern großer
Binärdaten. Zur Überwindung dieser Inkompatibilitäten
sind in JDBC zunächst einige generische Typbezeichner
definiert. Diese sind in der Klasse java . sql . Types als
Konstanten zusammengefaßt:
CHAR, VARCHAR, LONGVARCHAR für Zeichenketten
BIT für Bitwerte und BINARY,VARBINARY, LONGVAR
-BINARY für Binärfelder,
TINYINT, SMALLINT, BIGINT und INTEGER für Integer-Werte,
REAL, FLOAT und DOUBLE für Gleitkommawerte,
DATE, TIME, TIMESTAMP für Datums- und Zeitwerte.
Diese Konstanten werden in JDBC überall dort verwendet,
wo SQL-Typen anzugeben sind, also z. B. beim Registrieren von OUT-Parametern für gespeicherte Prozeduren. Die
Umsetzung in die DBMS-spezifischen Typen erfolgt wenn notwendig - durch den Treiber.
Beim Auslesen von Werten aus einem ResultSet-Objekt
bzw. bei der Übergabe von IN-Parametern an ein Prepared
-Statement sind dagegen die getXXX- bzw. setXXX-Methoden zu verwenden. Hierbei findet ebenfalls eine Konvertierung zwischen SQL- und Java-Typ durch den Treiber statt.
Die verwendete Abbildungsvorschrift ist in nachfolgenden
Tabelle dargestellt. Die getXXX- und setXXX-Methoden
sind entsprechend dem Java-Typ bezeichnet, wobei für die
Byte-Felder getBytes bzw. setBytes verwendet wird. Der
in der Tabelle angegebene Java-Typ ist auch der Typ, der
beim Aufruf von getObject zurückgegeben wird.
Datums- und Zeittypen:
Zur Repräsentation der Datums- und Zeittypen sind in
JDBC drei zusätzliche Klassen definiert: Date, Time und
Timestamp. Diese Klassen kapseln im wesentlichen die
Funktionalität der Klassen aus java . util und fügen nur
noch die Behandlund der Escape-Syntax hinzu. So kann
ein Date-Objekt für den 18. Februar 1999 über den normalen Konstruktoraufruf erzeugt werden:
Date d = new Date ( 99 , 2 , 18 );
Das erste Argument bezeichnet dabei die Jahreszahl minus
1900, d. h., für das Jahr 2000 ist dementsprechend der
Wert 100 anzugeben. Das zweite und dritte Argument sind
Monat und Tag. Alternativ dazu kann das Date-Objekt
auch durch Aufruf der statischen Methode valueOf erzeugt
werden:
Date d = Date . valueOf ( “ 1999 - 02 - 18 “ );
Diese Methode ist eine Zeichenkette mit dem gewünschten
Datum in Escape-Syntax als Argument zu übergeben. In
Ähnlicher Weise wird die Klasse java . sql .Time zur Repräsentation der Uhrzeit (SQL-Typ Time) verwendet. Hier ist
das Format der Zeitangabe hh : mm : ss.
Tabelle : Abbildung zwischen Java- und JDBC-Typen .
JDBC -Typ
CHAR
VARCHAR
LONGVARCHAR
NUMERIC
DECIMAL
BIT
TINYINT
SMALLINT
Java-Typ
String
String
String
java . math . BigDecimal
java . math . BigDecimal
boolean
byte
short
INTEGER
BIGINT
REAL
FLOAT
DOUBLE
BINARY
VARBINARY
LONGVARBINARY
DATE
TIME
TIMESTAMP
int
long
float
double
double
byte [ ]
byte [ ]
byte [ ]
java . sql . Date
java . sql . Time
java . sql . Timestamp
BEISPIELANWENDUNG MIT JDBC
Beispiel 1: Eintragen einer Bestellung
Die Methode createOrder zum Anlegen einer Bestellung
ist etwas aufwendiger. Hier ist zunächst die Bestellung als
ein Tupel in die Relation book_order einzutragen und für
jeden Artikel dieser Bestellung zusätzlich noch ein Tupel
in die Relation order_item einzufügen. Die einzelnen
Artikel werden dabei durch Einlesen der ISBN sowie der
Anzahl vom Benutzer erfragt. Da die Ausführung der einzelnen Anweisungen aufgrund von Verletzungen der Integritätsbedingungen (z. B. wenn ISBN oder Kundennummer
nicht existieren) fehlschlagen kann, muß der gesamte Ablauf als eine Transaktion realisiert werden. Hierzu wird das
Auto-Commit zu Beginn ausgeschaltet und im Fehlerfall
ein Rollback ausgeführt. Erst wenn die Bestellung voll-
ständig erstellt werden konnte, wird das abschließende
Commit gesendet.
Programm:
void createOrder (int custId)
throws SQLException {
boolean ready = false;
preparedStatement stmt1, stmt2;
try {
con . setAutoCommit (false);
/ / Bestellung Eintragen
stmt1 = con . prepareStatement (
“ INSERT INTO book_order “ +
“ VALUES ( order_seq. NEXTVAL, ?, “ +
“ SYSDATE, 0 ) “);
stmt1 . setInt (1, custId);
stmt1 . executeUpdate ( );
stmt2 = con . prepareStatement (
“ INSERT INTO order_item “ +
“ VALUES (order_seq . CURRVAL, ?, ?)“ );
do{
/ / ISBN erfragen
String isbn = Utils . readString ( “ isbn: “);
if ( isbn = = null | | isbn . Length ( ) = = 0 )
ready = true;
else {
/ / Anzahl erfragen
int num = Utils . readInt ( “ number: “ );
if (num > 0) {
/ / Bestellposition eintragen
stmt2 . setString ( 1, isbn );
stmt2 . setInt ( 2, num);
stmt2 . ExecuteUpdate ( );
}
}
} while (! ready);
con . commit ( );
}
catch ( SQLException exc ) {
con . Rollback ( ); throws exc;}
}
Herunterladen