Universität Zürich Institut für Informatik Sommersemester 2001 Seminar Datenbanktechnologie für das Web Prof. Dr. Klaus Dittrich Datenbankanbindung: JDBC und SQLJ Betreuung: Anca Dobre Jussi Prevost Borrweg 60 8055 Zürich [email protected] Universität Zürich Institut für Informatik Sommersemester 2001 Seminar Datenbanktechnologie für das Web Inhaltsverzeichnis 1. Einleitung ............................................................................................. 3 2. Grundlagen .......................................................................................... 3 2.1. Einsatzfälle........................................................................................................... 3 2.2. Die Architektur von JDBC...................................................................................4 A. Treiber ..............................................................................................................4 B. Programmierkonzept ........................................................................................5 2.3. SQLJ .................................................................................................................... 8 A. Zweck ...............................................................................................................9 B. Überblick .......................................................................................................... 9 C. Hostausdrücke ................................................................................................10 D. In or Out?........................................................................................................11 E. Iteratoren.........................................................................................................11 3. Vergleich ............................................................................................ 12 3.1. Verbindungsaufbau ............................................................................................14 3.2. Insert ..................................................................................................................14 3.3. Select.................................................................................................................. 14 3.4. Online-Überprüfung...........................................................................................14 3.5. Geschwindigkeit ................................................................................................15 4. Zusammenfassung............................................................................. 15 Literaturverzeichnis.................................................................................. 16 2 Universität Zürich Institut für Informatik Sommersemester 2001 Seminar Datenbanktechnologie für das Web 1. Einleitung JDBC (Java DataBase Connectivity)1 ist eine herstellerneutrale Programmierschnittstelle (Application programming interface, API). Sie erlaubt den Programmierern, Verbindung zu einer Datenbank herzustellen und mittels SQL die Datenbank abzufragen oder zu aktualisieren, ohne sich um die spezifischen Eigenheiten eines bestimmten Datenbanksystems zu kümmern. Um die Unterschiede kümmern sich sogenannte JDBC-Treiber, welche von den jeweiligen Datenbankherstellern bereitgestellt werden. Die JDBC-API ist vollständig in Java implementiert und ist somit auch plattformunabhängig. SQLJ befasst sich mit verschiedenen Aspekten der Integration von SQL und Java. SQLJ besteht aus drei Teilen: Der erste Teil (SQLJ Teil 0) stellt eine standardisierte Syntax und Semantik für die Einbettung von SQL in Java-Programmen zur Verfügung. Dabei bietet er gegenüber dem JDBC-Kit eine einfachere Verwendung und Überprüfungsmöglichkeiten der SQL Abfragen schon zu Übersetzungszeit. Der zweite Teil (SQLJ Teil 1) befasst sich mit der Implementierung von Prozeduren und Funktionen in Java, welche in einer Datenbank gespeichert werden können. Der dritte Teil (SQLJ Teil 2) beschreibt Möglichkeiten, wie man Java-Datentypen und -Klassen als benutzerdefinierte SQL-Datentypen in einer Datenbank ablegen kann. In dieser Arbeit werde ich mich auf JDBC und SQLJ Teil 02 konzentrieren, weil es um einen Überblick von Datenbankanbindung in Java geht. Wer sich für weitere Details von SQLJ Teil 1 und 2 interessiert, findet im Literaturverzeichnis weiterführende Referenzen. 2. Grundlagen 2.1. Einsatzfälle Wer eine Anwendung oder ein Applet in Java realisiert und dabei eine Datenbankanbindung benötigt, braucht JDBC oder SQLJ. Anwendungen haben dabei vollkommen freie Hand, um auf (verteilte) Datenbankserver zuzugreifen. Applets können nur eine Datenbankverbindung zu dem Server öffnen, von dem sie der Benutzer heruntergeladen hat. Das heisst Datenbankund Webserver müssen sich auf demselben Computer befinden, was kein typisches Setup ist. Für signierte Applets sind diese Restriktionen gelockert. Für die Realisierung der Anwendung spielt es keine Rolle, ob eine herkömmliche Client/Server-Architektur oder eine Multi-Tier-Architektur verwendet wird. 1 JDBC ist ein geschützter Markenname und eigentlich keine Abkürzung. Trotzdem wird JDBC vielfach als eine Abkürzung für Java Database Connectivity betrachtet. 2 Ich verwende ab jetzt nur noch die Bezeichnung SQLJ. 3 Universität Zürich Institut für Informatik Sommersemester 2001 Seminar Datenbanktechnologie für das Web 2.2. Die Architektur von JDBC JDBC besteht aus zwei Schichten. Die obere Schicht ist die eigentliche JDBC–API. Diese kommuniziert mit der unteren Schicht, dem JDBC-Treibermanager-API, und sendet dieser die verschiedenen SQL-Anweisungen. Der Treibermanager sollte mit den verschiedenen Treibern von Drittherstellern kommunizieren, welche die eigentliche Verbindung zur Datenbank herstellen. Der Programmierer muss sich nicht um herstellerspezifische Eigenheiten von DBMS kümmern. Diese Unterschiede übernimmt ein JDBC-Treiber vom jeweiligen Datenbankhersteller. Abbildung 1 gibt einen Überblick. Abbildung 1: Die Architektur von JDBC A. Treiber Wie aus Abbildung 1 ersichtlich ist, werden vier verschiedene Typen von Treibern unterschieden, die zumindest den SQL-92 Entry Level unterstützen müssen. 1. Ein Typ 1 Treiber (JDBC-ODBC Brücke) benützt die ODBC3-Schnittstelle, um mit der Datenbank zu kommunizieren. Ein solcher Treiber wird im JDK angeboten. Allerdings muss auf allen Clients eine ODBC-Schnittstelle installiert sein, was nicht immer ist bzw. möglich wäre. 2. Ein Typ 2 Treiber (Native API Treiber) ist zum Teil in Java geschrieben, benutzt aber bestimmte Schnittstellen von DBMS. Für eine Installation wird nicht nur die 3 Open Database Connectivity; DB-Zugriffsschnittstelle von Microsoft 4 Universität Zürich Institut für Informatik Java-Bibliothek benötigt, Datenbankhersteller. Sommersemester 2001 Seminar Datenbanktechnologie für das Web sondern auch Bibliotheken vom jeweiligen 3. Ein Typ 3 Treiber (JDBC-Net Treiber) ist eine reine Client-Bibliothek in Java, die über ein datenbankunabhängiges Protokoll Datenbankanforderungen an eine Serverkomponente schickt, die wiederum die Anforderungen in ein datenbankspezifisches Protokoll umsetzt. Die Client-Bibliothek ist unabhängig von der eigentlichen Datenbank und vereinfacht somit die Entwicklung. 4. Ein Typ 4 Treiber (Native-Protokoll Treiber) ist auch eine reine Java-Bibliothek, die JDBC-Anforderungen direkt in ein datenbankspezifisches Protokoll übersetzt. Die meisten Datenbankhersteller stellen ihren Datenbanken entweder einen Typ 3 oder Typ 4 Treiber zur Verfügung. Typ 1 und Typ 2 Treiber werden oft für das Testen verwendet oder in Unternehmensnetzwerken, wo die zusätzliche Installation der notwendigen Bibliotheken kein grosses Problem sein sollte. B. Programmierkonzept Die für die JDBC-Programmierung verwendeten Klassen sind im Paket java.sql enthalten. Abbildung 2 zeigt die wichtigsten Klassen. Abbildung 2: Die wichtigsten Klassen in JDBC Bei der Programmierung geht man nach folgenden Schritten vor: 1. Verbindung zur Datenbank herstellen (über den JDBC-Treiber) 2. SQL-Anweisung an ein sogenanntes Statement-Objekte übergeben 3. Statement ausführen 4. evt. Abfrageergebnis verarbeiten 5. nicht mehr verwendete Objekte freigeben Auf diese fünf Punkte werde ich jetzt kurz eingehen. In Kapitel 3 folgt dann ein zusammenhängendes Beispiel. 5 Universität Zürich Institut für Informatik 1) Sommersemester 2001 Seminar Datenbanktechnologie für das Web Verbindung herstellen Über die Klasse DriverManager wählt man einen Datenbanktreiber aus und erstellt eine neue Datenbankverbindung. Der Treibermanager muss einen Treiber allerdings erst registrieren, bevor er ihn aktivieren kann. Das folgende Beispiel registriert die JDBC-ODBC-Brücke von Sun: Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); Jetzt kann die Verbindung zur Datenbank hergestellt werden. Die Verbindung wird dabei als Objekt vom Typ Connection repräsentiert. Zum Verbindungsaufbau werden drei Parameter benötigt. Der erste Parameter ist eine URL4, welche den Verbindungsmechanismus, den Treiber und den Namen der Datenbank beinhaltet. Die beiden anderen Parameter sind Benutzername und Passwort. String url = "jdbc.odbc.seminarDB"; String username = “Jussi”; String pw = "seminar"; Connection con = DriverManager.getConnection(url, username, pw); Die Verbindung zur Datenbank steht nun. 2) SQL-Anweisungen SQL-Anweisungen werden als Statement-Objekte behandelt. Diese Objekte senden Anweisungen zur Datenbank und empfangen die zurückgelieferten Ergebnisse. Dabei werden drei verschiedene Arten von Statements unterschieden: 1. java.sql.Statement ist die Basisklasse für die Verarbeitung von Anweisungen. Ein Statement-Objekt kann dabei für mehrere Abfragen und Anweisungen benutzt werden (z.B. innerhalb einer Transaktion). 2. java.sql.PreparedStatement ist eine Klasse, welche die Verarbeitung von vorbereiteten Anweisungen zulässt. Dabei wird ein sogenannter IN-Parameter (vgl. zu IN und OUT Seite 11) in der SQL-Anweisung durch eine Hostvariable ersetzt. Als Platzhalter dient ein „?“. Bevor man nun die vorbereitete Anweisung ausführt, muss man die Hostvariable mit der passenden set-Methode an den tatsächlichen Wert binden. Es gibt für jeden Datentyp eine set-Methode. 3. java.sql.CallableStatement ist eine Klasse, welche die Verarbeitung von gespeicherten Prozeduren ermöglicht. Darauf gehe ich nicht weiter ein. 3) Ausführen von Anweisungen Zum Ausführen der Anweisungen benötigt man noch zusätzliche Methoden. Dabei muss man zwischen Abfragen (SELECT) und anderen Anweisungen (Datendefinition und Datenmanipulation) unterscheiden. 6 Universität Zürich Institut für Informatik Sommersemester 2001 Seminar Datenbanktechnologie für das Web Für Abfragen dient executeQuery(String sql) Im Erfolgsfalle wird das Ergebnis in einem java.sql.ResultSet zurückgeliefert. Die Ergebnismenge kann dabei durch den Aufruf von next() zeilenweise durchlaufen werden. Statement stmt = con.createStatement(); String query = "SELECT Nachname, Gehalt FROM Angestellter " + "WHERE Gehalt > 5000"; ResultSet rs = stmt.executeQuery(query); while (rs.next()) System.out.println("Name: " + rs.getString(1) + " | " + "Gehalt: " + rs.getDouble("Gehalt")); rs.close(); Für Datendefinitionen und Datenmanipulationen muss hingegen executeUpdate(String command) verwendet werden. Diese Methode liefert im Erfolgsfall die Anzahl veränderter Zeilen zurück, die von der SQL-Anweisung betroffen waren. Statement stmt = con.createStatement(); String cmd = "UPDATE Angestellte SET Gehalt = Gehalt * 1.1" + "WHERE Nachname = 'Prevost'"; stmt.executeUpdate(cmd); Daneben gibt es noch eine allgemeine Methode execute, mit der sich beliebige SQLAnweisung ausführen lassen. Die Methode liefert true, wenn das Ergebnis ein ResultSet ist, und false, wenn es ein Integer-Wert ist. 4) Ergebnisse von Abfragen Wie wir gesehen haben, werden Abfrageergebnisse in einem java.sql.ResultSet dargestellt. Ein ResultSet verwaltet einen internen Zeiger, der einen tupelweisen Zugriff auf die Ergebnismenge erlaubt (Ähnlich dem java.util.Iterator). Der Zeiger ist dabei zu Beginn auf eine Position vor der ersten Zeile gesetzt. Folglich muss man die Methode next5 einmal aufrufen, um den Iterator auf die erste Zeile zu setzen. Dies wird normalerweise in einer while-Schleife gemacht: while (rs.next()) { Zeile der Ergebnismenge betrachten } Für den Zugriff auf den Inhalt einer Spalte gibt es verschiedene Zugriffsmethoden. Für jeden Java-Typ gibt es Zugriffsmethoden wie getString und getDouble. Jede Zugriffsmethode hat zwei Formen, eine mit einem numerischen Parameter und eine mit einer Zeichenfolge. Übergibt man ein numerisches Argument, bezieht man sich auf die Spalte mit dieser Nummer (z.B. rs.getString(1);). Zu beachten ist, dass die Nummerierung der Datenbankspalten 4 5 Uniform Resource Locator Im JDBC 2 wurden auch andere Navigationsmethoden eingeführt. 7 Universität Zürich Institut für Informatik Sommersemester 2001 Seminar Datenbanktechnologie für das Web mit 1 beginnt und nicht mit 0. Übergibt man eine Zeichenfolge, bezieht man sich auf die Spalte mit diesem Namen (z.B. rs.getDouble("Gehalt");). Numerische Argumente sind zwar etwas effizienter, aber mit Zeichenfolgen ist der Code verständlicher und leichter zu pflegen. SQL- und Java-Datentypen sind nicht genau gleich. Jede get-Methode führt eine sinnvolle Typumwandlung durch, wenn der Typ der Methode nicht dem Typ der Spalte entspricht. Z.B. können fast alle Typen als Strings dargestellt werden. Tabelle 1 gibt einen Überblick über die wichtigsten Typumwandlungen. SQL-Datentyp Java-Datentyp CHAR String VARCHAR String BIT boolean INTEGER int BIGINT long REAL float FLOAT double DOUBLE double BINARY byte[] DATE java.sql.Date TIME java.sql.Time Tabelle 1: Typabbildungen zwischen SQL und Java 5) Freigabe von Objekten Objekte, wie z.B. Connection, Statement oder ResultSet, welche nicht mehr gebraucht werden, sollten durch die Methode close freigegeben werden. Ein ResultSet wird üblicherweise nach Beenden der while-Schleife geschlossen. Somit wird es sofort freigegeben und kann durch die automatische Speicherbereinigung entsorgt werden, was nebenbei auch DBMS-Ressourcen freimacht. Statement-Objekte werden oft am Ende der Methode geschlossen. Connection-Objekte werden meistens erst bei Beenden der Anwendung geschlossen, da diese ständig gebraucht werden. 2.3. SQLJ Es stellt sich die Frage, wieso man überhaupt SQLJ braucht. JDBC alleine reicht vollkommen aus. Es zeigen sich allerdings zwei Schwächen: 1. Der (SQL-relevante) Code wird lange und unhandlich. 2. Fehler innerhalb SQL-Operationen werden erst zur Laufzeit entdeckt. Wie wir sehen werden, löst SQLJ beide Probleme. Vom ersten Punkt und dessen Lösung können wir uns in Kapitel 3 überzeugen. 8 Universität Zürich Institut für Informatik Sommersemester 2001 Seminar Datenbanktechnologie für das Web A. Zweck Java gehört zu den objektorientierten Programmiersprachen. SQL ist dagegen eine nichtprozedurale Sprache zur Definition und Manipulation von Daten in relationalen Datenbanken. SQLJ ist der ANSI-Standard6 für die Einbettung von statischen SQL-Anweisungen in Java Programmen. Es ist das Produkt der Zusammenarbeit von Oracle, IBM, Sybase, Tandem, Informix, JavaSoft, XDB und Microsoft. B. Überblick SQLJ ersetzt JDBC nicht, sondern benutzt es weiterhin. Für statische SQL-Anweisungen, welche schon zum Entwicklungszeitpunkt bekannt sind, liefert SQLJ aber einen einfacheren und robusteren Weg. Für dynamische SQL-Anweisungen, welche z.B. während der Laufzeit von einem Endbenutzer kreiert werden, muss der bisherige „JDBC-Weg“ verwendet werden. Natürlich können beide Möglichkeiten nebeneinander eingesetzt werden. JDBC und SQLJ sind also keine Substitute, sondern Komplementärprodukte. SQLJ-Anweisungen werden grundsätzlich mit #sql { SQL-Ausdruck }; gekennzeichnet. Java-Code mit solchen Anweisungen kann somit nicht mehr in einem herkömmlichen Java-Compiler übersetzt werden, sondern benötigt einen SQLJ-Translator. Abbildung 3 zeigt das Vorgehen im Überblick. Abbildung 3: Übersetzung eines SQLJ-Programms 6 seit 1999 9 Universität Zürich Institut für Informatik Sommersemester 2001 Seminar Datenbanktechnologie für das Web Der Quellcode wird nicht in einer .java-Datei erfasst, sondern in einer .sqlj-Datei. Diese Datei wird von einem Präprozessor, dem SQLJ-Translator, übersetzt. Dabei werden die SQLOperationen auf ihre Syntax und Semantik überprüft. Zusätzlich besteht die Möglichkeit des Online-Überprüfens, d.h. der Translator kann die Anweisungen gegen das Datenbankschema testen. Während der Übersetzung werden (mindestens) zwei Dateien generiert: 1. Eine Java-Quellcodedatei (.java-Datei): Sie enthält a) die SQL-Anweisungen, welche durch Aufrufe an das SQLJ-Laufzeitsystems ersetzt wurden; und b) den „normalen“ Quellcode des Programms. 2. Ein sogenanntes Profil (Datei mit der Endung .ser), welches eine Beschreibung der SQL-Anweisungen beinhaltet. Nun kann der generierte Java-Quellcode mit einem Standard-Compiler (z.B. javac aus dem JDK) übersetzt werden. Auch das Starten der Anwendung ist normal möglich. Das SQLJLaufzeitsystem wird dabei automatisch aufgerufen, wenn es sich um eine SQLJ-Anwendung handelt. Zur Verbindung mit der Datenbank wird der JDBC-Treiber benutzt. C. Hostausdrücke Der zentrale Bestandteil der Integration von SQL in Java als Wirtssprache bilden die sogenannten Hostausdrücke. Die einfachste Form davon sind die Hostvariablen. Das sind normale Java-Variablen, welche den Konventionen der Typenabbildung zwischen Java und SQL genügen (vgl. Tabelle 1). Es versteht sich von selbst, dass diese Variablen innerhalb des Blockes sichtbar sein müssen. Hostvariablen werden in SQLJ-Anweisungen mit einem vorangestellten Doppelpunkt gekennzeichnet. Z.B.: double salaer; String name = "Prevost"; #sql { SELECT Gehalt INTO :salaer FROM Angestellter WHERE Nachname = :name }; Neben Hostvariablen können auch Hostausdrücke verwendet werden. Das sind u.a.: • Zuweisungen: z.B. Gehalt = Gehalt * (1.0 + bonus/100) • Inkrement und Dekrement: z.B. i++ • Methodenaufrufe: z.B. name.toUpperCase() Dabei müssen diese Ausdrücke innerhalb einer runden Klammer mit einem Doppelpunkt davor stehen. Als Beispiel: String[] namen = new String[] { "Prevost", "Scheff", "Knaller"}; double[] bonus = new double[] { 10.0, 5.0, 2.5 }; for (int i=0; i<namen.length; i++ ) { #sql { UPDATE Angestellter SET Gehalt = Gehalt * :(1.0 + bonus[i]/100) WHERE Nachname = :(namen[i].toUpperCase()) }; }… 10 Universität Zürich Institut für Informatik Sommersemester 2001 Seminar Datenbanktechnologie für das Web Doch noch nicht genug mit der Flexibilität von SQLJ. Es können auch gespeicherte Prozeduren und Funktionen als Hostausdrücke verwendet werden. Dabei werden die Schlüsselworte CALL für Prozeduren und VALUES für Funktionen benutzt. Das folgende Beispiel ruft die (eingebaute) SQL-Funktion SYSDATE()7 auf und weist den Wert einer Variable zu: java.sql.Date heute; #sql heute = { VALUES(SYSDATE()) }; System.out.println("Die Datenbank denkt, heute ist der " + heute); D. In or Out? Im vorherigen Abschnitt habe ich die Tatsache weggelassen, dass Hostvariablen (oder -ausdrücke) in drei verschiedenen Modi benutzt werden können. IN bezeichnet Werte, die an die Datenbank gesendet werde. Dagegen beschreibt OUT Variablen, die ihren Wert aus der Datenbank bekommen. INOUT umfasst beide. Normalerweise haben Hostausdrücke den Modus IN. Die Ausnahme sind Hostausdrücke in der SELECT INTO Anweisung, welche OUT-Variablen sind. In diesen Fällen ist der Modus implizit vorgegeben. In allen anderen Fällen, v.a. bei gespeicherten Prozeduren, muss der Modus explizit festgelegt werden. E. Iteratoren Hostausdrücke eignen sich nur für den Austausch einzelner Werte zwischen SQL und Java. Zur Speicherung von ganzen Ergebnismengen wird dagegen ein Iterator verwendet, ähnlich einem java.sql.ResultSet. Im Unterschied zum JDBC, muss ein Iterator vorher deklariert werden. Dabei wird zwischen zwei unterschiedlichen Arten unterschieden: • Positionsiteratoren: Sie werden durch den (Java-)Datentyp der Spalte charakterisiert. Zum Abrufen der Werte wird die FETCH-Anweisung verwendet. Dies ist eine übliche Vorgehensweise auch in anderen Sprachen mit eingebettetem SQL. #sql iterator MyPosIter(String, double); • Benannte Interatoren: Hier kann neben dem Datentyp auch der Bezeichner gewählt werden. Der Bezeichner liefert denn auch gerade noch den Namen für die Zugriffsmethode auf den jeweiligen Spaltenwert. JDBC-Programmier sollten sich unmittelbar mit dieser Art vertraut fühlen. #sql iterator MyNamedIter(String name, double gehalt); Dabei muss die Anzahl der Spalten der Ergebnisrelation mit der Anzahl der Attribute der Iteratoren übereinstimmen. In unserem Fall muss die Ergebnisrelation zweidimensional sein. Der SQLJ-Translator generiert aus der Iterator-Definition eine Iterator-Klasse mit dem angegebenen Namen. Z.B. ergibt sich für den Positionsiterator eine Klasse MyPosIter. Bei 7 Zumindest bei Oracle 11 Universität Zürich Institut für Informatik Sommersemester 2001 Seminar Datenbanktechnologie für das Web den benannten Iteratoren wird zusätzlich noch für jede Spalte eine Zugriffsmethode geschaffen, also in unserem Beispiel zwei Methoden mit den Namen name() und gehalt(). 3. Vergleich Nun wissen wir genug, um SQLJ und JDBC zu vergleichen. Am Besten können wir das ganze anhand eines Beispiels machen. Wie schon erwähnt, muss bei SQLJ das Datenbankschema schon bekannt sein. Ich verwende folgende zwei Relationen: 1. Angestellter(Vorname, Nachname, AHV_Nr, Gehalt, Abt_Nr) 2. Abteilung(Abt_Name, Nr, Ort) Im Beispiel wird zuerst ein INSERT und danach ein SELECT ausgeführt. import java.sql.*; import sqlj.runtime.*; import sqlj.runtime.ref.*; public class SeminarExample { // Die beiden folgenden Variablen sind für die Abfrage vorgesehen String name = "Müller"; double salary = 4000.0; Connection con; public static void main(String[] args) throws SQLException { …// Treiber registrieren, Verbindung herstellen usw. (Vgl. Seite 6) if (args[0].equals("jdbc")) jdbcExample(); else if (args[0].equals("sqlj")) sqljExample(); … } public void jdbcExample() throws SQLException { // Zuerst INSERT durchführen // SQL-Anweisung als Objekt vorbereiten Statement stmt = con.createStatement(); // INSERT-Anweisung erstellen String command = "INSERT INTO Angestellter " + "VALUES ('Jussi', 'Prevost', 2606, 10000.0, 3)"; // Anweisung ausführen stmt.executeUpdate(command); // Statement schliessen stmt.close(); // SELECT ausführen // Abfrage erstellen String query = "SELECT Vorname, Nachname, AHV_Nr, Gehalt, Ort " + "FROM Angestellter, Abteilung " + "WHERE Nachname=? AND " + "Gehalt>? AND " + "Abt_Nr=Nr"; 12 Universität Zürich Institut für Informatik Sommersemester 2001 Seminar Datenbanktechnologie für das Web // PreparedStatements vorbereiten und Hostvariablen einbinden PreparedStatement pstmt = con.prepareStatement(query); pstmt.setString(1, name); pstmt.setInt(2, salary); // Abfrage ausführen ResultSet rs = pstmt.executeQuery(); // und Resultate anzeigen while(rs.next()) { System.out.println("Vorname = " + rs.getString(1) + " | " + "Nachname = " + rs.getString(2) + " | " + "AHV_Nr = " + rs.getInt(3) + " | " + "Gehalt = " + rs.getDouble("Gehalt") + " | " + "Ort = " + rs.getString("Ort")); } // ResultSet- und Statement-Objekte schliessen rs.close(); pstmt.close(); } public void sqljExample() throws SQLException { // Zuerst INSERT durchführen // INSERT-Anweisung in der SQLJ-Syntax erstellen #sql { INSERT INTO Angestellter(Vorname, Nachname, AHV_Nr, Gehalt, Abt_Nr) values('Jussi', 'Prevost', 2606, 10000.0, 3) }; // SELECT ausführen /* Iterator für das Speichern der Abfrage definieren * ACHTUNG: MÜSSTE AUSSERHALB DES PROGRAMMS DEFINIERT WERDEN, * z.B. nach den IMPORT-Anweisungen!!!!!!! * Aus Übersichtsgründen habe ich es hier hergenommen */ #sql iterator MyIter(String Firstname, String Name, int AHV_NR, double Salary, String City); // Iterator-Objekt erstellen MyIter iter; // Abfrage ausführen und Ergebnis dem Iterator zuweisen #sql iter = { SELECT Vorname, Nachname, AHV_Nr, Gehalt, Ort FROM Angestellter, Abteilung WHERE Nachname = :name AND Gehalt> :salary AND Abt_Nr=Nr }; // Ergebnis Anzeigen while (iter.next()) { System.out.println("Vorname = " + iter.FirstName() + " | " + "Nachname = " + iter.Name() + " | " + "AHV_Nr = " + iter.AHV_NR()+ " | " + "Gehalt = " + iter.Salary () + " | " + "Ort = " + iter.City()); } // Iterator-Objekt freigeben iter.close(); } } 13 Universität Zürich Institut für Informatik Sommersemester 2001 Seminar Datenbanktechnologie für das Web 3.1. Verbindungsaufbau Der Verbindungsaufbau funktioniert in beiden Varianten gleich. Eine geschicktere Variante anstelle der manuellen Registrierung des Treibers, wäre die Verwendung der Systemeigenschaft jdbc.drivers. Dabei könnte auch die URL, der Benutzername und das Passwort in einer Eigenschaftsdatei (Datei mit der Endung .properties) gespeichert werden und bequem auf die Endbenutzer eingestellt werden. 3.2. Insert Eine Insert-Anweisung auf dem „JDBC-Weg“ umfasst mehrere Schritte, welche den Code aufblähen: Zuerst muss ein Statement-Objekt erzeugt werden, dann ausgeführt und zum Schluss sollte es wieder geschlossen werden. SQLJ braucht hingegen „nur“ eine Anweisung, welche (beinahe) die reine SQL-Anweisung in der SQLJ-Syntax beinhaltet. Es wird auch keine execute-Methode gebraucht. Die Verwendung von SQLJ ist also viel einfacher, kürzer und deshalb auch einfacher zu Warten. 3.3. Select Bei der Select-Anweisung sieht es ähnlich aus. Die JDBC-Methode benötigt wieder mehrere Schritte: (Prepared)Statement-Objekt schaffen, Variablen zuweisen, ausführen, Ergebnis einem ResultSet zuweisen. Danach Ergebnis bearbeiten und zum Schluss ResultSet- und Statement-Objekte schliessen. Bei der SQLJ-Methode muss zuerst der Iterator für die Ergebnismenge definiert werden. Dies sieht auf den ersten Blick nach zusätzlicher Arbeit aus, ist es aber nicht. Denn ein solcher Iterator liefert intuitivere Zugriffsmethoden auf die einzelnen Ergebnisspalten, als diejenigen von ResultSet. Auch hier wird keine execute-Methode verwendet. Zudem entfällt das Setzen der Platzhalter und Zuweisen der (Host-)Variablen durch Methodenaufruf. Die HostAusdrücke können direkt in der SQL-Anweisung verwendet werden. Das SQLJ-Verfahren ist wiederum kürzer und einfacher zu Bedienen. 3.4. Online-Überprüfung Bei SQLJ besteht die Möglichkeit, SQL-Anweisungen zur Übersetzungszeit anhand des Datenbankschemas zu prüfen. Dabei werden nicht schon die Daten benötigt, sondern das Schema mit den richtigen Attributen und Datentypen genügt. Eine solche OnlineÜberprüfung kann folgendermassen aussehen: sqlj –user=Jussi/seminar –url=jdbc:odbc:seminardb SeminarExample.sqlj So werden SQL-spezifische Fehler schon zur Übersetzungszeit erkannt und können behoben werden. JDBC (bzw. der Java-Compiler) bietet keine solchen Möglichkeiten. 14 Universität Zürich Institut für Informatik Sommersemester 2001 Seminar Datenbanktechnologie für das Web 3.5. Geschwindigkeit Da SQLJ über JDBC mit einer Datenbank kommuniziert, kann es kaum schneller sein als JDBC alleine. In beiden Technologien besteht die Möglichkeit, Datenmanipulationsanweisungen zu sammeln und in Form eines Stapels (Batch) an die Datenbank zu senden. Abfragen kann man nicht in einem Stapel einbinden, da sie Ergebnismengen zurückliefern. Hat man z.B. 20 Tupel einzufügen, so würde man diese in einem Stapeln sammeln und dann als eine Transaktion an die Datenbank senden, anstatt alle 20 Anweisungen einzeln zu übermitteln. Die Vorteile liegen auf der Hand. 4. Zusammenfassung Programmierer, die in ihrer Anwendung eine Datenbankanbindung brauchen, benötigen aus meiner Sicht JDBC und SQLJ. Dabei wird eine Anwendung geschaffen, die sowohl plattformunabhängig wie auch DBMS-unabhängig ist. JDBC alleine würde genügen, weist aber zwei Schwächen aus: 1. Der Quellcode wird lange und unübersichtlich, was Fehlersuche und Unterhalt erschwert. 2. Fehler innerhalb von SQL-Anweisungen werden erst zur Laufzeit erkannt. SQLJ löst beide Probleme folgendermassen: 1. SQLJ erlaubt die Verwendung von Hostausdrücken direkt in SQL-Anweisungen, was den Code kürzer und einfacher macht. 2. Der SQLJ Translator überprüft die Syntax und Semantik von SQL-Anweisungen. Es besteht die Möglichkeit die Anweisungen gegen das Datenbankschema zu prüfen, wobei SQL-Fehler frühzeitig entdeckt und behoben werden können. Voraussetzung für den Einsatz von SQLJ ist, dass die Datenbank schon vorhanden ist, oder zumindest das Schema. Ansonsten wäre es nicht möglich eine Online-Überprüfung vorzunehmen, oder sogar Abfragen vorzubereiten! Allgemein kann man sagen, dass JDBC und SQLJ komplementäre Technologien sind, weil: • SQLJ benutzt zur Kommunikation mit der Datenbank JDBC. • JDBC wird für dynamisches SQL gebraucht, wo die Anweisungen erst zur Laufzeit bekannt werden. • JDBC und SQLJ können miteinander eingesetzt werden. 15 Universität Zürich Institut für Informatik Sommersemester 2001 Seminar Datenbanktechnologie für das Web Literaturverzeichnis • Estermann, Conny. SQLJ. Seminar Datenbanktechnologie für das Web, Universität Zürich, Sommersemester 2001 • Horstmann, Cay S. und Cornell, Gary. Core Java 2 – Advanced Features. Prentice Hall, 1999 • JDBC API Documentation. http://java.sun.com/j2se/1.3/docs/guide/jdbc • Khan, Salman & Kurian, Thomas & Wright, Brian. SQLJ: It’s Javalicious!. http://technet.oracle.com/, 1999 • Lagler, Severin. JDBC und JavaBlend. Seminar Datenbanktechnologie für das Web, Universität Zürich, Sommersemester 2001 • Rohwelder, Ekkehard. SQLJ: Tricks, Traps and Gems. http://technet.oracle.com/, 1999 • Saake, Gunter und Sattler, Kai-Uwe. Datenbanken & Java: JDBC, SQLJ und ODMG. dpunkt.verlag, 2000 • SQLJ Konsortium. http://www.sqlj.org/ • SQLJ Standards. http://otn.oracle.com/tech/java/sqlj_jdbc/htdocs/standards.html • Taylor, Blair und Woodger, James. SQLJ: An Easier Way to Access SQL Data. Java Enterprise Developer online (http://www.pinnaclepublishing.com/je/JEmag.nsf/0/8C6831AB722AAD8E85256981 006F42A0), November 2000 16