Datenbankzugriff über JDBC Content .. ....... ... ..... ... ... .. ... ... ... ... ... ... ... ... ... . ... .. ... ... . . ... . ... ... ... ... . ... ... ... ... ... . ... ... ... ... . ... ... ... ... ... . ... ... ... ... . ... ... ... ... ... . ... ... ... ... . ... .. Information Concept Topic Datenbanken I (Systemorientierte Informatik IV) SS 2007 Gunar Fiedler ([email protected]) Institut für Informatik Arbeitsgruppe Technologie der Informationssysteme“ ” Christian-Albrechts-Universität zu Kiel 1 Einführung Mit SQL existiert eine standardisierte Sprache, um auf Daten eines relationalen Datenbanksystems zuzugreifen. Die Art und Weise, wie SQL-Anfragen an das System übermittelt werden, ist jedoch für jedes Datenbankmanagementsystem spezifisch. Oftmals unterscheiden sich sogar verschiedene Versionen desselben DBMS in den verwendeten Schnittstellen. Eine Anwendung, die auf ein DBS zugreifen möchte, muss die Kommunikationsprotokolle des DBMS kennen. Die Hersteller bieten deshalb für die gängigen Programmiersprachen und Laufzeitumgebungen Bibliotheken an, die den Datenbankzugriff ermöglichen. In PHP kann man beispielsweise auf eine MySQL-Datenbank folgendermaßen zugreifen (die Variablen sind selbsterklärend, die Fehlerbehandlung wurde weggelassen): $conn = mysql_connect($host,$user,$password); mysql_select_db($db,$conn); $result = mysql_query("select HOTEL_CODE,HOTEL_NAME from ACCOMMODATION"); while($row = mysql_fetch_row($result)) { echo "Code: $row[0], Name: $row[1]<br />"; } mysql_close($conn); Wenn diese Anwendung z.B. nach PostgreSQL portiert werden soll, so müssen alle mysql_*-Aufrufe z.B. durch entsprechende pg_*-Aufrufe des PostgreSQL-Moduls ersetzt werden. Um anwendungsseitig einen (relativ) DBMS-neutralen Zugriffsmechanismus auf Datenbanken zu ermöglichen, wurden diverse Frameworks spezifiziert, z.B. die Open Database Connectivity (ODBC), ActiveX Data Objects (ADO) oder die Java Database Connectivity (JDBC). Mit letzterem wollen wir uns hier beschäftigen. Um ein mögliches Missverständnis auszuschließen: JDBC ist kein Framework, dass die systemspezifische Unterstützung verschiedener SQL-Dialekte abstrahiert. Die 2 Grundgerüst eines JDBC-basierten Programms Datenbankzugriff über JDBC JDBC-Infrastruktur sorgt lediglich dafür, dass eine SQL-Anfrage von der Anwendung standardisiert an das DBS übergeben und das Ergebnis zurückgeliefert wird. JDBC sorgt nicht dafür, dass z.B. die in MySQL optionale from-Klausel beim Zugriff auf andere Systeme automatisch ergänzt wird. SQL Tuning“ für spezifische Systeme ” bleibt weiterhin Aufgabe des Programmierers. 2 Grundgerüst eines JDBC-basierten Programms Abbildung 1 zeigt den Grobaufbau einer JDBC-basierten Anwendung. Sie besteht aus 3 Schichten: .. ...... ...... ...... ...... .. ...... ...... ...... ...... Applikationslogik .. ...... ...... ...... ...... .......................................................................................................................................... .. ...... ...... ...... ...... . ...... ...... ...... ...... JDBCTreiber .. ...... ...... ...... ...... . ...... ...... ...... ...... JDBCTreiber . ...... ...... ...... ...... .. ...... ...... ...... ...... . ...... ...... ...... ...... JDBCTreiber . ...... ...... ...... ...... . ...... ...... ...... ...... .. ...... ...... ...... ...... .. ...... ...... ...... ...... ODBCTreiber .. ...... ...... ...... ...... .......................................................................................................................................... .. ........................ ................................ ......... ...... ...... ..... .... ... ..... ... .... ... ...... ...... ......... ........ . . .................. . . . . . . . . . . ....................... DB2 ...... ...... ...... ...... ....... ....... ........... .................................. .. ........................ ................................ ......... ...... ...... ..... .... ... ..... ... .... ... ...... ...... ......... ........ . . .................. . . . . . . . . . . ....................... Oracle ...... ...... ...... ...... ....... ....... ........... .................................. .......................................................... ......... ...... ...... ..... .... ... ..... .. .... .... ...... ..... ......... ........ . . .................. . . . . . . . . . . ........................ CSV-Datei ...... ...... ...... ...... ....... ....... ........... .................................. Abbildung 1: Aufbau einer JDBC-basierten Anwendung • Anwendungslogik: Sie definiert die Verarbeitungsregeln der Geschäftsprozesse. • JDBC-Datenbanktreiber: Er kapselt die Spezifika des Datenbanksystems und stellt der Anwendungslogik die Daten der Datenbank mit Hilfe einer standardisierten Schnittstelle zur Verfügung. • Datenbank: Speichert die Daten. Der Treiber kommuniziert mit der Datenbank über ein DBMS-spezifisches Protokoll. 2 SS 2007, Gunar Fiedler, ISE@CAU Datenbankzugriff über JDBC 2 Grundgerüst eines JDBC-basierten Programms Aus Sicht des Java-Programmierers ist ein JDBC-Datenbanktreiber eine Bibliothek bestehend aus einer Reihe von Klassen. Die Klassen implementieren die Interfaces der Standard-Packages java.sql und javax.sql. Diese Interfaces erlauben das datenbankunabhängige Programmieren mit Hilfe von JDBC: die Anwendung sieht“ ” nur ein java.sql-Interface. Welche Klasse aus welchem Treiber letztlich instanziiert wurde, bleibt verborgen. Der Zugriff auf eine Datenbank über JDBC innerhalb eines Java-Programms lässt sich in einzelne Phasen aufteilen, die nacheinander ausgeführt werden müssen, Abbildung 2 zeigt eine Übersicht. ppppp Laden des Treibers pppppppp ppppp Herstellen der Verbindung zum DBMS ppppppppp pppppp Erzeugen eines Statement-Objekts ppppppppp ppppppppppppppp pppppppppw p p p p ppppppppppppppp p p p p p p p p pppp ppppppppppppppp pp pppppppppp p ppppppppppppppp p p p p p p p p p p p pp ppppppppppppppp p p p p p p p p p p p p ppppppppppppppp ppp p p p p p p p p p p ppppppppppppppp p p ppppppppppppppp pppp ppppp ppppppppppppp Ausführen einer Anfrage w Ausführen eines Updates ppppp Auswertung der Ergebnisse p pppp ppp pppppppppp ppp ppppppppp pppppppppp pp pppppppppp pppppppppp p p p p p p p p p p p pppppppppp ppppp pppppppppp pppp pppppppp pppppppppp ppp ppppppppp pppppppppp pp pppppppppp p p p p p p pppppppppp p p p p p pppp pppppppppp ppp ppppppppp ppppppppppw pp pppppppppp w ppppp Schließen der Verbindung Abbildung 2: Phasen des Datenbankzugriffs 1. Laden des Treibers: In dieser Phase werden die Klassen des JDBC - Treibers dynamisch in die SS 2007, Gunar Fiedler, ISE@CAU 3 3 Die Phasen im Detail Datenbankzugriff über JDBC Anwendung eingebunden und registriert. 2. Herstellen der Verbindung: Der Treiber baut eine Verbindung zum DBMS auf. 3. Erzeugen eines Statements: Statements sind die JDBC-Repräsentation für SQL-Anweisungen. Bevor eine SQL-Anfrage gestartet werden kann, muss sie in ein Statement verpackt“ ” werden. 4. Ausführen des Statements: Die SQL-Anfrage wird an das DBMS übermittelt und dort ausgeführt. Die Resultate werden zurückgeliefert. 5. Auswertung der Ergebnisse: Über die Methoden des Interfaces java.sql.ResultSet kann das Ergebnis einer Anfrage ausgewertet werden. 6. Schließen der Verbindung: um nicht weiter benötigte Ressourcen freizugeben. 3 Die Phasen im Detail Dieser Abschnitt verdeutlicht die einzelnen Phasen eines JDBC-Zugriffs anhand von Codebeispielen und Abbildungen. Grundlage bildet wieder unsere 3-SchichtenAnwendung: .. ...... ...... ...... ...... .. ...... ...... ...... ...... Applikationslogik .. ...... ...... ...... ...... .......................................................................................................................................... .. ...... ...... ...... ...... .. ...... ...... ...... ...... DB2-Treiber .. ...... ...... ...... ...... .......................................................................................................................................... ....................................... ............... ........ ...... ........ .... ...... ... ... .... .... ... ...... .... ......... ...... .................. ......... ................................. DB2 ...... . ..... ...... ....... ...... ........... ....... .................................. 3.1 Laden des Treibers Wie bereits angedeutet, ist ein JDBC-Datenbanktreiber nichts anderes als eine Bibliothek diverser Java-Klassen, welche die Interfaces aus java.sql und javax.sql 4 SS 2007, Gunar Fiedler, ISE@CAU Datenbankzugriff über JDBC 3 Die Phasen im Detail implementieren. Jeder Treiber enthält als Repräsentanten eine Hauptklasse“, die ” das Interface java.sql.Driver implementiert. Über diese Klasse wird der Treiber identifiziert und angesprochen. Eine zentrale JDBC-Komponente ist der Treibermanager. Er wird durch die Klasse java.sql.DriverManager repräsentiert und ist zuständig für die Verwaltung und den Zugriff auf die Datenbanktreiber. Ein Treiber steht der Anwendung erst zur Verfügung, wenn er sich beim Treibermanager registriert hat. Das dynamische Laden eines Treibers gestaltet sich genauso wie das Laden jeder anderen beliebigen Java-Klasse. Angenommen, wir möchten den Treiber für DB2-Datenbanken laden. Die Hauptklasse“ des JDBC-Treibers hat den Namen ” com.ibm.db2.jcc.DB2Driver: try { C l a s s . forName ( ”com . ibm . db2 . j c c . DB2Driver ” ) . n e w I n s t a n c e ( ) ; } catch ( C l a s s N o t F o u n d E x c e p t i o n e ) { // F e h l e r b e h a n d l u n g , f a l l s K l a s s e n i c h t g e f u n d e n wurde } Die Treiberklasse sorgt automatisch für eine Registrierung beim Treibermanager. Das Laden des Treibers muss nur einmal während der Initialisierungsphase durchgeführt werden, der Treiber steht bis zum Ende der Anwendung zur Verfügung: . ...... ...... ...... ...... .. ...... ...... ...... ...... Applikationslogik .. ...... ...... ...... ...... .......................................................................................................................................... .. ...... ...... ...... ...... .. ...... ...... ...... ...... DB2-Treiber .. ...... ...... ...... ...... .......................................................................................................................................... ................................. .................. ......... ......... ...... ...... .... .... ... ..... . .... ... ...... ..... ......... ....... . . . . . . .................. . . . . ............................. DB2 ...... ...... ...... ...... ....... ........... ....... .................................. 3.2 Herstellen der Verbindung Nachdem der Datenbank-Treiber in die Anwendung integriert wurde, kann die Verbindung zur Datenbank geöffnet werden. Dafür sind die folgenden Informationen von Bedeutung: SS 2007, Gunar Fiedler, ISE@CAU 5 3 Die Phasen im Detail Datenbankzugriff über JDBC • Welcher Treiber soll benutzt werden? • Wie heißt die Datenbank und wo befindet sie sich? • Mit welchen Daten authentifiziere ich mich gegenüber der Datenbank? • ... Das Öffnen der Verbindung wird durch den Treibermanager und dessen statische Methode getConnection() veranlasst. Diese Methode liefert ein Objekt zurück, dessen Klasse das Interface java.sql.Connection implementiert. Die oben genannten Daten werden der Methode in Form einer URL (uniform resource locator) übergeben. Diese URL hat die folgende standardisierte Form: jdbc:<subprotocol>:<subURL> Über die Zeichenkette <subprotocol> wird der gewünschte Treiber spezifiziert. Der Inhalt von <subURL> hängt vom konkreten Treiber ab. Der DB2-Treiber erwartet beispielsweise folgende URL: jdbc:db2://<host>:<port>/<database>:<parameter> Die Kombination aus <host> und <port> gibt dabei den Server an, auf dem sich die Datenbank befindet, <database> spezifiziert den Namen der Datenbank. Zusätzliche Parameter können in der Form <name>=<wert>; nach dem Doppelpunkt angegeben werden. Für Verbindungsparameter sei auf das DB2-Handbuch verwiesen. Angenommen, der Nutzer joe (Passwort: foo) möchte die DB2-Datenbank namens Personal auf dem Rechner dbserver nutzen. Das DBMS ist über Port 1234 erreichbar. Es gibt 3 verschiedene Varianten der DriverManager.getConnection-Methode: • DriverManager.getConnection(String url): Der allgemeine Fall. Alle Daten müssen gemäß der oben genannten Vorschrift in eine URL verpackt werden: String url = ”j d b c : db2 : / / d b s e r v e r : 1 2 3 4 / P e r s o n a l : u s e r=j o e ; p a s s w o r d=f o o ; ” ; C o n n e c t i o n conn = DriverManager . getConnection ( u r l ) ; • DriverManager.getConnection(String url, Properties info): Der Methode wird zusätzlich ein assoziatives Array info übergeben, welches die Parameter aufnehmen kann, die normalerweise nach dem Fragezeichen der URL stehen würden. Sinnvoll ist diese Trennung, wenn diese Parameter dynamisch generiert werden. Dann entfällt die umständliche Kodierung der URL: 6 SS 2007, Gunar Fiedler, ISE@CAU Datenbankzugriff über JDBC 3 Die Phasen im Detail P r o p e r t i e s p r o p s = new P r o p e r t i e s ( ) ; p r o p s . pu t ( ”u s e r ” , ”j o e ” ) ; p r o p s . pu t ( ”p a s s w o r d ” , ”f o o ” ) ; S t r i n g u r l = ”j d b c : db2 : / / d b s e r v e r : 1 2 3 4 / P e r s o n a l ” ; C o n n e c t i o n conn = DriverManager . getConnection ( url , props ) ; • DriverManager.getConnection(String url, String user, String password): Analog zur 2. Variante, falls als Parameter nur Nutzername und Passwort benötigt werden: String url = ”j d b c : db2 : / / d b s e r v e r : 1 2 3 4 / P e r s o n a l ” ; C o n n e c t i o n conn = D r i v e r M a n a g e r . g e t C o n n e c t i o n ( u r l , ”j o e ” , ”f o o ” ) ; Nach Abschluss dieser Phase besteht eine Kommunikationsverbindung zwischen der Anwendung und der Datenbank: .. ...... ...... ...... ...... .. ...... ...... ...... ...... Applikationslogik .. ...... ...... ...... ...... .......................................................................................................................................... .... ......... ......... ......... .... ......... ......... ......... DB2-Treiber .... ......... ......... ......... qqq qqqq qqqqq qqqq .......................................................................................................................................... ................................... ................. ......... ........ ...... ...... .... .... ... ..... .... ... ...... .... ......... ....... . . . . . . . .................. . ................................ qqqq DB2 ...... ..... ...... ...... ....... ....... ........... ................................... 3.3 Datasources In realen Umgebungen ist es normalerweise notwendig, den Verbindungsaufbau weiter zu abstrahieren. In der URL sind z.B. der Datenbankserver und der Name der Datenbank vermerkt. Eine Anwendung wird i.d.R. in verschiedenen Umgebungen eingesetzt, und es kann relativ häufig passieren, dass sich die Konfigurationsparameter der Datenbank ändern. Außerdem ist vielleicht eine zentrale Konfiguration SS 2007, Gunar Fiedler, ISE@CAU 7 3 Die Phasen im Detail Datenbankzugriff über JDBC für verschiedene Anwendungen sinnvoll, die alle parallel auf ein Datenbanksystem zugreifen möchten. Seit der JDBC-Version 2.0 existiert ein neues Interface namens javax.sql.DataSource, welches sich dieses Problems annimmt. DataSource-Objekte kapseln eine Datenquelle. Über die Methode getConnection des DataSource-Interfaces kann man sich eine vorkonfigurierte Verbindung zu dieser Datenquelle geben lassen. DataSources spielen ihre Vorteile insbesondere in J2EE-Umgebungen aus. Dort übernimmt der Anwendungsserver die zentrale Verwaltung aller bekannten Datenquellen. Über einen Verzeichnisdienst (JNDI, Gelbe Seiten“) kann eine Anwendung auf eine Datenquelle ” des Anwendungsservers über einen Namen zugreifen. Wenn sich die physischen Parameter der Datenquelle ändern (z.B. wenn die Datenbank auf einen neuen Rechner umzieht), dann muss diese Änderung nur zentral im Anwendungsserver durchgeführt werden. Die Benutzung von Datenquellen hat einen weiteren entscheidenden Vorteil: das Öffnen einer Verbindung ist ein relativ kostenintensiver Prozess, der einige Sekunden dauern kann. Die zentrale Verwaltung von Datenbankverbindungen erlaubt die Implementierung des Pooling“-Patterns: Die Datenquelle verwaltet einen Pool von ” offenen Verbindungen. Wenn eine Anwendung eine Verbindung anfordert, dann wird keine neue Verbindung geöffnet, sondern sie bekommt eine der offenen Verbindungen zugeteilt, die sich im Pool befinden. Wenn die Anwendung die Verbindung schließt, dann wird die Verbindung wieder als frei“ markiert. Durch diesen Mechanismus, ” der einigen vielleicht aus dem Kontext von Prozessen und Threads bekannt ist, wird eine erhebliche Performance-Steigerung erreicht. 3.4 Erzeugen eines Statements SQL-Anfragen werden unter JDBC in Statement-Objekte eingekapselt. Bevor eine Anfrage ausgeführt werden kann, muss ein derartiges Objekt erzeugt werden. Dies geschieht durch Aufruf der createStatement-Methode des im vorherigen Schritt erzeugten Connection-Objekts: S t a t e m e n t stmt = conn . c r e a t e S t a t e m e n t ( ) ; 8 SS 2007, Gunar Fiedler, ISE@CAU Datenbankzugriff über JDBC 3 Die Phasen im Detail .. ...... ...... ...... ...... .. ...... ...... ...... ...... Applikationslogik .. ...... ...... ...... ...... .......................................................................................................................................... ... ......... ......... ......... ... ......... ......... ......... DB2-Treiber ................................................. .............. ........ ....... ..... ..... .. .. .... ....... ............. .................................................... qqqqqqqqqqqqqq................ stmt ... ......... ......... ......... qqq qqq qqqqq qqqq .......................................................................................................................................... .. ........................ ................................ ...... ......... ..... ...... .... ... ..... . .... ... ...... .... ......... ...... . . . . . . . . .................. ................................. qqqq DB2 ...... ...... ...... ...... ....... ....... ........... ................................... Neben einfachen SQL-Anfragen, wie wir sie hier betrachten, können über StatementObjekte auch spezielle vorcompilierte Anfragen (prepared statements) und stored procedures ausgeführt werden. Außerdem lässt sich über die Statement-Objekte die Rückgabe des Ergebnisses steuern. Zu beachten ist: ein einzelnes Statement kann nicht mehrere SQL-Anfragen gleichzeitig verwalten. Ergebnisse müssen verarbeitet werden, bevor mit diesem Statement-Objekt eine neue Anfrage gestellt wird. 3.5 Ausführen eines Statements Das Statement-Objekt stellt Methoden zum Ausführen von SQL-Anweisungen bereit. Aufgrund der unterschiedlichen Bedürfnisse werden zwei verschiedene Anweisungsarten unterschieden: Anfragen und Updates. Anfragen sind SQL-Anweisungen, die ein Ergebnis zurückliefern, typischerweise select - Anweisungen. Sie werden über die executeQuery-Methode des StatementObjekts ausgeführt, diese Methode nimmt die Anfrage als Parameter in Textform entgegen und liefert das Ergebnis in einem ResultSet-Objekt zurück: ResultSet re s ult = s t m t . e x e c u t e Q u e r y ( ”SELECT ∗ FROM s t a f f ” ) ; SS 2007, Gunar Fiedler, ISE@CAU 9 3 Die Phasen im Detail Datenbankzugriff über JDBC .. ...... ...... ...... ...... .. ...... ...... ...... ...... Applikationslogik .... ...... ...... ...... ..... pp p p p p ppppppppppp p pp p p p p p p p p p ppp p p p p p ppp pp p p p . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .p p.pp p. . . . . . . . . . .pp.p . . . . . . . . . . . . . . . . . . . . . . . . . . . . ................................... p p pp pp p ........ pp ................. ...... pp p ........ ppp ..... ... p p p ..... p result ... pp pp ..... .... p . ........ . . p . . . pppp pppp ... ................ ...................................... ppp pppp p p ppp p pppppp ... ... ......... ......... ......... ......... ............................................................... ......... ......... .......... ...... ...... .. ..... qqqqqqqqqqqqqq............ stmt ............... . .............. ...........................p.p..................... DB2-Treiber p p p ... ......... ppppppp pppppp ......... ......... pp pp p qqq pp ppp ppp pp ppp ppp p pp pp ppp pp p p . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .qq. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .pp. . . . . . . . . q p ppp q q p ppp pp p qqq qqqq pp pp p ... p pp p p .... ... .. .............................................. pp p pp ............ ........ p ........ p ..... p . . . p p . ... qqqq ... pp .. ..... ppp p ... .... pp p p p .... ...... p pp p p ...... ......... pp p p p p ......... .................. p p pp p p p p ................................. p p p p p pp p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p pp p p p pp p p p p p p p p p p p p p p p p pp p p p p p p pp p p ppppppppp pppppp ppppp pppppp ppppppppppppp pppppppppppppppppppppppppppppppppp DB2 SELECT * FROM staff ...... ...... ...... ...... ....... ....... ........... ................................... Updates entsprechen den drei DML-Anweisungen insert, update und delete. Sie werden über die executeUpdate-Methode des Statement-Objekts ausgeführt. Diese Methode liefert als Ergebnis die Anzahl der durch das Update betroffenen Tupel zurück, vorausgesetzt der Treiber sowie das DBMS unterstützen dies: stm t . e x e c u t e U p d a t e ( ”UPDATE s t a f f SET s a l a r y =3000 WHERE name=’ Joe Hacker ’ ” ) ; ... ...... ...... .... ...... ...... Applikationslogik .... ..... ...... ..... ..... .... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ........ . . . . . . . . . . . . . . . . ... ... ... ... .. ....... . . . ......... ......... ........................ . . . . . . . . . . . . . ......... ......... . . . ...... ..... .. ..... ........ stmt ......... ................................... DB2-Treiber ... . . . . . ... . . . . .. ......... ... . .. ... ... ... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ...... . . . . . . . . . . . . . . . .. .. .. ... ... ....... .... ................ ..................... .... ...... ... . . . . ..... . . ..... ...... ... ...... ............ ...... ........ ............................ ......... ............ ....................................... ppppppppp ppp ppp ppppp pp DB2 ...... .. .......... ....... ............................. UPDATE staff SET salary=3000 WHERE name=’Joe Hacker’ 3.6 Auswertung der Ergebnisse Wurde eine Anfrage ausgeführt, muss in dieser Phase das zurückgelieferte ResultSet ausgewertet werden. Dies geschieht nach dem Iterator-Pattern. Das Objekt besitzt einen internen Zeiger auf das jeweils aktuelle Tupel der Ergebnismenge. Über die Methode next() wird dieser Zeiger auf das jeweils nächste Tupel gesetzt. Ist kein Tupel mehr vorhanden, so liefert die next-Methode den boolschen Wert false zurück. Zu Beginn steht der Zeiger vor dem ersten Tupel. 10 SS 2007, Gunar Fiedler, ISE@CAU Datenbankzugriff über JDBC 4 Ein zusammenhängendes Beispiel Auf die einzelnen Attribute des aktuellen Tupels kann über die getXXX-Methoden des ResultSet-Objekts zugegriffen werden. XXX steht dabei für die Namen der Standarddatentypen, z.B. String oder Double. Diese Methoden erwarten entweder den Index des Attributs (Achtung: die Zählung beginnt bei 1!) oder dessen Namen. Es wird vorausgesetzt, dass der Typ des jeweiligen Attributs bekannt ist. while ( r e s u l t . next () ) { System . o u t . p r i n t l n ( r e s u l t . g e t S t r i n g ( ”name ”)+ ” : ”+ Double . t o S t r i n g ( r e s u l t . g e t D o u b l e ( ” s a l a r y ” ) ) ) ; } 3.7 Schließen der Verbindung Wird eine Datenbank-Verbindung nicht mehr benötigt, kann sie durch einen Aufruf der close-Methode des Connection-Objekts geschlossen werden, um Systemressourcen freizugeben. Gleiches gilt für Resultsets und Statements: result . close (); stmt . c l o s e ( ) ; conn . c l o s e ( ) ; .. ...... ...... ...... ...... . ...... ...... ...... ...... Applikationslogik . ...... ...... ...... ...... .......................................................................................................................................... .... ......... ......... ......... .... ......... ......... ......... DB2-Treiber ... ......... ......... ......... .......................................................................................................................................... ................................... ................. ......... ........ ...... ...... .... .... ... ..... . .... ... ...... ..... ......... ....... . . . . . .................. . . . . . ............................ DB2 ...... . ...... ..... ....... ...... ........... ....... .................................. Man sollte sich angewöhnen, jedes Resultset, jedes Statement und jede Verbindung explizit zu schließen und sich nicht auf den Garbage-Collector der JVM verlassen. 4 Ein zusammenhängendes Beispiel Nachfolgend sind die Codebeispiele des letzten Abschnitts nochmal in einem Block zusammengefasst: SS 2007, Gunar Fiedler, ISE@CAU 11 4 Ein zusammenhängendes Beispiel Datenbankzugriff über JDBC // I m p o r t d e r I n t e r f a c e s d e s j a v a . s q l −P a c k a g e s import j a v a . s q l . ∗ ; import j a v a . u t i l . P r o p e r t i e s ; p u b l i c c l a s s JDBCMain { p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { try { // Laden d e s DB2−T r e i b e r s C l a s s . forName ( ”com . ibm . db2 . j c c . DB2Driver ” ) . n e w I n s t a n c e ( ) ; } catch ( C l a s s N o t F o u n d E x c e p t i o n e ) { e . printStackTrace ( ) ; System . e r r . p r i n t l n ( ”T r e i b e r n i c h t g e f u n d e n ! ” ) ; System . e x i t ( −1); } try { S t r i n g u r l = ”j d b c : db2 : / / d b s e r v e r : 1 2 3 4 / P e r s o n a l ” ; // O e f f n e n d e r V e r b i n d u n g zum DBMS // Nutzername und P a s s w o r t s o l l t e n n a t u e r l i c h // vom N u t z e r a b g e f r a g t werden ! C o n n e c t i o n conn = D r i v e r M a n a g e r . g e t C o n n e c t i o n ( u r l , ”j o e ” , ”f o o ” ) ; // E r z e u g e n e i n e s S t a t e m e n t s S t a t e m e n t stmt=conn . c r e a t e S t a t e m e n t ( ) ; // A u s f u e h r e n e i n e s U p d a t e s String sql = ”UPDATE s t a f f SET s a l a r y =3000 WHERE name=’ Joe Hacker ’ ” ; stm t . e x e c u t e U p d a t e ( s q l ) ; // A u s f u e h r e n e i n e r A n f r a g e s q l = ”SELECT ∗ FROM s t a f f ” ; R e s u l t S e t r e s u l t = s tm t . e x e c u t e Q u e r y ( s q l ) ; // A u s w e r t e n d e r A n f r a g e −E r g e b n i s s e while ( r e s u l t . next () ) { System . o u t . p r i n t l n ( r e s u l t . g e t S t r i n g ( ”name ”)+ ” : ”+ Double . t o S t r i n g ( r e s u l t . g e t D o u b l e ( ” s a l a r y ” ) ) ) ; } result . close (); 12 SS 2007, Gunar Fiedler, ISE@CAU Datenbankzugriff über JDBC 5 Weitergehende Möglichkeiten mit JDBC s tmt . c l o s e ( ) ; // Beenden d e r V e r b i n d u n g conn . c l o s e ( ) ; } catch ( E x c e p t i o n e ) { // Ausgabe d e r F e h l e r m e l d u n g e . printStackTrace () ; System . e x i t ( −1); } } } 5 Weitergehende Möglichkeiten mit JDBC Die Möglichkeiten von JDBC gehen weit über die hier vorgestellten Grundlagen hinaus. Dieser Abschnitt stellt als Beispiele einige fortgeschrittene Techniken vor: Abfrage der Schema-Information, die Bearbeitung von Ergebnismengen und Transaktionen. 5.1 Nutzung von Metadaten Bisher wurde vorausgesetzt, dass der Programmierer das Schema der Datenbank genau kennt. Wenn z.B. der Typ eines Attributs eines Tupels der Ergebnismenge unbekannt ist, weiß der Entwickler nicht, welche getXXX-Methode zu benutzen ist. Diese Schema-Informationen (Metadaten) sind allerdings im Data Dictionary der Datenbank gespeichert und können über JDBC abgefragt werden, sofern der Treiber und das DBMS dies unterstützen. Metadaten stehen an zwei Stellen zur Verfügung: • Connection.getMetaData liefert ein DatabaseMetaData - Objekt zurück, das Informationen über die Datenbank im Allgemeinen enthält, d.h. die Tabellenstruktur, die gespeicherten Prozeduren, den verwendeten SQL-Dialekt, etc. • ResultSet.getMetaData liefert ein ResultSetMetaData - Objekt zurück, das Informationen über die Struktur einer Ergebnismenge enthält, also z.B. Namen und Typen der Attribute. Das folgende Code-Fragment (aufbauend auf der oben genutzten Personal-Datenbank) analysiert die Attribut-Struktur der Ergebnismenge einer Anfrage: SS 2007, Gunar Fiedler, ISE@CAU 13 5 Weitergehende Möglichkeiten mit JDBC Datenbankzugriff über JDBC /∗ . . . ∗/ // A n f r a g e a u s f u e h r e n R e s u l t S e t r e s u l t = stm t . e x e c u t e Q u e r y ( ”SELECT ∗ FROM s t a f f ” ) ; // Metadaten a u s l e s e n R e s u l t S e t M e t a D a t a md = r e s u l t . getMetaData ( ) ; // Ausgabe a l l e r A t t r i b u t n a m e n // ACHTUNG: d a s e r s t e A t t r i b u t h a t d i e P o s i t i o n 1 ! f o r ( i n t i d x =1; i d x <=md . getColumnCount ( ) ; i d x ++) { System . o u t . p r i n t l n (md . getColumnName ( i d x ) ) ; } /∗ . . . ∗/ 5.2 Verarbeitung von Ergebnismengen Ein normales ResultSet-Objekt hat zwei einschränkende Eigenschaften: • Die Werte können nicht verändert werden. • Sie besitzen ein sogenanntes forward-only-Cursor1 , d.h. die Menge kann nur genau einmal vom ersten bis zum letzten Element durchlaufen werden. Seit JDBC Version 2.0 existieren scrollbare ResultSets. Mittels diverser Navigationsmethoden kann der Zeiger innerhalb der Menge beliebig gesetzt werden: auf absolute Positionen, vorwärts oder rückwärts. Außerdem ist es wünschenswert, wenn man die Daten eines ResultSets nachbearbeiten kann. Zu diesem Zweck existieren änderbare ResultSet-Objekte mit updateXXXMethoden. Der Typ eines ResultSets wird durch das Statement bestimmt, in dessen Kontext es erzeugt wird. Ein Statement, das mittels conn.createStatement() erzeugt wurde, generiert stets nicht veränderbare und nicht scrollbare ResultSets. Werden der Methode zwei Parameter übergeben, lässt sich dieses Verhalten ändern: S t a t e m e n t stmt = conn . c r e a t e S t a t e m e n t ( i n t r e s u l t S e t T y p e , int resultSetConcurrency ); Der Parameter resultSetType kann folgende Werte annehmen: 1 Cursor steht hier für current set of records. 14 SS 2007, Gunar Fiedler, ISE@CAU Datenbankzugriff über JDBC 5 Weitergehende Möglichkeiten mit JDBC resultSetType ResultSet.TYPE_FORWARD_ONLY ResultSet.TYPE_SCROLL_INSENSITIVE ResultSet.TYPE_SCROLL_SENSITIVE Bedeutung Das ResultSet kann nur einmal vom ersten bis zum letzten Element durchlaufen werden. Das ResultSet ist scrollbar, simultane Änderungen anderer Nutzer bleiben verborgen Das ResultSet ist scrollbar, Änderungen anderer Nutzer schlagen auf die Werte durch. Für resultSetConcurrency gibt es diese Möglichkeiten: resultSetConcurrency ResultSet.CONCUR_READ_ONLY ResultSet.CONCUR_UPDATABLE Bedeutung Das Ergebnis kann nicht verändert werden. Das Ergebnis kann editiert werden. Das folgende Code-Fragment erzeugt ein Statement für scrollbare und änderbare ResultSets: /∗ . . . ∗/ S t a t e m e n t stmt = conn . c r e a t e S t a t e m e n t ( R e s u l t S e t . TYPE SCROLL INSENSITIVE , R e s u l t S e t . CONCUR UPDATABLE ) ; /∗ . . . ∗/ // a u s f u e h r e n e i n e r A n f r a g e ResultSet re s ult = s t m t . e x e c u t e Q u e r y ( ”SELECT ∗ FROM s t a f f ” ) ; // s e t z t den Z e i g e r a u f P o s i t i o n 2 result . absolute (2); // s e t z t den Z e i g e r a u f d a s e r s t e T u p e l result . first (); // a e n d e r t d a s G e h a l t r e s u l t . updateDouble ( ”s a l a r y ” ,2000); 5.3 Transaktionen Die JDBC-API unterstützt die Nutzung von Transaktionen. Standardmäßig ist definiert, dass jede SQL-Anweisung, die per executeQuery oder executeUpdate ausgeführt wird, einer Transaktion entspricht. Dieses Verhalten lässt sich über die setAutoCommit-Methode des Connection-Objekts ändern. Durch den Parameter SS 2007, Gunar Fiedler, ISE@CAU 15 6 Weiterführende Lektüre Datenbankzugriff über JDBC false wird nach Ausführung von executeXXX-Methoden ein expliziter Aufruf von Connection.commit bzw. Connection.rollback erwartet: /∗ . . . ∗/ // e i n e T r a n s a k t i o n f u e r s i c h stm t . e x e c u t e U p d a t e ( ”UPDATE s t a f f SET s a l a r y =3000 WHERE name=’ Joe Hacker ’ ” ) ; conn . setAutoCommit ( f a l s e ) ; /∗ ∗∗∗ ∗ Beginn der T r a n s a k t i o n ∗∗∗ ∗/ stm t . e x e c u t e U p d a t e ( ”DELETE FROM s t a f f WHERE name=’ A l f Weise ’ ” ) ; stm t . e x e c u t e U p d a t e ( ”INSERT INTO s t a f f ( name , s a l a r y ) VALUES ( ’ Tina K u n t e r b u n t ’ , 1 0 0 0 ) ” ) ; // Commit conn . commit ( ) ; /∗ ∗∗∗ ∗ Ende d e r T r a n s a k t i o n ∗∗∗ ∗/ /∗ ∗∗∗ ∗ B e g i n n e i n e r neuen T r a n s a k t i o n ∗∗∗ ∗/ stm t . e x e c u t e U p d a t e ( ”UPDATE s t a f f SET s a l a r y =2000 WHERE name=’ Tina K u n t e r b u n t ’ ” ) ; // R o l l b a c k conn . r o l l b a c k ( ) ; /∗ ∗∗∗ ∗ Ende d e r T r a n s a k t i o n ∗∗∗ ∗/ /∗ . . . ∗/ Hinweis: Das zugrunde liegende DBMS muss natürlich Transaktionen unterstützen! 6 Weiterführende Lektüre Im WWW sind eine Vielzahl von Dokumenten über den Gebrauch von JDBC verfügbar, z.B.: 16 SS 2007, Gunar Fiedler, ISE@CAU Datenbankzugriff über JDBC 6 Weiterführende Lektüre • Java-API Dokumentation: http://java.sun.com/products/jdk/1.4.2/docs/api/index.html • JDBC-Tutorial von Sun: http://java.sun.com/docs/books/tutorial/jdbc/ • ein weiteres Sun-Tutorial: http://developer.java.sun.com/developer/Books/JDBCTutorial/ • Kurzübersicht: http://www.cs.unc.edu/Courses/wwwp-s98/members/thornett/jdbc/ • Code-Beispiele für Java allgemein: http://www.exampledepot.com/ • ... SS 2007, Gunar Fiedler, ISE@CAU 17