Programmieren / Java Datenbankzugriff mit JDBC Frage Nr. 13 Datenbankzugriff mit JDBC Ausarbeitung einer Maturafrage aus dem Fach P r o g r a m m i e r e n / Andreas Hechenblaickner 5CDH | HTBLA Kaindorf/Sulm Dipl.-Ing. Manfred Wilfling Erstelldatum: 19. Februar 2003 Letzte Überarbeitung: 21. Mai 2003 J a v a PUC | Datenbankzugriff mit JDBC Inhaltsverzeichnis Inhaltsverzeichnis Inhaltsverzeichnis 2 1 Datenbankzugriff mit JDBC 3 1.1 JDBC: der Zugriff auf Datenbanken mit Java 3 1.2 Datenbanktreiber für den Zugriff 3 2 Datenbanken und ihre Treiber 5 2.1 Interbase 5 2.2 Die freie Datenbank MySQL 5 2.3 Oracle Enterprise Edition 6 3 Datenbankverbindung herstellen 7 3.1 Den Treiber laden 7 3.2 Verbindung zur Datenbank 7 4 Datenbankabfragen 9 4.1 SQL-Anweisungen ausführen 9 4.2 Ergebnisse einer Abfrage in ResultSet 9 4.3 Datenbankänderungen 11 4.4 Die Klasse SQLException 11 4.5 Vorbereitete Anweisungen – Prepared Statements 12 4.6 Transaktionen 13 Folienvorschlag 14 Zusammenfassung 15 Quellen 15 HTBLA Kaindorf/Sulm Seite 2 von 15 PUC | Datenbankzugriff mit JDBC 1 Datenbankzugriff mit JDBC Datenbankzugriff mit JDBC Datenbankverwaltungssysteme (DBMS, engl. Database Management Systems) arbeiten auf einer Datenbasis (Tabellen) die miteinander in Beziehung stehen. Die Programme, die die Datenbasis kontrollieren, bilden die zweite Hälfe der DBMS. 1.1 JDBC: der Zugriff auf Datenbanken mit Java JDBC ist die Abkürzung für Java Database Connectivity und bezeichnet einen Satz von Klassen und Methoden, um relationale Datenbanksysteme von Java zu nutzen. Mit der JDBC-API und den JDBC-Treibern wird eine wirksame Abstraktion von Datenbanken erreicht, sodass durch die einheitliche Programmierschnittstelle die Funktionen differierender Datenbanken in gleicher Weise genutzt werden können. Das Lernen von verschiedenen Zugriffsmethoden für unterschiedliche Datenbanken der Hersteller entfällt. Jede Datenbank hat ihr eigenes Protokoll aber die Implementierung ist nur dem Datenbanktreiber bekannt. Folgende Schritte sind für den Zugriff auf eine relationale Datenbank mit JDBC erforderlich: 1. Installieren der JDBC-Datenbanktreiber. 2. Eine Verbindung zur Datenbank über den entsprechenden JDBC-Treiber für das verwendete DBMS aufbauen. 3. Eine SQL-Anweisung erzeugen. 4. Ausführen der SQL-Anweisung. 5. Das Ergebnis der Anweisung holen. 6. Schließen der Datenbankverbindung. 1.2 Datenbanktreiber für den Zugriff Damit wir JDBC nutzen können, benötigen wir einen passenden Treiber für die Datenbank. Es gibt vier Treiber-Kategorien: 1. JDBC-ODBC-Bridge-Treiber Eine JDBC-ODBC-Brücke wandelt die Aufrufe von JDBC in ODBC-Aufrufe der Client-Seite um. Die Methoden sind nativ und daher nicht plattformunabhängig. 2. Native-API Java Driver Diese Treiber übersetzen die JDBC-Aufrufe direkt in Aufrufe der DatenbankAPI. Die Methoden sind ebenfalls nativ. HTBLA Kaindorf/Sulm Seite 3 von 15 PUC | Datenbankzugriff mit JDBC Datenbankzugriff mit JDBC 3. Netz-Protokoll All-Java Driver Hier wird ein in Java programmierter Treiber genutzt, der beim Datenbankzugriff auf den Client geladen wird. Der Treiber kommuniziert nicht direkt mit der Datenbank, sondern mit einer Middleware. 4. Native Protocol All-Java Driver Diese Treiber sind vollständig in Java programmiert und kommunizieren direkt mit dem Datenbankserver. Ein Kriterium ist, ob sie vollständig in Java implementiert und daher portabel sind (Typ 3 und 4) oder plattformabhängigen Programmcode beinhalten (Typ 1 und 2). Applets können mit diesen plattformabhängigen Treibern nichts anfangen. HTBLA Kaindorf/Sulm Seite 4 von 15 PUC | Datenbankzugriff mit JDBC 2 Datenbanken und ihre Treiber Datenbanken und ihre Treiber Wir wollen uns im Folgenden mit einigen Datenbanken und ihren Treibern beschäftigen, die für den Zugriff unter Java geeignet sind. Diese Auflistung dient nur zur Übersicht und ist keinesfalls vollständig. 2.1 Interbase Die Datenbank Interbase 6.0 (http://www.interbase.com) von Borland implementiert viele Anforderungen aus dem ANSI-SQL-92. Interbase bietet gespeicherte Prozeduren oder Trigger und ist eine ausgewachsene performante Lösung. Borland stellt den Quellcode sowie die ausführbaren Dateien für Windows, Novell Netware und verschiedene UNIX-Versionen wie Linux und Solaris zur Verfügung. Interbase kann auf dem Rechner leicht installiert werden und benötigt wenig Hauptspeicher. Firebird (http://firebird.sourceforge.net) ist die Open-Source-Version von Interbase. Der Lizenz-Unterschied zu Interbase wird in den FAQs erklärt. JDBC-Treiber für Interbase Für Java gibt es einen Java-Datenbanktreiber mit dem Namen InterClient (http://firebird.sourceforge.net) der eine hundertprozentige Java-Implementierung ist. 2.2 Die freie Datenbank MySQL MySQL (http://www.mysql.com) ist ein häufig eingesetzter freier und schneller Open-Source-Datenbankserver. Er wird oft im Internet in Zusammenhang mit dynamischen Webseiten eingesetzt; das Zusammenspiel Linux, Apache, MySQL, PHP (LAMP-System) ist hoch gelobt. Eine herausragende Eigenschaft ist Geschwindigkeit und Bedienbarkeit, die teilweise darauf beruht, dass MySQL auf Transaktionen verzichtet. Seit der Version 4 kann eine alternative Datenbank-Engine aber auch Transaktionen ausführen. JDBC-Treiber für MySQL MySQL kann einfach über den stehenden Typ-4 JDBC-Treiber Connector/J bedient werden (http://www.mysql.com). Nach dem Entpacken muss das Treiber.jar-Archiv noch im Klassenpfad aufgenommen werden. HTBLA Kaindorf/Sulm Seite 5 von 15 PUC | Datenbankzugriff mit JDBC 2.3 Datenbanken und ihre Treiber Oracle Enterprise Edition Einer der bekanntesten Datenbanken stammt von Oracle. Um die Verbreitung weiter zu erhöhen, ist die Firma dazu übergegangen, eine vollwertige Version zum Download oder als CD freizugeben. Wer das Download nicht scheut, der kann unter http://otn.oracle.com/software/content.html die Version für Windows, Linux und weitere Unixe herunterladen. Weiters ist auch ein JDBCTreiber für den Datenbankzugriff aus Java-Programmen erhältlich. HTBLA Kaindorf/Sulm Seite 6 von 15 PUC | Datenbankzugriff mit JDBC 3 Datenbankverbindung herstellen Datenbankverbindung herstellen Die Verbindung zu einer Datenbank wird über die Klasse DriverManager und die Schnittstelle Connection aufgebaut. Alle verwendeten Pakete liegen unter java.sql.* und müssen zunächst importiert werden. 1 import java.sql.*; Vor der Ausführung der JDBC-Befehle muss ein passender Datenbanktreiber geladen werden. 3.1 Den Treiber laden Der Datenbanktreiber ist eine ganz normale Java-Klasse, die sich bei einem Treibermanager automatisch anmeldet. Unsere Aufgabe ist es nur, ein TreiberObjekt einmal zu erzeugen. Um eine Klasse zur Laufzeit zu laden und so ein Laufzeit-Objekt zu erstellen, gibt es mehrere Möglichkeiten. Eine davon nutzt die statische Methode forName() der Klasse Class. Die Syntax für das Laden der JDBC-ODBC-Bridge lautet somit: 1 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); Um möglichst unabhängig zu bleiben, sollte die Klasse auch nicht hart einkodiert werden. Besser ist es, den Klassennamen in eine Property zu schreiben. Da wir die Klasse nur laden, aber die Referenz auf den Klassen-Deskriptor nicht benötigen, belassen wir es bei einem Aufruf und beachten den Rückgabewert nicht. Diese Operation löst eine ClassNotFoundException aus, falls die Klasse nicht gefunden wurde, der Treiber also nicht geladen werden konnte. Datenbank 3.2 Borland Interbase Klassenname für den JDBC-Treiber interbase.interclient.Driver MySQL org.gjt.mm.mysql.Driver Oracle oracle.jdbc.driver.OracleDriver PostgreSQL org.postgresql.Driver IBM DB2 com.ibm.db2.jdbc.net.DB2Driver ODBC-Datenquellen sun.jdbc.odbc.JdbcOdbcDriver Verbindung zur Datenbank Nach dem Laden des Treibers können wir eine Verbindung zur Datenbank mit Hilfe des Connection-Objekts aufbauen, welches von DriverManager.getConnection() zurückgegeben wird. Eine Verbindung wird mit speziellen Optionen parametrisiert, unter anderem mit dem Treiber, der die Datenquelle anspricht. Alle Datenquellen sind durch eine besondere URL qualifiziert, die folgendes Format besitzt: jdbc:Subprotokoll:Datenquellenname HTBLA Kaindorf/Sulm Seite 7 von 15 PUC | Datenbankzugriff mit JDBC Datenbank Datenbankverbindung herstellen Borland Interbase Subprotokoll interbase Beispiel jdbc:interbase://host/database.gdb MySQL mysql jdbc:mysql://host:3306/database Oracle oracle:thin jdbc:oracle:thin:@:1243:database PostgreSQL postgresql jdbc:postgresql://host:5432/database IBM DB2 db2 jdbc:db2://database ODBCDatenquellen odbc jdbc:odbc:database Verbindung aufbauen Die getConnection()-Methode liefert nun ein Connection-Objekt, das mit der Quelle verbunden ist. Die nachfolgende Anweisung verbindet uns mit einer Datenbank, die den Namen scotttiger.gdb trägt. Diesen Namen haben wir im Datenquellen-Administrator festgelegt und er hat nichts mit dem Dateinamen zu tun. 1 Connection connection = DriverManager.getConnection( 2 "jdbc:interbase://localhost/scotttiger.gdb", 3 "sysdba", "masterkey"); Die Methode getConnection() erwartet bis zu drei Parameter: Die URL der Datenbank, zu der die Verbindung aufgenommen werden soll, ist der Pflichtparameter. Der Anmeldename und das Passwort sind optional. Meldet getConnection() keinen Fehler, so liefert sie uns eine geöffnete Datenbankverbindung. Verbindung beenden Um die Datenbankverbindung zu beenden genügt ein Aufruf von: 1 connection.close(); HTBLA Kaindorf/Sulm Seite 8 von 15 PUC | Datenbankzugriff mit JDBC 4 Datenbankabfragen Datenbankabfragen Mit einer gelungenen Verbindung lassen sich nun SQL-Kommandos absetzen, und die Datenbank kann gesteuert werden. 4.1 SQL-Anweisungen ausführen Für SELECT-Abfragen ist ein Statement-Objekt anzulegen. JDBC bietet dazu die Methode createStatement() an, die eine SQLException auslösen kann. Dies ist eine Methode des Connection-Objekts. 1 Statement stmt = connction.createStatement(); Um Informationen auszulesen, benutzen wir die SELECT-Befehle aus SQL und geben sie durch die executeQuery()-Methode der Statement-Schnittstelle an. Der Aufruf liefert uns die Ergebnisse als Zeilen in Form eines ResultSetObjekts. Wir benutzen executeQuery() für Abfragen und executeUpdate() bei Update-, Insert- oder Delete-Operationen. Wieder dürfen wir das Auffangen von SQLException nicht vergessen. 1 String query = "SELECT * FROM Tabellenname"; 2 ResultSet rs = stmt.executeQuery(query); An dieser Stelle sei noch einmal darauf hingewiesen, dass JDBC nicht in die Zeichenketten hineinsieht, die es an den Treiber weiterleitet. Sind die SQLAnfragen also falsch, lassen sich Fehler schwer finden. So kann zum Beispiel schon die falsche Groß- beziehungsweise Kleinschreibung zu Fehlern in der Datenbank führen. Solche Fehler sind natürlich schwer zu entdecken. Daher bietet es sich an, zum Testen erst die Kommandos auf der Konsole auszugeben. Insbesondere bei zusammengesetzten Ausdrücken finden sich dann die Fehler. 4.2 Ergebnisse einer Abfrage in ResultSet Das Ergebnis einer Abfrage durch executeQuery() wird in einer Ergebnistabelle vom Typ ResultSet zurückgegeben. Mit Methoden von ResultSet lassen sich die unterschiedlichen Spalten ansprechen und die Zeilen auswerten. Das Interface ResultSet bietet für jeden Datentyp eine entsprechende Methode getXXX() an – XXX ist der Datentyp. Da alle Spalten zusätzlich als String ausgelesen werden können, ist es immer möglich, einfach getString() zu verwenden. HTBLA Kaindorf/Sulm Seite 9 von 15 PUC | Datenbankzugriff mit JDBC Datenbankabfragen getXXX-Methode getInt() Rückgabewert int SQL-Typ getLong() long BIG INT getFloat() float REAL getDouble() double FLOAT getBigDecimal() BigDecimal NUMBER getBoolean() boolean BIT getString() String VARCHAR, CHAR getAsciiStream() InputStream LONGVARCHAR getDate() Date DATE getTime() Time TIME getTimestamp() Timestamp TIME STAMP getObject() Object jeder Typ INTEGER Auswertung Um das ResultSet auszuwerten, müssen wir zunächst in die erste Zeile springen. Dies geschieht mit der next()-Methode von ResultSet. Danach sind wir mit getXXX() in der Lage, die Spalten dieser Zeile auszuwerten. Um weitere Zeilen zu erhalten, nutzen wir wieder next(). Die Methode gibt false zurück, falls es keine neue Zeile mehr gibt. Die Abfragen befinden sich somit oft in einer whileSchleife. 1 while (rs.next()) { 2 System.out.print(rs.getString("bezeichnung") + "\t" + 3 rs.getInt("preis")); 4 } Wird der Methode getXXX() ein String übergeben, so bestimmt dieser über den Namen der Spalte. Alternativ kann auch ein numerischer Parameter übergeben werden, der besagt die wievielte Spalte anzusprechen ist. wasNull() bei ResultSet Soll festgestellt werden, ob eine Spalte den Wert NULL hatte, so kann das nach dem Aufruf der get-Methode durch Aufruf von wasNull() abgefragt werden. wasNull() gibt genau dann true zurück, wenn die letzte abgefragte Spalte einen NULL-Wert als Inhalt hatte. Bei allen Spalten, die NULL-Werte enthalten können, muss diese Abfrage also erfolgen. Bei den get-Methoden, die ein Objekt als Ergebniswert haben, geht es etwas einfacher. Hier wird null zurückgegeben, wenn der Spaltenwert NULL war. HTBLA Kaindorf/Sulm Seite 10 von 15 PUC | Datenbankzugriff mit JDBC 4.3 Datenbankabfragen Datenbankänderungen Datenbankänderungen werden mit den SQL-Anweisungen INSERT INTO, UPDATE oder DELETE FROM oder den SQL-DDL-Anweisungen (Data Definition Language) zum Ändern der Datenbankstruktur durchgeführt. Im Gegensatz zu Datenbankabfragen geben diese Anweisungen keine Ergebnismenge zurück, sondern lediglich einen einzelnen Wert. Im Falle von INSERT INTO, UPDATE und DELETE FROM gibt dieser Wert an, wie viele Datensätze von der Änderung betroffen waren, bei DDL-Anweisungen ist er immer 0. Um solche Anweisungen durchzuführen, stellt das Interface Statement die Methode executeUpdate() zur Verfügung, welche die Anzahl der betroffenen Zeilen als int zurückgibt. 4.4 Die Klasse SQLException Wenn SQL-Anweisungen fehlschlagen, lösen sie normalerweise eine Ausnahme des Typs SQLException aus. Das gilt sowohl, wenn keine Verbindung zur Datenbank zustande gekommen ist, als auch bei allen Arten von Syntaxfehlern in SQL-Anweisungen. Auch bei semantischen Fehlern durch falsche Typisierung oder inhaltlich fehlerhafte SQL-Anweisungen wird eine solche Ausnahme ausgelöst. SQLException ist eine Erweiterung der Klasse Exception und stellt folgende zusätzliche Methoden zur Verfügung: int getErrorCode() String getSQLState() SQLException getNextException() Mit getErrorCode() kann der herstellerspezifische Fehlercode abgefragt werden, getSQLState() liefert den internen SQL-Zustandscode. Etwas ungewöhnlich ist die Methode getNextException(), denn sie unterstützt die Verkettung von Ausnahmen. Jeder Aufruf holt die nächste Ausnahme aus der Liste. Ist der Rückgabewert null, gibt es keine weiteren Ausnahmen. Code zum Behandeln einer SQLException könnte also etwa so aussehen: 1 ... 2 catch (SQLException e) { 3 do { 4 System.err.println(e.toString()); 5 System.err.println("SQL-State: " + e.getSQLState()); 6 System.err.println("ErrorCode: " + e.getErrorCode()); 7 } while ((e = e.getNextException()) != null) 8 } HTBLA Kaindorf/Sulm Seite 11 von 15 PUC | Datenbankzugriff mit JDBC 4.5 Datenbankabfragen Vorbereitete Anweisungen – Prepared Statements Die SQL-Anweisungen, die mittels executeQuery() oder executeUpdate() an die Datenbank gesendet werden, haben bis zur Ausführung im Datenbanksystem einige Umwandlungen vor sich. Zuerst müssen sie auf syntaktische Korrektheit getestet werden. Dann werden sie in einen internen Ausführungsplan der Datenbank übersetzt und mit anderen Transaktionen optimal verzahnt. Deutlich besser wäre es jedoch, eine Art Vorübersetzung für SQL-Anweisungen zu nutzen. Diese Vorübersetzung ist eine Eigenschaft, die JDBC unterstützt und die sich Prepared Statements nennt. Vorbereitet (englisch prepared) deshalb, da die Anweisungen in einem ersten Schritt zur Datenbank geschickt und dort in ein internes Format umgesetzt werden. Später verweist ein Programm auf diese vorübersetzten Anweisungen, und die Datenbank kann sie schnell ausführen, da sie in einem optimalen Format vorliegen. Ein Geschwindigkeitsvorteil macht sich immer dann besonders bemerkbar, wenn Schleifen Änderungen an Tabellenspalten vornehmen. Das kann durch die vorbereiteten Anweisungen schneller geschehen. PreparedStatement-Objekt vorbereiten Genauso wie ein Connection-Objekt eine Methode für ein Statement-Objekt anbietet, werden PreparedStatement-Objekte angelegt. Dazu dient dann eine Methode prepareStatement(). Als Parameter wird eine SQL-Zeichenkette übergeben, die den gleichen Aufbau wie etwa ein executeUpdate() hat. Einen Unterschied werden wir jedoch feststellen: Bei den normalen StatementObjekten können wir dynamische Einträge einfach mit in den String einbauen. Dies geht bei vorbereiteten Anweisungen nicht mehr. Woher sollte auch die Anweisung wissen, was der Benutzer in seine Eingabemaske tippt? Damit jetzt auch eine vorbereitete Anweisung Parameter enthalten kann, werden in die Zeichenkette Platzhalter mit einem Fragezeichen eingefügt. 1 PreparedStatement updateLfrnt = connection.prepareStatement( 2 "UPDATE Lieferanten SET Adresse = ? WHERE Adresse LIKE ?"); Die Zeile instruiert die Datenbank, die Zeile zu interpretieren, in das interne Format umzusetzen und vorbereitet zu halten. Im nächsten Schritt muss die Anweisung für die Platzhalter Werte einsetzen. Werte für die Platzhalter eines PreparedStatement Bevor die executeUpdate()-Methode die vorbereitete Anweisung abarbeitet, müssen die Platzhalter gefüllt werden. Dazu bietet das PreparedStatementObjekt für die Datentypen jeweils eine setXXX()-Methode an, die den Wert für einen angegebenen Platzhalter setzt. So wird setInt(1,100) die Zahl 100 für das erste Fragezeichen einsetzen. Nach der Zuweisung ist das Objekt für die Ausführung bereit. executeUpdate() kann aufgerufen werden. HTBLA Kaindorf/Sulm Seite 12 von 15 PUC | Datenbankzugriff mit JDBC Datenbankabfragen 1 updateLfrnt.setString(1, "Uferstraße 80"); 2 updateLfrnt.setString(2, "Seestraße 78"); 3 updateLfrnt.executeUpdate(); In einer Schleife lässt sich jetzt nun immer wieder executeUpdate() aufrufen, und die schon gesetzten Parameter werden übernommen. 4.6 Transaktionen Transaktionen sind für Datenbanken ein sehr wichtiges Konzept, denn nur dadurch bleibt die Integrität der Daten erhalten. Transaktionen sind vergleichbar mit einer atomaren Ausführung bei Threads, mit dem Unterschied, dass die Datenbank inmitten einer gescheiterten Transaktion die bisher veränderten Werte rückgängig macht. In der Standard-Verarbeitung in JDBC wird jede SQL-Anweisung für sich als Transaktion abgearbeitet. Dies nennt sich Auto-Commit. Um jedoch eine Folge von Anweisungen in einer Transaktion auszuführen, muss zunächst das AutoCommit zurückgesetzt werden. Dann werden die Datenbankmanipulationen ausgeführt, und die Transaktion kann anschließend abgeschlossen (commit) oder zurückgesetzt (rollback) werden. 1 connection.setAutoCommit(false); Werden alle Statements korrekt abgearbeitet, werden mit connection.commit() die Änderungen dauerhaft gemacht. Tritt ein Fehler auf, können wir mit connection.rollback() die gestartete Transaktion zurücksetzen. Da ist es lohnenswert, eine Ausnahmebehandlung zu schreiben, und im catch das rollback() einzusetzen. HTBLA Kaindorf/Sulm Seite 13 von 15 PUC | Datenbankzugriff mit JDBC Folienvorschlag Folienvorschlag Grundlagen JDBC Datenbanktreiber Verbindung herstellen Treiber anmelden Verbindung aufbauen Verbindung beenden Datenbankabfragen Statement-Objekt executeQuery() und executeUpdate() ResultSet-Objekt SQLException Prepared Statements Transaktionen HTBLA Kaindorf/Sulm Seite 14 von 15 PUC | Datenbankzugriff mit JDBC Zusammenfassung Zusammenfassung In diesem Skriptum wurden folgende Themen behandelt: Die grundsätzliche Arbeitsweise und wichtige Architekturmerkmale von JDBC. Benötigte Treiber für den Zugriff auf die Datenbanken Interbase, MySQL und Oracle. Laden des JDBC-Treibers und Aufbau einer Datenbankverbindung. Ausführen von Datenbankabfragen. Ergebnisse eine Abfrage mit ResultSet ermitteln. Änderungen an der Datenbank durchführen. Behandlung von Ausnahmen bei der Datenabfrage. Verwendung von vorbereiteten Anweisungen (Prepared Statements) Verwendung von Transaktionen Quellen Ein Großteil der Informationen stammt aus dem Buch "Java ist auch eine Insel" (2. Auflage) von Christian Ullenboom welches als Openbook auf http://www.galileocomputing.de/katalog/openbook zum freien Download zur Verfügung gestellt wird. Weitere Quellen Handbuch der Java-Programmierung, 3. Auflage (http://www.javabuch.de) Sun Microsystems, Java Technologie Home Page (http://java.sun.com) HTBLA Kaindorf/Sulm Seite 15 von 15