Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) David Schiffer WS 2007/2008 2.Auflage (2007) [email protected] http://fh.atbits.de Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Inhalt 1 2 3 4 5 6 Allgemein..........................................................................................................................5 1.1 Überblick...................................................................................................................5 1.1.1 Zugriff auf Datenbanken: ...................................................................................5 1.1.2 Zusatzprogramme innerhalb von Datenbanken: ................................................6 1.2 Motivation für Datenbankprogrammierung ................................................................6 1.3 Beziehungen zu anderen Fächern ............................................................................7 1.4 Bestandteile einer DB-Anwendung............................................................................8 1.5 Datenbank Architekturen...........................................................................................9 1.6 Fragestellungen bei Datenbankprogrammierung.....................................................11 SQL – eine Wiederholung?.............................................................................................12 Probleme bei Datenbankprogrammierung ......................................................................22 3.1 Die Coddschen Regeln ...........................................................................................22 3.2 Der „Impedance Mismatch“ .....................................................................................24 3.2.1 Was ist der Impedance Mismatch?..................................................................24 3.2.2 Objekte versus Tabellen..................................................................................25 3.2.3 Die unterschiedlichen Paradigmen in der Praxis .............................................26 3.3 Cursor-Konzept.......................................................................................................27 3.4 Andere Lösungsansätze zur Überwindung des Impedance Mismatch.....................28 Zugriff auf Datenbanken .................................................................................................29 4.1 Allgemein ................................................................................................................29 4.2 Verarbeitung einer SQL-Anfrage.............................................................................30 Datenbankprogrammierung innerhalb der Datenbank - Stored Procedures & Trigger.....31 5.1 Was sind Stored Procedures?.................................................................................31 5.2 Wozu dienen Stored Procedures? ..........................................................................31 5.3 Wer bietet Stored Procedures? ...............................................................................31 5.4 Wann sollten Stored Procedures verwendet werden?.............................................32 5.5 Stored Procedures / Trigger (bei Oracle) ................................................................32 5.5.1 Allgemein ........................................................................................................32 5.5.2 Aufbau von Stored Procedures........................................................................33 5.5.3 Beispiel (PL/SQL)............................................................................................33 5.5.4 Ausnahmebehandlung ....................................................................................34 5.5.5 Aufbau von Triggern (PL/SQL) ........................................................................35 5.5.6 Beispiel (PL/SQL)............................................................................................35 5.6 Erstellen von Stored Procedures.............................................................................36 5.7 Aufruf und Ablauf von Stored Procedures ...............................................................37 5.7.1 Aufruf einer Stored Procedure aus dem Client-Programm...............................37 5.7.2 Stored Procedure auf dem DB-Server .............................................................37 Java Database Connectivity (JDBC)...............................................................................38 6.1 Allgemein ................................................................................................................38 6.2 Grundstruktur eines JDBC-Programms...................................................................38 6.3 Einbinden der JDBC-Klassen..................................................................................39 6.4 Architektur...............................................................................................................40 6.5 Verbindungsaufbau zur Datenbank .........................................................................42 6.5.1 Laden eines JDBC Treibers ............................................................................44 6.5.2 Verbindung zur Datenbank: Connection Objekt erzeugen ...............................44 6.6 Datenzugriff mittels SQL .........................................................................................47 6.6.1 Dynamisches SQL mit Statments ....................................................................47 6.6.2 Vorkompiliertes SQL .......................................................................................48 6.7 Ergebnisverarbeitung mittels ResultSet ..................................................................51 6.7.1 ResultSets.......................................................................................................51 6.7.2 Ergebnisabfrage..............................................................................................52 6.7.3 Datensätze ändern..........................................................................................52 6.7.4 Datensätze einfügen .......................................................................................53 6.7.5 Batch Updates.................................................................................................54 Seite 2 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Fehlerbehandlung ...................................................................................................56 6.8 6.8.1 Allgemein ........................................................................................................56 6.8.2 Abfangen von Fehlern und Ausnahmen ..........................................................58 6.8.3 Abfragen von Warnungen................................................................................59 6.9 Verbindungsabbau..................................................................................................60 6.10 Transaktionssteuerung............................................................................................60 6.11 Fazit........................................................................................................................61 6.12 Erweiterte Themen..................................................................................................62 6.12.1 JNDI & Connection Pooling .............................................................................62 7 DAPRO - Best Practices.................................................................................................64 7.1 DAO – Data Access Objects ...................................................................................65 7.1.1 Allgemein: .......................................................................................................66 7.1.2 Am Beispiel: Kundeninformation......................................................................68 7.1.3 Vorteile............................................................................................................71 8 Embedded SQL ..............................................................................................................73 8.1 Allgemein ................................................................................................................73 8.2 Beispiel ...................................................................................................................73 8.3 Übersetzung eines Programms mit embedded SQL ...............................................74 8.4 Datenaustausch zwischen Programm und Datenbank ............................................74 8.5 Cursorkonzept ........................................................................................................75 8.6 Statisches und dynamisches SQL...........................................................................76 8.7 Kopplung zwischen Anwendung und Datenbank.....................................................77 8.8 Fazit........................................................................................................................77 9 Open Database Connectivity (ODBC).............................................................................78 9.1 Allgemein ................................................................................................................78 9.1.1 Unterschiede zu embedded SQL.....................................................................78 9.1.2 Ziele ................................................................................................................78 9.1.3 Design-Prinzipien ............................................................................................78 9.2 Architektur...............................................................................................................79 9.3 Vergleich zu proprietären DB-API’s.........................................................................80 9.4 Programmschritte beim ODBC-Zugriff.....................................................................81 9.5 Beispiel ...................................................................................................................82 9.6 Prinzipien von ODBC ..............................................................................................86 9.6.1 Handles...........................................................................................................86 9.6.2 Fehlerbehandlung ...........................................................................................86 9.7 Dynamisches SQL in ODBC ...................................................................................87 9.8 Retrieval von Informationen in ODBC .....................................................................87 9.9 Fazit........................................................................................................................88 10 ActiveX Data Objects.NET (ADO.NET) .......................................................................89 10.1 Allgemein ................................................................................................................89 10.2 Historie ...................................................................................................................90 10.2.1 Open Database Connectivity (ODBC) ..............................................................90 10.2.2 Data Access Objects (DAO) ............................................................................90 10.2.3 Remote Data Objects (RDO)...........................................................................91 10.2.4 OLE DB...........................................................................................................91 10.2.5 ADO - ActiveX Data Objects............................................................................92 10.3 Das ADO.NET Objekt Modell ..................................................................................93 10.3.1 Managed Provider ...........................................................................................95 10.4 Connection Objekt ..................................................................................................96 10.5 Command Objekt ....................................................................................................96 10.6 DataReader Objekt .................................................................................................98 10.7 DataAdapter..........................................................................................................100 10.8 DataSet Objekt .....................................................................................................101 10.9 DataTable .............................................................................................................102 10.10 DataRelation .....................................................................................................103 10.11 XML ..................................................................................................................104 Seite 3 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Fazit ..................................................................................................................104 10.12 11 Literatur: ...................................................................................................................105 Seite 4 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 1 Allgemein 1.1 Überblick Die Vorlesung Datenbankprogrammierung beschäftigt sich mit: 1.1.1 Zugriff auf Datenbanken: • JDBC • • • • Embedded SQL ODBC ADO Proprietäre Datenbankschnittstellen (z.B. OCI) Ausser bei Embedded SQL handelt es sich bei allen Datenbank-API’s um sog. Call Level Interfaces (CLI). CLI bei Wikipedia: Das Call Level Interface (kurz CLI) ist eine Datenbankschnittstellen-Spezifikation für den Zugriff auf relationale Datenbanken aus anderen Anwendungen heraus. Bei konkreten Implementierungen des CLI handelt es sich um so genannte Middleware, mit deren Hilfe aus beliebigen Anwendungen auf beliebige Datenbanken zugegriffen werden kann. Ähnlich wie Gerätetreiber zum Ansteuern der Hardware wird die CLI-Implementierung zum Ansteuern von Datenbanken benötigt. Somit muss der Programmierer einer Anwendung den Funktionsumfang zum Ansprechen der Datenbank nicht für jedes Programm neu schreiben, sondern greift auf bestehende Module zurück. Die CLI-Implementierung bildet ebenfalls die Befehlssätze verschiedener Datenbanksysteme auf eine immer gleiche Funktionsbibliothek ab, so dass es für das Programm an sich unwichtig ist, mit welcher Datenbank es arbeitet. Das CLI hat also die Funktion eines Übersetzers, der Programmaufrufe in eine „Sprache“ übersetzt, die die Datenbank versteht und umgekehrt Daten, die von der Datenbank zurückgeliefert werden, so formatiert, dass sie vom Programm verarbeitet werden können. Bekannte Programmierschnittstellen, die das CLI-Konzept umsetzen, sind Open Database Connectivity (ODBC) und Java Database Connectivity (JDBC). Der CLI-Standard wurde Anfang 1990 von einem Konsortium aus Datenbankherstellern – der SQL Access Group – entwickelt und 1992 mit Microsofts ODBC erstmals umgesetzt. Die ISOSeite 5 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer und ANSI-Standardisierung folgte 1993. Seit Ende 1994 wird der CLI-Standard von der X/Open-Gruppe weiterentwickelt, welche selbst im Konsortium The Open Group aufging. 1.1.2 Zusatzprogramme innerhalb von Datenbanken: • • • Stored Procedures Stored Functions Trigger 1.2 Motivation für Datenbankprogrammierung Jede moderne eBusiness-Applikation basiert in ihrer Architektur auf Datenbanken. Die Erstellung von Business Software verlangt also nach Datenbankprogrammierung. Seite 6 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 1.3 Beziehungen zu anderen Fächern Programmieren Datenbanksysteme Objektorientierte Programmierung Client-Server Datenbankprogrammierung Internet 2 Objektorientierte Datenbanken Seite 7 von 104 Middleware eBusiness Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 1.4 Bestandteile einer DB-Anwendung Bei der Entwicklung von datenbankgestützten Anwendungen kommt in der Regel ein Schichten-Modell zum Einsatz. Typische Schichten darin sind: Die Benutzerschnittstelle. Die Schichten, die die Anwendung dem Benutzer präsentiert. Die Anwendungs- / Geschäftslogik. Die Software, die die Datenbank für den Benutzer verfügbar macht, indem sie Funktionen der Datenbank-Schnittstelle verwendet, so etwa Daten selektiert oder manipuliert und sie über die Benutzerschnittstelle verfügbar macht. Die Datenbank-Schnittstelle. Die Komponente, mittels der die Anwendung mit dem Datenbank-Management-System interagiert. Diese Komponente wird vom Entwickler der Anwendung verwendet und auf diese konzentriert sich das darauf folgende Kapitel. Die Vermittlungssoftware. Hierbei handelt es sich z.B. um Betriebssystem-, Netzwerk-, und Kommunikationssoftware, die (je nach ‚Architektur) die Verbindung zwischen der Datenbank-Schnittstelle und der Datenbank herstellt. Das Datenbank-Management-System. Die Software, die die eigentlichen Datenzugriffe auf dem Datenspeicher durchführt. Wenn wir mit einer SQL-Datenbank arbeiten, enthält diese Komponente einen SQL-Parser, einen Optimierer, ein Datenzugriffsmodul, etc. Kurz ausgedrückt stellt diese Komponente die „Datenbank“ oder den „Server“ dar. Der Datenspeicher. Die Komponente, die die eigentlichen Datenbank-Inhalte gespeichert hält. Dies ist in der Regel ein Festplatten-System. Seite 8 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 1.5 Datenbank Architekturen Ausgehend von dem Schichten-Modell sollten wir betrachten, in welcher Art und Weise die Komponenten implementiert sind bzw. miteinander agieren. Diese Betrachtung gibt uns einen groben Überblick über die gängigen Datenbank-Architekturen. Im Folgenden teilen wir diese Architekturen in 4 Gruppen ein: 1. Zentralisierte Datenbank-Systeme, die in der Regel auf Mainframes laufen. Die Datenbank-Anwendung läuft als Prozess auf dem Server, die Schnittstelle zum Anwender ist nur ein einfaches Terminal. Dabei wird selbstverständlich davon ausgegangen, dass die Anwendung ein vom eigentlichen DatenbankManagementsystem getrennter Prozess ist, der durch einen Prozess-KommunikationsMechanismus des Betriebssystems mit dem Datenbank-Prozess kommuniziert. 2. Client-Server-Datenbanken verwenden einen zentralen Datenbank-Server. Die Anwendung selbst jedoch läuft im Gegensatz zu den zentralisierten DatenbankSystemen auf dem Client. Die Anwendung ist von der Datenbank getrennt und kommuniziert mit dem Datenbank-Server über eine Vermittlungssoftware, die in der Regel ein Daten-Protokoll zwischen Client und Server betreibt. 3. Desktop-Datenbanken sind in der Regel auf einem einzelnen PC installiert. Die Datenbankanwwendung verwendet eine Programmbibliothek (z.B. ISAM-Bibliothek) um auf die Daten zuzugreifen. Mittlerweile setzen sich aber auch in diesem Bereich verstärkt SQL-Datenbanken durch. In diesem Fall stellt sich eine ähnliche Konstellation wie bei den Client-ServerDatenbank dar, wobei Client und Server auf einem Rechner betrieben werden. 4. 3-Tier-Architektur unterscheidet sich von der Client-Server-Architektur darin, dass zwischen Client und Server eine zusätzliche Schicht eingeführt wird. Diese zusätzliche Schicht enthält die Anwendungslogik. Auf diese Weise entsteht ein Applikationsserver. Die Archtitektur kommt insbesondere bei web-basierten Anwendungen häufig zum Einsatz. Der Client selbst führt keinen Datenbank-Zugriff durch. Seite 9 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Seite 10 von 104 Dipl.-Ing. (FH) D. Schiffer Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 1.6 Fragestellungen bei Datenbankprogrammierung Das Client-Server-Modell deckt eine sehr große Bandbreite der Datenbank-Architekturen ab. Zur näheren Erläuterung der Datenbank-Zugriffe ergeben sich diverse konzeptionelle Fragen: 1. Wie verwendet das Anwendungsprogramm die Funktionalität der Datenbank? Welche Rolle spielt dabei die Programmiersprache des Anwendungsprogramms? Durch welche Mechanismen löst der Prozess des Anwendungsprogramms eine Aktion des Datenbank-Servers aus? 2. Wie findet der Datenaustausch zwischen Anwendung und Datenbank statt? Z.B. ein Suchkriterium, das der Datenbank mitgeteilt wird, oder ein Ergebnisfeld, das die Datenbank der Anwendung liefert. Wie werden die Daten ausgetauscht? Welche Typumwandlungen finden statt? Wie werden NULL-Werte der Datenbank im Datenaustausch behandelt? Wie erfährt die Anwendung von Fehlern und Warnungen des Datenbank-Prozesses? 3. Wie wird eine Ergebnismenge der Datenbank an die Anwendung übergeben? Eine Datenbank kann eine (u.U. sehr große) Ergebnismenge liefern. Programmiersprachen, wie die des Anwendungsprogramms haben in der Regel kein Mengenkonzept. 4. Zu welchem Zeitpunkt findet welche Aktion der Datenbank statt? Welche Reihenfolge von Aktionen erwartet die Datenbank bzw. wie sorgt die Schnittstelle für die Einhaltung des geforderten Vorgehens? 5. Wie kann man voll dynamische Abfragen an die Datenbank richten? Oftmals werden Anwendungen entwickelt, in denen Abfragen vorkommen, bei denen zum Zeitpunkt der Erstellung der Anwendung noch gar nicht feststeht, wie die Ergebnismenge aufgebaut ist. 6. Welche Art von Kopplung entsteht zwischen Anwendungsprogramm und Datenbank? Was passiert, wenn man das Datenbank-System austauscht? Was passiert, wenn man die Datenbank-Struktur ändert: veränderte Spalten, zusätzliche Spalten? Was passiert, wenn sich das Anwendungsprogramm ändert, etwa die Portierung auf eine andere Programmiersprache? Im Folgenden Kapitel wird auf diverse Datenbank-APIs eingegangen und fallweise auf diese Fragestellungen eingegangen. Seite 11 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 2 SQL – eine Wiederholung? So genannte Datenbank-Abfragen zur Ermittlung von Datenbankinhalten stellen den elementaren Sprachkern von SQL dar, mit der größten Bedeutung. Jede Abfrage wird durch das Schlüsselwort SELECT eingeleitet. Syntax der SELECT-Anweisung: SELECT [ALL|DISTINCT] select_item,... FROM table_specification,... [WHERE search_condition] [GROUP BY grouping_column,...] [HAVING search_condition] [ORDER BY sort_specification,...] Als weiterführende Literatur sei hier auf [1] verwiesen. Das Erstellen einfachster Abfrageformen wird an dieser Stelle vorausgesetzt! Enthält die Ausgabe nicht den Primärschlüssel einer Tabelle, so kann es vorkommen, das mehrfach dieselben Werte ausgegeben werden. Dies kann durch Angabe des Schlüsselwortes DISTINCT in der SELECT-Klausel vermieden werden. DISTINCT überschreibt das vorgegebene Verhalten (ALL) alle Einträge auszugeben. SELECT DISTINCT LASTNAME FROM EMPLOYEE; liefert alle verschiedenen Werteinträge der Spalte LASTNAME duplikatfrei Durch Angabe mehrerer Einträge in der FROM-Klausel können Inhalte aus verschiedenen Tabellen innerhalb einer Anfrage extrahiert werden: SELECT DNAME, PNUMBER FROM DEPARTMENT, PROJECT; Die Anfrage bildet das kartesische Produkt der beiden angefragten Tabellen. Aliasbildung Bei Anfragen über mehrere Tabellen kann es zu Konflikten hinsichtlich der Eindeutigkeit der Spaltenbezeichner kommen. So könnte bspw. in 2 Tabellen die Spalte NAME vorkommen. Einmal als NAME in der Tabelle AUTO und einmal als NAME in der Tabelle FAHRZEUGHALTER SELECT NAME, NAME FROM AUTO, FAHRZEUGHALTER; Als Lösung bietet SQL die Möglichkeit den Spaltennamen zusätzlich durch Voranstellung des Namens der die Spalte beherbergenden Tabelle zu qualifizieren um die erforderliche Eindeutigkeit herzustellen: SELECT AUTO.NAME, FAHRZEUGHALTER.NAME FROM AUTO, FAHRZEUGHALTER; Durch Nutzung der Möglichkeit Alternativnamen für Tabellen, sog. Aliasnamen, anzugeben Seite 12 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer ergibt sich eine in der Schreibung kompaktere Umsetzung: SELECT t1.NAME, t2.NAME FROM AUTO as t1, FAHRZEUGHALTER as t2; Gleichzeitig kann die Aliasbildung verwendet werden, um die Benennung der Spalten bei der Ausgabe zu modifizieren. Auf diesem Wege können wenig sprechende Namen oder Doppelbenennungen umgangen werden. SELECT t1.NAME as autoname, t2.NAME as haltername FROM AUTO as t1, FAHRZEUGHALTER as t2; Beschränkung der Ergebnismenge Die bisher betrachteten Anfrageformen lieferten immer die gesamten Inhalte der betrachteten Tabellen. Durch Angabe einer einschränkenden Bedingung innerhalb der WHERE-Klausel einer SELECT-Anweisung können die Tabelleninhalte hinsichtlich einer Bedingung gefiltert werden. SELECT NAME, BIRTHDAY FROM FAHRZEUGHALTER WHERE NAME="HUGO"; Durch die Filterung der Ergebnistupel werden alle diejenigen Datenbankeinträge, welche die getroffene Bedingung nicht erfüllen ausgeblendet. Dieser Vorgang wird als Selektion bezeichnet. Neben den einfachen Vergleichsoperationen können durch den LIKE-Operator unscharfe musterbasierte Suchen ausgedrückt werden. Die Musterausdrücke werden dabei aus den tatsächlich in der Ergebnisemenge erwarteten Zeichen ergänzt um Metazeichen mit besonderer Bedeutung zusammengesetzt. Hierbei stehen „%“ zur Stellvertretung einer (möglicherweise leeren) Menge beliebiger Zeichen und „_“ zur Stellvertretung genau eines Zeichens zur Verfügung. SELECT NAME, BIRTHDAY FROM FAHRZEUGHALTER WHERE NAME="H%"; Kombination von Einzelbedingungen Zur Selektion nach mehreren Bedingungen können diese mit den logischen Operationen AND, OR und NOT kombiniert werden. SELECT NAME, BIRTHDAY FROM FAHRZEUGHALTER WHERE NAME="H%" AND SALARY > 50000; Mittels der Verknüpfungsoperatoren können auch einige der zuvor gezeigten Vergleichsoperatoren ausgedrückt werden. Vergleichsoperator Alternative Schreibweise mit Bedingungsverknüpfung a BETWEEN b and c (a >= b) AND (a <= c) x IN (a, b, c) (x = a) OR (x = b) OR (x = c) Seite 13 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer JOINS / Verbünde Häufig besteht der Wunsch Werte aus verschiedenen Tabellen nicht nur gemeinsam abzufragen und anzuzeigen, sondern auch inhaltlich in Beziehung zu setzen. Im Beispiel wird versucht durch die Abfrage der Tabellen EMPLOYEE und DEPARTMENT die Namen der Mitarbeiter mit der (in der anderen Tabelle abgelegte) Bezeichnung der Abteilung verbunden. Aufgrund der Bildung des kartesischen Produkts werden jedoch alle theoretisch möglichen Kombinationen geliefert und nicht die Untermenge der tatsächlich existierenden Paarungen. SELECT FNAME, DNAME FROM DEPARTMENT, EMPLOYEE; Innerer Verbund / Inner Join Durch (geschickte) Nutzung der WHERE-Bedingung, die Werte aus beiden Tabellen miteinander in Beziehung setzt, gelingt jedoch die gewünschte Ermittlung: SELECT FNAME, DNAME FROM DEPARTMENT AS d, EMPLOYEE as e WHERE d.DNUMBER = e.DNO; Diese Abfrage liefert lediglich diejenigen Tupel, für die das in EMPLOYEE abgespeicherte DNO-Attribut einen Wert enthält, der auch in der Spalte DNUMBER der Tabelle DEPARTMENT auftritt. Es handelt sich um einen sog. Inneren Verbund oder Inner-Join Definition: Innerer Verbund Ein Innerer Verbund enthält die selektierten Daten aller beteiligten Tabellen, welche die formulierte Einschränkungsbedingung erfüllen. Der SQL-Standard gibt für diese besondere Anfrageform eine eigene Syntax vor: SELECT FNAME, DNAME FROM DEPARTMENT AS d INNER JOIN EMPLOYEE AS e ON d.DNUMBER = e.DNO; Die Bildung von Verbünden ist nicht auf die Angabe verschiedener Tabellen beschränkt, sondern kann auch durch mehrfache Bezugnahme auf dieselbe Tabelle geschehen: SELECT e1.FNAME as Chef, e2.FNAME as Mitarbeiter FROM EMPLOYEE AS e1, EMPLOYEE AS e2 WHERE e1.SSN = e2.SUPERSSN; Selbstverständlich können Joins auch über mehrere Tabellen getätigt werden. SELECT FNAME, DNAME, PNAME FROM EMPLOYEE AS e, DEPARTMENT AS d, PROJECT AS p, WORKS_ON AS w WHERE d.DNUMBER = e.DNO AND e.SSN = w.ESSN AND w.PNO = p.PNUMBER; Die Anfrage liefert die Familiennamen (Tabelle EMPLOYEE) sowie die Abteilungen denen der Mitarbeiter zugeordnet ist (aus Tabelle DEPARTMENT) sowie die durch den Mitarbeiter bearbeiteten Projekte (Tabelle PROJECT). Die Tabelle PROJECT kann jedoch nicht direkt in den Verbund einbezogen werden, da sie über keine geeigneten Attribute (d.h. Attribute die mit derselben Semantik in einer der beiden anderen Tabellen auftreten) verfügt. Daher wird zusätzlich die Tabelle WORKS_ON in die Anfrage miteinbezogen, weil sie mit dem Attribut ESSN ein Attribut bietet, welches die in Seite 14 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer EMPLOYEE enthaltene Attribut SSN als Fremdschlüssel beinhaltet. Ausgehend hiervon kann eine Bedingung unter Einbezug von PROJECT formuliert werden. Für Verbünde ist die Bildung durch ausschließliche Nutzung des Gleichheitsoperators innerhalb der WHERE-Klausel keineswegs zwingend, wenngleich diese sog. Equi Joins eine häufige Anwendungsform darstellen. SELECT FNAME, DNAME FROM EMPLOYEE JOIN DEPARTMENT ON DEPARTMENT.DNUMBER < EMPLOYEE.DNO; Das Beispiel zeigt einen durch Nutzung des kleiner-Operators gebildeten Verbund, der alle Abteilungen enthält, in denen ein Mitarbeiter (noch) nicht arbeitet und deren Abteilungsnummer größer ist als die Nummer der Abteilung welcher der Mitarbeiter gegenwärtig zugeordnet ist. (Mögliche semantische Deutung: Liste möglicher Beförderungen, sofern größere Abteilungsnummern einen Aufstieg codieren. Äußere Verbunde Neben der Möglichkeit durch innere Verbünde Tupel die über Attribute mit übereinstimmenden Wertbelegungen zu selektieren besteht durch äußere Verbünde die Möglichkeit neben den Tupeln mit übereinstimmenden Werten alle Tupel einer am Verbund beteiligten Tabelle vollständig zu selektieren. Definition: Äußerer Verbund Ein Äußerer Verbund enthält die selektierten Daten aller beteiligten Tabellen, welche die formulierte Einschränkungsbedingung erfüllen, sowie alle Daten der „äußeren“ Tabelle. Die nicht mit Werten belegbaren Felder werden durch NULL aufgefüllt. Konzeptionell wird zwischen linken und rechten Äußeren Verbünden unterschieden. Die „Seite“ des Verbundes gibt diejenige beteiligte Tabelle an, die im Rahmen der Verbundbildung vollständig ausgegeben wird. Folgendes Beispiel zeigt einen linken äußeren Verbundes: INSERT INTO EMPLOYEE VALUES("John", "X", "Doe", "999999999", "196503-04", "42 XYZ Street", "M", 50000, NULL, NULL); SELECT FNAME, DNAME FROM EMPLOYEE LEFT OUTER JOIN DEPARTMENT ON DEPARTMENT.DNUMBER = EMPLOYEE.DNO; Das Beispiel fügt zunächst einen Tupel zur Tabelle EMPLOYEE hinzu, der keiner Abteilung zugeordnet ist. In einem Inneren Verbund erscheint dieser Tupel daher nicht. Der linke Äußere Verbund des Beispiels hingegen umfaßt alle Tupel aus EMPLOYEE sowie die Werte der hinsichtlich der Bedingung DEPARTMENT.DNUMBER = EMPLOYEE.DNO ermittelten Übereinstimmungen in DEPARTMENT. Für die nicht ermittelbaren Übereinstimmungen werden NULL-Werte erzeugt. Dieses Beispiel veranschaulicht einen rechten äußeren Verbund: INSERT INTO DEPARTMENT VALUES("New Dept.", 0, 888665555, NULL); SELECT FNAME, DNAME FROM EMPLOYEE RIGHT OUTER JOIN DEPARTMENT ON DEPARTMENT.DNUMBER = EMPLOYEE.DNO; Seite 15 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Auch hier wird zunächst einen Datensatz eingefügt; diesmal in die Tabelle DEPARTMENT, der zu keinem Tupel in EMPLOYEE in Beziehung steht. Analog dem linken Äußeren Verbund liefert der rechte Äußere Verbund alle Tupel der rechtsstehenden Tabelle (DEPARTMENT) sowie die mit EMPLOYEE übereinstimmenden. Kreuzverbund Der Kreuzverbund liefert alle gemäß den Gesetzen des kartesischen Produkts bildbaren Kombinationen aus Tupeln der beitragenden Relationen: SELECT DNAME, PNUMBER FROM DEPARTMENT CROSS JOIN PROJECT; Das Ergebnis diesen Beispiels entspricht dem folgender Abfrage: SELECT FNAME, DNAME FROM DEPARTMENT, EMPLOYEE; Wird beim Kreuzverbund eine Bedingung angegeben, so entspricht er dem Inneren Verbund: SELECT FNAME, DNAME FROM DEPARTMENT CROSS JOIN EMPLOYEE WHERE DEPARTMENT.DNUMBER = EMPLOYEE.DNO; Weitere Informationen zu Joins finden Sie unter http://www.onjava.com/lpt/a/4443#1 oder http://www.onjava.com/pub/a/onjava/2004/01/07/SQLJoins.html Sortierungen Zur Sortierung hinsichtlich einer oder mehrerer Spalten der als Anfrageergebnis ermittelten Tabelle steht die ORDER BY-Klausel zur Verfügung. CREATE TABLE Person( Vorname VARCHAR(10), Nachname VARCHAR(10)); INSERT INSERT INSERT INSERT INSERT INSERT INSERT INSERT INSERT INTO INTO INTO INTO INTO INTO INTO INTO INTO Person Person Person Person Person Person Person Person Person VALUES("Adam", "C-Mann"); VALUES("Cesar", "C-Mann"); VALUES("Berta", "C-Mann"); VALUES("Adam", "A-Mann"); VALUES("Cesar", "A-Mann"); VALUES("Berta", "A-Mann"); VALUES("Adam", "B-Mann"); VALUES("Cesar", "B-Mann"); VALUES("Berta", "B-Mann"); SELECT * FROM Person ORDER BY Nachname; Ist die Sortierung bezüglich mehrerer Attribute, d.h. Sortierung innerhalb eines gleicher Attributwerte hinsichtlich eines anderen Attributs, gewünscht, so können auch mehrere Sortierattribute in der ORDER BY-Klausel versammelt werden. Zusätzlich zeigt das Beispiel die Kurzschreibweise, welche die zu sortierenden Attribute nicht namentlich benennt, sondern nur hinsichtlich ihrer Position innerhalb der SELECT-Klausel referenziert. Seite 16 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer SELECT * FROM Person ORDER BY Nachname, Vorname; Vorgabegemäß erfolgt die Sortierung aufsteigend (ascending). Die Umkehrung der Sortierreihenfolge kann durch nachstellen der Zeichenfolge DESC (für descending) nach dem Namen des Sortierattributes erreicht werden. Die aufsteigende Vorgabesortierung (ASC) wird üblicherweise nicht ausgeschrieben. SELECT * FROM Person ORDER BY Nachname ASC, Vorname DESC; Unteranfragen Bisher wurden Anfragen lediglich auf Tabellen in ihrer Rolle als in der Datenbank abgelegte Eingabemengen betrachtet. Die relationale Sichtweise erfordert jedoch keineswegs, dass die Eingangswerte einer Anfrage direkt aus der Datenbank gelesen werden müssen. Sie können auch Ergebnis einer weiteren Anfrage sein. Anfragen die vor einer anderen Anfrage ausgeführt werden müssen um für diese Eingangswerte zu liefern werden daher als Unterabfragen (subqueries, nested queries oder einfach als Sub-Selects) bezeichnet. Das folgende Beispiel zeigt eine solche Unteranfrage die alle Projektnummern liefert welche Projekten zugeordnet sind die in der durch Smith geleiteten Abteilung bearbeitet werden. Eine zweite Unterabfrage des Beispiels liefert alle Nummern von Projekten an denen dieser Mitarbeiter selbst arbeitet. Die durch diese Abfrage gelieferten Daten (Projektnummern) sind Eingangsdaten in die Ermittlung der Projektnamen. SELECT DISTINCT PNAME FROM PROJECT WHERE PNUMBER IN ( SELECT PNUMBER FROM PROJECT AS p, DEPARTMENT AS d, EMPLOYEE AS e WHERE e.SSN = d.MGRSSN AND d.DNUMBER = p.DNUM AND e.LNAME="Smith" ) OR PNUMBER IN ( SELECT PNO FROM WORKS_ON AS w, EMPLOYEE AS e WHERE w.ESSN = e.SSN AND e.LNAME="Smith" ); Das nächste Beispiel zeigt den Vergleich eines Einzelwertes (SALARY) mit einer Menge gelieferter Werte. Die Anfrage ermittelt diejenigen Mitarbeiter, deren Einkommen höher liegt als das Einkommen aller Mitarbeit in Abteilung Nummer 5. (Hinweis es wird nicht ermittelt ob das Einkommen größer ist als die Summe aller Einkommen der Mitarbeiter aus Abteilung 5, sondern nur ob das Einkommen größer ist als jedes Einzeleinkommen eines Mitarbeiters aus Abteilung 5.) SELECT LNAME, FNAME FROM EMPLOYEE WHERE SALARY > ALL (SELECT SALARY FROM EMPLOYEE WHERE DNO=5); Seite 17 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Korrelierte Unteranfragen Eine besondere Form der Unteranfragen stellen solche dar, die sich in ihrer WHERE-Klausel auf die äußere Anfrage beziehen. Diese Form der Anfrageschachtelung wird auch als korrelierte Unteranfrage bezeichnet. SELECT e.FNAME, e.LNAME FROM EMPLOYEE AS e WHERE e.SSN IN (SELECT ESSN FROM DEPENDENT WHERE e.SEX = SEX); Diese Anfrage ermittelt alle Verwandten (DEPENDENT), die das selbe Geschlecht haben wie der in der Tabelle EMPLOYEE erfasste Mitarbeiter. Jede korrelierte Unterabfrage kann durch Umschreibung in eine nicht-korrelierte Fassung überführt werden. So lautet die Formulierung der obigen Abfrgae ohne geschachtelte Unterabfrage: SELECT e.FNAME, e.LNAME FROM EMPLOYEE AS e, DEPENDENT AS d WHERE e.SSN = d.ESSN AND e.SEX = d.SEX; Die Formulierung als geschachtelte Unterabfrage ist damit nicht zwingend notwendig, kann jedoch aus Gründen der Übersichtlichkeit gewünscht sein. Die nähere Betrachtung der Anfragen aus den beiden Beispielen zeigt, dass die aus der Tabelle DEPENDENT angefragten Daten lediglich zur Formulierung der Bedingung, nicht jedoch zur Ausgabe herangezogen werden. Daher lässt sich die Bedingung unter Verwendung des EXISTS-Operators umschreiben zu: SELECT e.FNAME, e.LNAME FROM EMPLOYEE AS e WHERE EXISTS ( SELECT * FROM DEPENDENT WHERE e.SSN = ESSN AND e.SEX = SEX); EXISTS liefert den Boole'schen Wahrheitswert immer dann, wenn die (Unter-)Abfrage eine nichtleere Menge ist, d.h. Daten enthält. Anfragen die EXISTS oder IN beinhalten können auch durch linke Äußere Verbünde ausgedrückt werden: SELECT e.FNAME, e.LNAME FROM EMPLOYEE AS e LEFT JOIN DEPENDENT AS d ON e.SSN = d.ESSN AND e.SEX = d.SEX WHERE d.SEX IS NOT NULL; Eine ähnliche Funktion wie die EXISTS-Operation stellt ANY bereit, jedoch liefert diese die durch die Unterabfrage angefragten Tupel zurück um sie an eine Bedingung zu knüpfen. Nachfolgendes Beispiel zeigt die Ermittlung der Namen derjenigen Mitarbeiter, die mehr als irgendein beliebiger Manager verdienen. SELECT FNAME FROM EMPLOYEE WHERE SALARY > ANY (SELECT SALARY FROM EMPLOYEE WHERE SSN IN (SELECT SUPERSSN FROM EMPLOYEE)); Seite 18 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Aggregatfunktionen und Gruppierung Über die Sortierung hinausgehend ist oftmals ein bestimmte Anordnung der durch eine Anfrage ermittelten Ergebnistupel gewünscht, etwa als inhaltliche Gruppierung. Gleichzeitig sind oft quantitative Aussagen über Eigenschaften der Resultatmenge - wie größter oder kleinster Wert sowie Summen- oder Durchschnittsbildung - gewünscht. Das Beispiel zeigt die Ermittlung der Summe aller Gehälter (SQL-Funktion SUM) sowie des Maximal- (MAX), Minimal- (MIN) und Durchschnittsgehalts (AVG) für die Mitarbeiter der Research-Abteilung. Die genannten SQL-Funktionen werden als Aggregierungsfunktionen bezeichnet, da sie die durch die Abfrage ermittelten Einzelwerte (d.h. die Einträge der Spalte SALARY) jeweils zu genau einer Aussage verdichten. SELECT SUM(SALARY), MAX(SALARY), MIN(SALARY), AVG(SALARY) FROM EMPLOYEE, DEPARTMENT WHERE DNO = DNUMBER AND DNAME="Research"; Mit der Funktion COUNT steht eine Möglichkeit zur Ermittlung der Mächtigkeit einer Tupelmenge zur Verfügung. SELECT COUNT(*) FROM EMPLOYEE, DEPARTMENT WHERE DNO = DNUMBER AND DNAME = "Research"; zeigt ihre Verwendung zur Ermittlung der Anzahl der Mitarbeiter der mit Research bezeichneten Abteilung. Als Argument der COUNT-Funktion kann mit DISTINCT ein Schlüsselwort angegeben werden, welches die ausschließliche Zählung verschiedener Werte erwirkt. SELECT COUNT(DISTINCT SALARY) FROM EMPLOYEE; Diese Anfrage ermittelt durch Nutzung dieses Schlüsselwortes die Anzahl der verschiedenen Werte in der Spalte SALARY. Häufig wird, wie in folgendem Beispiel gezeigt, eine Anfrage zur Ermittlung der Anzahl als Unterabfrage formuliert und in der umgebenden Hauptabfrage mit einer Bedingung versehen. SELECT LNAME, FNAME FROM EMPLOYEE WHERE (SELECT COUNT(*) FROM DEPENDENT WHERE SSN=ESSN) >= 2; Neben den bisher gezeigten aggregierten Aussagen über eine Gesamtmenge besteht oftmals der Wunsch nach von Ermittlung Aussagen dieses Stils über bestimmte Werteklassen innerhalb der betrachteten Gesamtmenge. Hierzu dienen Gruppierungen der Ausgangsmenge, auf welche dann die verschiedenen Aggregierungsfunktionen separat angewandt werden können. SELECT d.DNAME AS "Abteilung", COUNT(*) AS "Anzahl Mitarbeiter", AVG(SALARY) AS "Durchschnittsgehalt" FROM EMPLOYEE AS e, DEPARTMENT AS d WHERE e.DNO = d.DNUMBER GROUP BY DNO; Ermittelt die Mitarbeiteranzahl pro Abteilung sowie der Berechnung des abteilungsinternen Seite 19 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Durchschnittsgehalts. Zur Realisierung wird die GROUP BY-Klausel verwendet, welche die Angabe eines oder mehrerer Attribute zulässt anhand der die selektierte Menge partitioniert werden soll. Die folgende Abfrage zeigt die Nutzung einer Verbundbedingung innerhalb einer Gruppierungsanfrage, die Projektnummer und -name sowie vermöge der COUNT-Funktion die Anzahl der das Projekt bearbeitenden Mitarbeiter ermittelt. SELECT PNUMBER, PNAME, COUNT(*) AS "Anzahl Mitarbeiter" FROM PROJECT, WORKS_ON WHERE PNUMBER = PNO GROUP BY PNUMBER, PNAME; Durch zusätzliche Angabe der HAVING-Klausel kann die Menge der Gruppierungsresultate mittels einer Bedingung beschränkt werden. SELECT PNUMBER, PNAME, COUNT(*) FROM PROJECT, WORKS_ON WHERE PNUMBER = PNO GROUP BY PNUMBER, PNAME HAVING COUNT(*) > 2; Die Abfrage ermittelt dieselben Resultat wie die Abfrage zuvor, jedoch nur für Projekte deren Mitarbeiteranzahl größer 2 ist. Die formulierte Beschränkung wirkt sich nicht auf die zur Berechnung herangezogene Grundgesamtheit, sondern lediglich auf die Ausgabe der Gruppierungsergebnisse aus, die vor der Auswertung der in der HAVING-Klausel formulierten Bedingung berechnet werden müssen. Zur Beschränkung der zur Berechnung heranzuziehenden Grundgesamtheit steht auch unter Nutzung der GROUP BY-Klausel der durch WHERE formulierte Bedingungsteil der SELECTAnfrage zur Verfügung. SELECT PNUMBER, PNAME, COUNT(*) FROM PROJECT, WORKS_ON, EMPLOYEE WHERE PNUMBER = PNO AND SSN = ESSN AND DNO=5 GROUP BY PNUMBER, PNAME; Gruppierungsschritte können auch in Unterabfragen auftreten, wie im folgenden Beispiel zur Ermittlung des Abteilungsnamens und der Anteil der darin arbeitenden Personen mit einem Gehalt über 40000 für alle Abteilungen mit mindestens 2 Mitgliedern zeigt: SELECT DNAME, COUNT(*) FROM DEPARTMENT, EMPLOYEE WHERE DNUMBER = DNO AND SALARY > 4000 AND DNO IN ( SELECT DNO FROM EMPLOYEE GROUP BY DNO HAVING COUNT(*) > 2) GROUP BY DNUMBER; Seite 20 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Befehle zur Datenmanipulation Die Befehle INSERT, UPDATE und DELETE und deren Anwendung werden als bekannt vorausgesetzt. INSERT INSERT INTO tbl_name (col_name,...)? VALUES(constant|NULL ...) UPDATE UPDATE tbl_name SET col_name=expression, ... [WHERE search_condition] DELETE DELTE FROM tbl_name [WHERE search_condition] Seite 21 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 3 Probleme bei Datenbankprogrammierung 3.1 Die Coddschen Regeln Die Entwicklung von SQL zielt darauf ab, den Konzepten des relationalen Datenbankmodells zu entsprechen. Dr. Codd veröffentlichte 12 Regeln (inklusive Regel 0 eigentlich 13), die eine relationale Datenbank im strengen Sinne definieren. Diese hierin erhobenen Forderungen sind jedoch so streng, dass sie bis heute kein System vollständig erfüllt. Die Regeln sind nachfolgend mit ihren englischsprachigen Originalbezeichnungen wiedergegeben, da sich für sie bisher keine eindeutige und allgemeinverständliche deutsche Übersetzung etablieren konnte. Regel 1: The Information Rule (Darstellung von Informationen): Alle Informationen in einer relationalen Datenbank (einschließlich Namen von Tabellen und Spalten) sind explizit als Werte in Tabellen darzustellen. Regel 2: Guaranteed Access Rule (Zugriff auf Daten): Jeder gespeicherte Wert muss über Tabellenname, Spaltenname und Wert des Primärschlüssels zugreifbar sein, wenn der zugreifende Anwender über hinreichende Zugriffsrechte verfügt. Regel 3: Systematic Treatment of Null Values (Systematische Behandlung von Nullwerten): Nullwerte müssen datentypunabhängig zur Darstellung fehlender Werte unterstützt werden. Systematisch drückt hierbei aus, dass Nullwerte unabhängig von denjenigem Datentyp für den sie auftreten gleich behandelt werden. Regel 4: Dynamic On-line Catalog Based on the Relational Model (Forderung nach einem Online-Datenkatalog (data dictionary) in Form von Tabellen): Die Datenbank und ihre Inhalte werden in einem sogenannten Systemkatalog auf derselben logischen Ebene wie die Daten selbst - also in Tabellen - beschrieben. Demzufolge läßt sich der Katalog mit Hilfe der Datenbanksprache abfragen. Regel 5: Comprehensive Data Sublanguage Rule (Abfragesprache): Für das DBMS muss mindestens eine Sprache existieren durch die sich die verschiedenen Inhaltstypen (Tabelleninhalte, Sichten, Integritätsstrukturen (Schlüsselbeziehungen, Wertebereichseinschränkungen, Aufzählungstypen) sowie Zugriffsrechte) definieren lassen. Regel 6: View Updating Rule (Aktualisierung von Sichten): Sofern theoretisch möglich, müssen Inhalte von Basistabellen auch über deren Sichten änderbar sein. Regel 7: High-level Insert, Update, and Delete (Abfragen und Bearbeiten ganzer Tabellen): Innerhalb einer Operation können beliebig viele Tupel bearbeitet werden, d.h. die Operationen werden grundsätzlich mengenorientiert ausgeführt. Hierfür ist eine so abstrahierte Sicht dieser Operationen notwendig, dass keinerlei Information über die systeminterne Darstellung der Tupel notwendig ist. Regel 8: Physical Data Independence (Physikalische Datenunabhängigkeit): Änderungen an der internen Ebene dürfen keine Auswirkungen auf die auf den abgespeicherten Daten operierenden Anwendungsprogramme besitzen. Seite 22 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Werden Daten demnach reorganisiert oder beispielsweise durch Indexe zugriffsbeschleunigt, so darf eine solche Änderung die auf die Datenbank zugreifenden Anwendungsprogramme nicht beeinträchtigen. Regel 9: Logical Data Independence (Logische Datenunabhängigkeit): Änderungen des konzeptuellen Schemas dürfen keine Auswirkung auf die Anwedungsprogramme besitzen, solange diese nicht direkt von der Änderung betroffen sind. Regel 10: Integrity Independence (Unabhängigkeit der Integrität): In Verfeinerung der fünften Regel wird gefordert, dass alle Integritätsbedingungen ausschließlich durch die Sprache des DBMS definieren lassen können müssen. Definierte Integritätsbedingungen müssen in Tabellen abgespeichert werden und durch das DBMS zur Laufzeit abgeprüft werden. Im Mindesten werden folgende Forderungen durch verfügbare Systeme unterstützt: Kein Attribut welches Teil eines Primärschlüssels ist darf NULL sein. Ein Fremdschlüsselattribut muss als Wert des zugehörigen Primärschlüssels existieren. Regel 11: Distribution Independence (Verteilungsunabhängigkeit): Die Anfragesprache muss so ausgelegt sein, dass Zugriffe auf lokal gehaltene Daten identisch denen auf verteilt gespeicherte Daten formuliert werden können. Hieraus lässt sich auch die Ausdehnung der Forderungen nach logischer und physischer Datenunabhängigkeit für verteilte Datenbanken ableiten. Regel 12: Nonsubversion Rule (Kein Unterlaufen der Abfragesprache): Definiert ein DBMS neben der High-level Zugriffssprache auch eine Schnittstelle mit niedrigerem Abstraktionsniveau, dann darf durch diese keinesfalls eine Umgehung der definierten Integritätsregeln möglich sein. Zusätzlich fasst Codd in Regel 0 nochmals die Anforderungen dahingehend zusammen, dass er postuliert, alle Operationen für Zugriff, Verwaltung und Wartung der Daten ausschließlich mittels relationaler Fähigkeiten abzuwickeln. Derzeit existiert kein am Markt verfügbares kommerzielles System welches alle zwölf Regeln vollständig umsetzt. Darüber hinaus greifen die Coddschen Regeln nicht alle Gesichtspunkte des praktischen Datenbankeinsatzes auf. So bleiben Fragestellungen des Betriebs (wie Sicherungs-, Wiederherstellungs- und Sicherheitsaspekte) eines DBMS völlig ausgeklammert. Seite 23 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 3.2 Der „Impedance Mismatch“ 3.2.1 Was ist der Impedance Mismatch? Objektorientierte Programmiersprachen wie C++, C#, Visual Basic, Delphi oder Java stellen Sprachelemente bereit, die nicht nur geeignet sind Benutzungsoberflächen zu entwickeln, sondern ebenso, logische Probleme anzugehen. Allerdings müssen auch OO-Entwickler zur dauerhaften Speicherung ihrer Daten auf einen Hintergrundspeicher – in den meisten Fällen eine relationale Datenbank – zugreifen. Die grundlegende Problematik des Impedance Mismatch entsteht durch die fehlende einfache Abbildung von objektorientierten Strukturen auf das Datenmodell relationaler Datenbankmanagementsysteme (RDBMS) und umgekehrt. • Relationale Datenbanken arbeiten mittels SQL immer auf Mengen von Tupeln, objektorientierte Programmiersprachen hingegen verarbeiten Objekt für Objekt. • Die “Struktur” wird durch die Datenbank vorgegeben, “Verhalten” und „Logik“ weitgehend von Anwendungsprogrammen (Programmiersprache) vorgegeben. • Datenbank und Programmiersprache verwenden unterschiedliche Datentypen (z.B. Möglicherweise: Integer auf DB-Seite ≠ Integer in Programmiersprache) • unterschiedliche Behandlung transienter und persistenter Objekte Nicht jedes Objekt, das durch die Programmiersprache im Arbeitsspeicher erzeugt wird, wird auch zwangsläufig persistent in der Datenbank gespeichert. Es muss eine separate Logik zur Speicherung der Objekte geschaffen werden. Die Überwindung des Paradigmenbruchs ist komplex und kaum ohne Reibungsverluste möglich. Diese äußern sich nicht nur in einer längeren Zeit bis zur Marktreife der Applikation sowie höheren Kosten für Design, Entwicklung und Qualitätssicherung, sondern beeinträchtigen auch die Wartung und Erweiterbarkeit des Codes sowie die Performance der Anwendungen. Seite 24 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 3.2.2 Objekte versus Tabellen Das relationale Modell beschreibt alle Daten anhand eines standardisierten Formats von flachen Tabellen mit Zeilen und Spalten. Komplexe Datenstrukturen benötigen zumeist eine Vielzahl von Tabellen, deren Beziehungen untereinander (1:1, 1:N und M:N) auf Fremdschlüsseln basieren. Vorgänge, die die Geschäftslogik betreffen, werden anhand von Quellen außerhalb der Tabellen – beispielsweise über eingebetteten SQL-Code, statische, vordefinierte Stored Procedures oder Trigger – zugewiesen. Um effiziente Anwendungen zu entwickeln, ist eine tiefgehende Kenntnis der Tabellen, ihrer Beziehungen untereinander und der darin verankerten Stored Procedures erforderlich. Im Gegensatz dazu sind die Klassen im objektorientierten Modell in sich abgeschlossene Einheiten. Wie relationale Tabellen enthalten auch sie Informationen (Attribute). Allerdings können hier zusammenhängende Daten (etwa eingebettete Klassen und Collections) in der ContainerKlasse gespeichert werden, die kein Fremdschlüsselkonstrukt wie im relationalen Modell benötigt. Ein weiterer bedeutender Unterschied ist, dass die Geschäftslogik beim objektorientierten Modell nicht von den Daten losgelöst ist. Stattdessen implementiert jede Klasse ihre eigenen Methoden, die den Code für sämtliche Operationen auf den Attributen ihrer Klasse enthalten. Die Methoden ihrerseits stellen Schnittstellen bereit, über die sie von außen aufgerufen werden können. Eine Überführung der Daten vom einen in das andere Modell muss vom Applikationsentwickler dem Anwendungsfall entsprechend programmiert werden. Seite 25 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 3.2.3 Die unterschiedlichen Paradigmen in der Praxis Nachfolgend werden die Unterschiede zwischen objektorientiertem und relationalem Modell anhand einer Anwendung zur Registrierung von Kraftfahrzeugen im Entwurf und der Kodierung einer Datenbank aufgezeigt. Ein Auto verfügt über bestimmte Merkmale wie Hersteller, Modell, Ausstattung, Baujahr, Fahrzeugidentifikationsnummer (VIN) etc. Es hat einen oder mehrere Besitzer, einen oder mehrere Fahrer, eine Reparaturhistorie und muss registriert werden. Um die Daten eines KFZ im Objektmodell darzustellen, legen wir die Klassen Car, Person, Owner, Driver und RepairHistory an. Die Klassen Owner und Driver erben die Attribute und Methoden der Klasse Person. Diese lassen sich bei Bedarf mit eindeutigen Attributen und Methoden erweitern. Owner, Driver und RepairHistory werden als Collections in der Klasse Car betrachtet – etwa als Collection von Verweisen bei den ersten beiden und als eingebettete Instanzen für letztere. Für eine praxisbezogenere Gestaltung geben wir an, dass ein Owner viele Cars besitzen und ein Car mehrere Owner aufweisen kann. Um diese M:N-Beziehung zu implementieren, kann die Klasse Owner eine Collection von Verweisen auf Car enthalten. (Wie oben angeführt, hat Car bereits eine Collection von Owner.) Wir können auch eine M:N-Beziehung für Car und Driver implementieren. Schließlich geben wir eine Methode für die Klasse Car namens RegisterNew() an, die mit dem Parameter VIN aufgerufen wird. Die gesamte Arbeit, die für die Registrierung eines neuen KFZ erforderlich ist, kann hinter dieser Schnittstelle erledigt werden. Um das gleiche Beispiel im relationalen Modell darzustellen, müssen, wie gezeigt, mehrere Tabellen erstellt werden. Man beachte, dass zusätzliche Tabellen (CarDrivers und CarOwners) erforderlich sind, um die M:N-Beziehung zwischen Owner, Driver und Car abzubilden. Die Zuordnung von Klassen zu einer oder mehreren Tabellen erfolgt durch die Abbildung der Klassen-Attribute auf Felder der Tabellen innerhalb der Datenbank. Eine 1:1 Abbildung ist hierbei in den seltensten Fällen möglich. Weitere Informationen finden Sie unter [7]. Seite 26 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 3.3 Cursor-Konzept Cursor - „Current Set of Records“ Ein Cursor ermöglicht die tupelweise Verarbeitung der Ergebnismenge einer Datenbankabfrage. Die Verarbeitung einer Anfrage mittels Cursor erfolgt in vier Phasen: Ein Cursor kann also verstanden werden als: • Iterator über eine Liste von Tupeln • Hauptspeicherbereich zur Verarbeitung von SQL-Anweisungen • SQL-Objekt, das mit einem bestimmten Tabellenausdruck (als Ergebnis einer Abfrage) verbunden ist Cursoroperationen im Überblick: – Cursor deklarieren (Zuordnung des Select-Statements) – Cursor öffnen (Tupel der Anfrage werden zur Verfügung gestellt) – Operation fetch auf dem geöffneten Cursor verwenden (Zugriff auf ein Tupel des Cursors und Datentransfer in das Anwendungsprogramm sowie Weitersetzen des Cursor-Zeigers) – Cursor schließen (Speicherplatzfreigabe) – (positioniertes update und delete - in Bezug auf das gerade im Zugriff befindliche Tupel - …where current of …) Seite 27 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 3.4 Andere Lösungsansätze zur Überwindung des Impedance Mismatch Um die Lücke zwischen objektorientierten Anwendungen und einer relationalen Datenbank zu schliessen, gibt es drei Lösungsansätze: Objektorientierte Erweiterungen relationaler Datenbanken Die Hersteller relationaler Datenbankmanagementsysteme haben große Anstrengungen unternommen Ihre Datenbanken in Richtung Objektorientiertheit zu erweitern – die Lücke wirklich geschlossen haben sie allerdings nicht. Einsatz von Mapping-Tools Zur Abbildung von Objekten auf Tabellen einer relationalen Datenbank werden zahlreiche Mapping-Tools angeboten. Die Abbildungsvorschriften sind jedoch nach wie vor vom Applikations-Entwickler anzugeben. Der einzige Unterschied ist der, dass nun für die Abbildung nicht mehr programmiert sondern lediglich konfiguriert werden muss. Meist sind die Abbildungsvorschriften in einer speziellen, toolspezifischen XML-Syntax zu verfassen. Einige dieser Mapping-Tools sind beispielsweise: Hibernate - http://www.hibernate.org/ - für Java, .Net - für Java, .Net, Ruby iBatis - http://ibatis.apache.org/ Einführung einer objektorientierten oder postrealtionalen Datenbank Statt der Verwendung einer relationalen Datenbank kann auch eine objektorientierte Datenbank eingesetzt werden. Diese Art der Datenbanken konnte sich jedoch bisher nicht durchsetzen. Das einzige postrelationale Datenbanksystem ist Cache von InterSystems. Es verspricht die Vorteile beider Welten – der relationalen und der objektorientierten – zu vereinen. Einen großen Verbreitungsgrad hat dieses Datenbankmanagementsystem jedoch trotz des interessanten Ansatzes nicht gefunden. Seite 28 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 4 Zugriff auf Datenbanken 4.1 Allgemein Von einer DB-API werden typischerweise folgende Funktionen erwartet: • Verbindungsaufbau zu einer Datenbank • Datenbankverbindung schließen • SQL-Anweisung an DB schicken, sofort ausführen • SQL-Anweisung an DB schicken, analysieren und übersetzen • Datensätze holen, Cursor (zur zeilenweisen Verarbeitung der Ergebnismengen) => vgl. Impedance Mismatch • Transaktionssteuerung • Statusabfrage Seite 29 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 4.2 Verarbeitung einer SQL-Anfrage Eine SQL-Anfrage wird prinzipiell in Folgenden Schritten von einer SQL-Datenbank abgearbeitet: 1. Parsen des Statements: Überprüfung, ob es sich um ein syntaktisch korrektres SQLStatement handelt. 2. Validierung: Überprüfung des SQL-Statements gegenüber dem Datenbank-Katalog. D.h. Prüfung, ob verwendete Tabellen existieren, ggf. Überprüfung von Berechtigungen. 3. Optimierung: Analyse der Zugriffsstrategie und Auffinden eines optimalen Vorgehens. Verwendung von Indices, Umstellung von Joins,etc. Aus dieser Optimierung entsteht ein Zugriffsplan („binäre Form des SQL-Statements“) 4. Ausführung des Statements und Ermittlung der Ergebnismenge bzw. Modifizierung der Daten. Verarbeitung einer SQL-Abfrage Seite 30 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 5 Datenbankprogrammierung innerhalb der Datenbank Stored Procedures & Trigger Stored Procedures (SP’s) oder auch User Defined Functions (UDF’s) gennant 5.1 Was sind Stored Procedures? • In der Datenbank gespeicherte Programmteile • Datenbankobjekte • Vom DB-Benutzer definiert • Werden im DB-Server-Prozess ausgeführt • Erweitern die Funktionalität des DBMS 5.2 Wozu dienen Stored Procedures? • SP’s dienen der Erweiterung der Funktionalität von DBMSn - wenn die Möglichkeiten von SQL nicht genügen - und wenn eine Verarbeitung am Server sinnvoller ist • Trigger dienen dazu: - um die Daten der DB konsistent zu halten - um mehr semantische Informationen zu definieren (z.B. Bei Abbuchung vom Girokonto: Prüfung ob der Kreditrahmen auf dem Konto überschritten wird, wenn der gewünschte Betrag abgebucht wird – ACHTUNG: Viele Trigger machen den DB-Zugriff langsam!) Mehr zum Thema Trigger siehe [8] http://www.dbmsmag.com/9605d17.html • Methoden von Objekten - bei objekt-relationalen DBn 5.3 Wer bietet Stored Procedures? Folgende DBMS bieten Stored Procedures: • IBM DB2 • Microsoft SQL-Server • Oracle • PostgreSQL • Sybase • MySQL (eingeschränkt seit Version 5) Stored Procedures werden nicht unterstützt von: • Dateisystem Datenbanken (z.B. Access) Seite 31 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 5.4 Wann sollten Stored Procedures verwendet werden? • Sobald eine Stored Procedure (SP’s) ein wenig komplexer wird ist sie meist abhängig vom verwendeten Datenbank-Management-System. SP’s in Oracle sind also beispielsweise nicht kompatibel zu SP’s in DB2! Verwenden Sie Stored Procedures also nur dann, wenn Sie sicher sind mit Ihrer Applikation nur ein DBMS unterstützen zu müssen! • Stored Procedures bringen oft einen kleinen Performance-Vorteil, weil die SP nur einmalig „kompiliert“ werden muss und danach in binärer Form in der DB liegt. Führt man eine SP aus kann daher direkt bei „Schritt 4. - der Ausführung“ (vgl. Kapitel 4.2 Verarbeitung einer SQL-Anfrage) begonnen werden. Bei den meisten modernen Rechnerarchitekturen spielt dies jedoch keine ausschlaggebende Rolle, da in den meisten höheren Programmiersprachen auch sog. „Prepared Statements“ unterstützt werden, die den selben Vorteil bieten – optimieren Sie daher lieber an anderer Stelle! • SP’s sind von Vorteil, wenn Sie Ihre Anwendung von der darunter liegenden Datenstruktur (Tabellen) entkoppeln wollen – hierfür waren SP’s früher immer das Mittel der Wal und gehörten zu sauberem Applikationsdesign oft dazu. Heute verwendet man oft innerhalb der Applikation einen dünnen Mapping Layer, der die Business-Objekte aus der Ebene der Geschäftslogik von der Datenbank entkoppelt (Hierzu später mehr). • Die Verwendung von SP’s ist eine grundsätzliche Designentscheidung – und eine Glaubensfrage! 5.5 Stored Procedures / Trigger (bei Oracle) 5.5.1 Allgemein • Programmiersprache bei Oracle: PL/SQL o Procedural Language / Structured Query Language o proprietäre Programmiersprache der Firma Oracle o mittlerweile von den meisten anderen DBMS-Herstellern übernommen o Verbindet den Komfort einer prozeduralen Sprache mit der Geschwindigkeit von SQL o Unterstützt Variablen, Bedingungen, Schleifen, Ausnahmen o typenstrenge Programmiersprache o Oracle PL/SQL Tutorial unter: [11] http://www.datenbank-plsql.de [12] http://www.orafaq.com/faq/plsql/ [13] http://www.oracle10g.de/oracle_10g_documentation/appdev.101/b10807/toc.htm • • • • • Programmiersprache bei DB: SQL PL Programmiersprache bei MS-SQL-Server und Sybase: Transact SQL C, C++ und JAVA sind auch möglich Cursor verbinden SQL mit prozeduraler Verarbeitung Besondere Konstrukte für Datenzugriff - for cursor loop - Datensatztyp-Deklaration - Collection-Typen Seite 32 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten 5.5.2 Aufbau von Stored Procedures • Deklarationsteil: - Deklaration von Variablen, Cusor, Collections, … • Ausführungsteil: - Programmierung des Algorithmus - Transaktionssteuerung • Ausnahmebehandlung - Reaktionen auf Fehler werden hier zusammengefasst - Damit wird GOTO vermieden 5.5.3 Beispiel (PL/SQL) create or replace function Leihe (resID in number)) return varchar2 as varKundeID varModellID varBeginn varEnde varStartKm NUMBER;; NUMBER;; DATE; DATE; DATE; DATE; NUMBER;; varKennzeichen CHAR( ); CHAR(10); CURSOR curReservierung IS SELECT KundeID,, ModellID,, Beginn,, Ende FROM Reservierung WHERE ID = resID;; cursor curCar is select KMSTAND,, KENNZEICHEN from auto,, reservierung where auto.modell= =reservierung.modellid and reservierung.id = resID and auto.kennzeichen not in ( select kennzeichen from leihvertrag,, auto,, reservierung where auto.modell= =reservierung.modellid and reservierung.id = resID and auto.kennzeichen= =leihvertrag.autoid and leihvertrag.ende> >sysdate ); BEGIN /* Reservierung mit der ReservierungsID holen */ OPEN curReservierung;; FETCH curReservierung INTO varKundeID,, varModellID,, varBeginn,, varEnde;; Seite 33 von 104 Dipl.-Ing. (FH) D. Schiffer Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer /* Freies Auto holen */ OPEN curCar;; FETCH curCar INTO varStartKm,, varKennzeichen;; /* Prüfen ob die abgerufene Reservierung mit dem heutigen Datum übereinstimmt */ if to_char((varBeginn,,'dd.mm.yyyy')= )=to_char( (sysdate,,'dd.mm.yyyy')) then )= if curCar% %rowcount <= 0 then return ('Kein freies Auto !!!'); ); end if; if; /* Auto ist verfügbar - reservierung in leihe umwandeln */ /* Eintrag in Leihvertrag einfügen */ Insert Into leihvertrag (id,, kundeid,, autoid,, beginn,, ende,, startkm)) values( values(leihseq.nextval,, varKundeID,, varKennzeichen,, varBeginn,, varEnde,, varStartKm); ); /* Reservierungseintrag löschen */ Delete From reservierung where id = resID;; return varKennzeichen;; else ); return ('Reservierung ist nicht von Heute !!!!'); end if; if; END; END; 5.5.4 Ausnahmebehandlung Benutzerdefinierte Ausnahmen: declare ausnahme1 exception; … begin … raise ausnahme1 … exception when ausnahme1 then <aktion> end; Daten-Ausnahmen (vordefiniert), z.B. when data_not_found when max_open_cursors Seite 34 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 5.5.5 Aufbau von Triggern (PL/SQL) CREATE [OR REPLACE]] TRIGGER <trigger_name> > {BEFORE| BEFORE|AFTER} AFTER} {INSERT| INSERT|DELETE| DELETE|UPDATE} UPDATE} ON <table_name> > [REFERENCING [NEW AS <new_row_name>] >] [OLD AS <old_row_name>]] >]] [FOR EACH ROW [WHEN (<trigger_condition>)]] >)]] (< <trigger_body> > 5.5.6 Beispiel (PL/SQL) CREATE TRIGGER mitarbeiter_trig_gehalt_biu BEFORE INSERT OR UPDATE OF gehalt ON mitarbeiter FOR EACH ROW -- Triggername mitarbeiter_trig_gehalt_biu (_biu steht für B-efore I-nsert U-pdate ) -- Tabelle: MITARBEITER -- Trigger soll nur ziehen, wenn Gehalt kleiner 5000 ist oder Neues Gehalt kleiner altem Gehalt -- in der folgenden WHEN erfolgt der Zugriff auf :old und :new OHNE vorangestellte : WHEN (new.gehalt <= 5000 or new.gehalt < old.gehalt)) DECLARE v_mitarbeiter_name VARCHAR2((255):= ):=null ):=null; null; -- Cursor holt "Name, Vorname" von Mitarbeitertabelle -- Explizite Definition des Cursors hat -- Performance-Vorteile gegen "select into <variable>" im Trigger-Body cursor c_name is :select name|| ||', '|| ||vorname || || from mitarbeiter where mitarbeiternummer = :NEW.MITARBEITERNR;; BEGIN -- Mitarbeiter-Name ermitteln und in Variable v_mitarbeiter_name speichern open c_name;; fetch c_name into v_mitarbeiter_name;; close c_name;; -- Bei Gehalt kleiner 5000 Exception auslösen --> Fehler wird in der Regel bis zur Anwendung hochgereicht -- Exception-Nr -20000 - -29999 können selbst definiert werden IF :NEW.GEHALT <= 5000 THEN RAISE_APPLICATION_ERROR(( -20901,, 'Manager Gehalt für Mitarbeiter '|| ||v_mitarbeiter_name|| ||' || || zu klein!' ); END IF; IF; IF :NEW.GEHALT < :OLD_GEHALT THEN THEN RAISE_APPLICATION_ERROR(( -20902,, 'Gehaltskürzung für Mitarbeiter '|| ||v_mitarbeiter_name|| ||' || || nicht erlaubt!' ); END IF; IF; END; END; / Seite 35 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 5.6 Erstellen von Stored Procedures Für die Erstellung von SP’s und Triggern greift man unter Oracle am besten auf das kommerzielle Toad der Firma Quest (http://www.toadsoft.com/ ) oder den Oracle SQLDeveloper (http://www.oracle.com/technology/software/products/sql/index.html ) zurück. Letzterer muss leider separat von der Oracle Seite heruntergeladen werden, hierfür ist allerdings eine Registrierung notwendig. Ansonsten ist der Einsatz kostenlos. Handbuch des Oracle SQL-Developers: http://download-uk.oracle.com/docs/cd/B32485_01/doc/appdev.110/b31695/toc.htm Seite 36 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 5.7 Aufruf und Ablauf von Stored Procedures 5.7.1 Aufruf einer Stored Procedure aus dem Client-Programm (z.B. Java, JDBC) vgl. hierzu: [9] http://www.onjava.com/pub/a/onjava/2003/08/13/stored_procedures.html … public static void setDeathAge((Poet dyingBard,, int age)) throws SQLException { Connection con = null; null; CallableStatement proc = null; null; try { con = connectionPool..getConnection(); (); proc = con..prepareCall(("{ call set_death_age(?, ?) }"); proc..setString((1,, dyingBard..getName(()); proc..setInt((2,, age); ); proc..execute(); (); } finally { try { proc..close(); (); } catch (SQLException e)) {} con..close(); (); } } …. 5.7.2 Stored Procedure auf dem DB-Server create procedure set_death_age((poet VARCHAR2,, poet_age NUMBER)) poet_id NUMBER;; begin SELECT id INTO poet_id FROM poets WHERE name = poet;; INSERT INTO deaths (mort_id,, age)) VALUES (poet_id,, poet_age); ); end set_death_age; Seite 37 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 6 Java Database Connectivity (JDBC) 6.1 Allgemein • Ist ein Call Level Interface (CLI) (wie ODBC), d.h. Sammlung von Methode / Klassen (vgl. S. 4) • Definiert eine SQL „Schnittstelle“ für Java (kein Interface im Sinne von Java) • Ist Basis für höhere Programmierschnittstellen (Hibernate, SQLJ - Embedded SQL für Java) • Ein JDBC-Treiber ist eine spezielle Implementierung des JDBC-Interface • Austauschbarkeit des Datenbanksystems wird vereinfacht • JDBC-spezifische Klassen und Interfaces sind im Paket java.sql definiert • Unterschiede zu ODBC: Methoden statt Funktionen, Objekte statt Handles (kurz: objektorientiert, statt prozedural) 6.2 Grundstruktur eines JDBC-Programms • Einbinden der JDBC-Klassen • JDBC Treiber laden • Verbindungsaufbau zur Datenbank • Eigenschaften der Verbindung spezifizieren • Zugriff auf Daten mittels SQL • Ergebnisverarbeitung mittels Cursor • Ausnahme- und Fehlerbehandlung • Verbindungsabbau (Wichtig! – niemals vergessen) Seite 38 von 104 R e i h e n f o l g e Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 6.3 Einbinden der JDBC-Klassen Import java.sql.*; Die wichtigsten Klassen / Interfaces aus dem Package java.sql.* Treibermanager zur Verwaltung von Treibern Repräsentiert einen bestimmten Treiber Repräsentiert eine DB Verbindung SQL Anweisung Ergebnis nach Ausführung einer SQLAnweisung ResultSetMetaData Metainformationen über Tabellen, Attribute DatabaseMetaData Metainformationen Datenbankverbindung SQLException Bei fehlerhaften SQL-Anweisungen DriverManager Driver Connection Statement ResultSet Seite 39 von 104 Class Interface Interface Interface Interface Interface Interface Exception Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 6.4 Architektur JDBC implementiert das Bridge-Pattern nach GOF [14]: Die eigentliche Applikation als Client im oberen Bild dargestellt verwendet lediglich die abstrakte Schnittstelle, die darunterliegende Implementation ist der Anwendung selbst egal • JDBC spezifiziert nur Schnittstellen • Implementation durch DBMS-Hersteller: Treiber • Treibermanager lädt Treiber • Treiber stellt Verbindung zur Datenbank her Seite 40 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten • Es gibt 4 Treibertypen • Typ 3 und 4 sind vorzuziehen: pure JAVA Seite 41 von 104 Dipl.-Ing. (FH) D. Schiffer Datenbankprogrammierung Hochschule Ravensburg-Weingarten 6.5 Verbindungsaufbau zur Datenbank JDBC unterscheidet 4 verschiedene Treibertypen: Typ 1: JDBC-ODBC Bridge • • • Verwendung eines externen ODBCTreibers Muß mit der Java-Anwendung installiert werden Übersetzt Java Methodenaufrufe in Prozeduraufrufe der ODBC C-Bibliothek Typ 2: Native Java-Library Treiber • • JDBC-Treiber, der anstelle eines ODBCTreibers auf einen datenbankspezifischen Treiber zugreift z.B. Oracle8 OCI-Treiber, der Net8 verwendet Typ 3: JDBC-Net Treiber • • • • keine direkter Datenbankzugriff Zugriff über „Middleware“ = Datenbankgateway Treiber ist vom Datenbankgateway abhängig Nützlich z.B. zur Umgehung einer Firewall Typ 4: Native-Protocol Treiber • • • Vollständig in Java implementierter Treiber Wird vom DBMS-Hersteller zur Verfügung gestellt (z.B. Oracle8 Thin Driver) Direkte Kommunikation mit dem DBMS Seite 42 von 104 Dipl.-Ing. (FH) D. Schiffer Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Die Klasse java.sql.DriverManager Driver registerDriver (Driver driver) Connection getConnection (String url, String user, String password) Registriert einen (neuen) nicht-ODBC-Treiber beim Treibermanager. Dieser Treiber wird zum Verbindungsaufbau verwendet Baut über den registrierten Treiber eine Verbindung zur Datenbank auf. Datenbank Klassenname für den JDBC-Treiber DB2/Derby com.ibm.db2.jcc.DB2Driver Oracle oracle.jdbc.driver.OracleDriver mSQL COM.imaginary.sql.msql.MsqlDriver MySQL com.mysql.jdbc.Driver org.gjt.mm.mysql.driver früher: Borland JDataStore com.borland.datastore.jdbc.DataStoreDriver Borland Interbase interbase.interclient.Driver Adabas D de.sag.jdbc.adabasd.ADriver SYBASE ASE com.sybase.jdbc.SybDriver IDS Server ids.sql.IDSDriver Datenbank Subprotokoll Beispiel-URL Derby derby:net jdbc:derby:net://localhost:1527/ ODBC-Datenquellen odbc jdbc:odbc:Pflanzen mSQL msql jdbc:msql://host:1234/database Interbase interbase jdbc:interbase://localhost/dabase.gdb Oracle Thin oracle:thin jdbc:oracle:thin:@host:1243:database Oracle Fat oracle:oci8 jdbc:oracle:oci8:@database IBM DB2 Db2 jdbc:db2://database Sybase sybase:Tds jdbc:sybase:Tds:host:1234/database MySQL mysql jdbc:mysql://host/database An Hand der unterschiedlich aufgebauten URL erkennt der Driver-Manager, welcher der datenbankspezifischen Treiber geladen und verwendet werden soll. Das Interface java.sql.DatabaseMetaData String getURL() String.getDriverName() String.getDriverVersion() Boolean.supportsGroupBy () Liefert die Datenbank-URL Liefert den Treibernamen Liefert die Treiberversion Überprüft, ob die Datenbank die SQL Group ByKlausel unterstützt Seite 43 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 6.5.1 Laden eines JDBC Treibers 6.5.2 Verbindung zur Datenbank: Connection Objekt erzeugen Beispiel: // Aufbau der Verbindung DriverManager.registerDriver ( new oracle.jdbc.driver.OracleDriver ()); // oder: Class.forName("oracle.jdbc.driver.OracleDriver"); // Vorteil dieser // Variante, man kann //den String z.B. aus //einer Konfiguration // einlesen String url = "jdbc:oracle:thin:@<host>:<port>:<database>"; // oder: String url = "jdbc:oracle:oci8:@<database>"; Connection con = DriverManager.getConnection (url,“Scott”, “Tiger”); // Abfrage von Metadaten DatabaseMetaData dma = con.getMetaData(); System.out.println(“Connected to “+ dma.getURL()); System.out.println(“Driver: “+ dma.getDriverName()); System.out.println(“Version: “+ dma.getDriverVersion()); Seite 44 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Aufgabe: Vervollständigen Sie folgende Klasse unter der Annahme, dass der Datenbankhost jeweils der aktuelle Rechner und der Datenbankname „testdatabase“ ist. Was für ein Design-Pattern implementiert die Klasse ConnectionHelper? import java.sql.*; public class ConnectionHelper { public final static String DB2 = "db2"; public final static String ORACLE = "oracle"; public final static String MYSQL = "mysql"; private ConnectionHelper() { super(); } public static synchronized Connection getConnection(String dbtype) throws ClassNotFoundException, SQLException { Connection con = null; String url = null; String user = null; String pwd = null; if (dbtype.equals(DB2)) { Class.forName(__________________________________); url = "jdbc:db2://_______"; user = "fridolin"; pwd = "sicher"; } else if (dbtype.equals(ORACLE)) { Class.forName(__________________________________ url = "jdbc:oracle:thin:@_______:1243:_______"; user = "scott"; pwd = "tiger"; } else if (dbtype.equals(MYSQL)) { Class.forName__________________________________); url = "jdbc:mysql://_______/_______"; user = "ich"; pwd = "privat"; } If (______________) // nur dann Verbindung aufbauen, wenn Parameter // passt con = DriverManager.getConnection(url, user, pwd); } return con; } } Seite 45 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 6.6 Datenzugriff mittels SQL • • • • • • • • • • Statements sind die Arbeitspferde in JDBC Mit ihnen werden Daten manipuliert und gesichtet Es gibt 3 Typen mit verschiedenen Aufgaben Statement o Zur Datenmanipulation und zum Erzeugen von ResultSets. PreparedStatement o Precompiled Statement PreparedStatement pstmt = connnection.prepareSatement( "INSERT INTO emp VALUES (?,?,?,?,?);"); CallableStatement o zum Ausführen von Stored Procedures Statement kann nicht mit new erzeugt werden, sondern das Connection-Objekt dient als Factory für das Statement Es wird bei einem Connection Objekt angefordert Dadurch bleibt der Bezug zur Connection bestehen Beispiel: Statement stmt = connection.createStatement() 6.6.1 Dynamisches SQL mit Statments Die komplette SQL-Anweisung wird bei jeder Ausführung durch den Precompiler des DBMS erneut verarbeitet. Das Interface java.sql.Statement ResultSet executeQuery(String sql) Integer executeUpdate(String sql) Boolean execute(String sql) Erlaubt ausschließlich die Ausführung von SELECT-Statements INSERT, UPDATE oder DELETE Statements und SQL DDL Statements erlaubt Beliebiges SQL Statement erlaubt Beispiel: Statement stmt = con.createStatement(); // SELECT Statement: Menge von Tupeln ist Ergebnis ResultSet rs = stmt.executeQuery(“SELECT name, salary FROM emp WHERE name LIKE ‘Smith’ AND salary > 1000”); // UPDATE Statement: Ergebnis (count) ist Anzahl // der Änderungen oder 0 für Statements // die kein Ergebnis zurückliefern Integer count = stmt.executeUpdate(“UPDATE emp SET sal = sal * .95“); Seite 46 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 6.6.2 Vorkompiliertes SQL Die SQL-Anweisung wird unter Angabe von Parametern nur einmal kompiliert und anschließend gespeichert. Bei jeder Ausführung müssen nur noch die Parameter eingestellt werden. Dadurch führt die Verwendung der Klasse PreparedStatement in der Regel zu einer besseren Performance, denn die SQL-Anweisungen, die mittels execute(), 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 (vgl. dynamisches SQL bei ODBC & Embedded SQL). JDBC unterstützt eine Vorübersetzung, die sich Prepared Statements nennt. Vorbereitet (engl. prepared) deshalb, weil 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übergesetzten Anweisungen, und die Datenbank kann sie schnell ausführen, da sie in einem optimalen Format vorliegen. Der Geschwindigkeitsvorteil macht sich immer dann besonders bemerkbar, wenn Schleifen Änderungen an Tabellenspalten vornehmen. Das kann durch die vorbereiteten Anweisungen schneller geschehen. Das Interface java.sql.PreparedStatement implementiert im Wesentlichen dieselben Methoden wie das Interface java.sql.Statement. Seite 47 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Genauso wie ein Connection-Objekt eine Methode für ein Statement-Objekt anbietet, werden PreparedStatement-Objekte angelegt. Dazu dient die Methode prepareStatement(). Als Argument wird eine SQL-Zeichenkette übergeben, die den gleichen Aufbau wie etwa ein executeUpdate() hat. Unterschied: Bei den normalen Statement-Objekten können dynamische Einträge mit in den SQl-Stament-String eingebaut werden, indem einfach der SQL-String entsprechend konkateniert wird. Bei vorbereiteten Anweisungen ist dies nicht mehr möglich. Damit jetzt auch eine vorbereitete Anweisung Parameter enthalten kann, werden in die Zeichenkette Platzhalter mit einem Fragezeichen eingefügt. Die Ersetzung dieser Platzhalter im Statement erfolgt der Reihenfolge nach. Seite 48 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Beispiel: // SELECT Statement String sql = “SELECT name, salary FROM emp WHERE name LIKE ? AND salary > ?“; PreparedStatement prepStmt = con.prepareStatement(sql); prepStmt.setString(1, “Smith“); prepStmt.setInt(2, 1000); ResultSet rs = prepStmt.executeQuery(); // UPDATE Statement String sql = “UPDATE emp SET sal = sal * ? WHERE ename = ?“; PreparedStatement prepStmt = con.prepareStatement(sql); Float ratio = 0.95; String name = “Smith”; prepStmt.setFloat(1, ratio); // setzen der Parameter prepStmt.setString(2, name); Integer count = prepStmt.executeUpdate(); Seite 49 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 6.7 Ergebnisverarbeitung mittels ResultSet Auch in JDBC ist das Problem des „Impedance Mismatch“ nach wie vor präsent. D.h. ein SQL-Befehl liefert eine Menge von Zeilen als Ergebnis. Im Java-Programm müssen die Zeilen einzeln nacheinander verarbeitet werden. 6.7.1 ResultSets Zur Überwindung des „Impedance Mismatch“ wird für einen SQL-Befehl ein Cursor definiert. In Java stellt dieser Cursor ein Objekt der Klasse ResultSet dar. Der Cursor ist ein Objekt, welches die Zeilen der Ergebnismenge des SQL-Befehls als Wert annimmt. Das ResultSet wird von einem Statement erzeugt. Jedes ResultSet hat für sich einen Cursor, der auf die aktuelle Zeile der Ergebnismenge zeigt Innerhalb des ResultSet werden Typkonvertierungen zwischen SQL-Datentypen und JavaDatentypen vorgenommen: SQL-Datentyp Java-Datentyp CHAR, VARCHAR, … String, InputStream BINARY, ::: Byte[], InputStream BIT Boolean TINYINT, SMALLINT Short, int BIGINT Long, int REAL Float DOUBLE, FLOAT Double DECIMAL, NUMERIC Java.math.BigDecimal DATE Java.sql.Date Nicht verwechseln mit java.util.Date TIME Java.sql.Time TIMESTAMP Java.sql.Timestamp Wichtige Methoden der Klasse ResultSet: Boolean next() Setzt den Satzzeiger um einen Satz weiter. Initialzustand ist vor dem ersten Datensatz. XXX getXXX(int columnIndex) Liest den Wert der Spalte an der Position columnIndex des aktuellen Datensatzes aus. XXX steht für einen bestimmten Datentyp. XXX getXXX(String columnName) Wie oben, jedoch durch Angabe des Spaltennamens columnName anstelle des Index. ResultSetMetaData Liefert eine Beschreibung der aktuellen Spalte des getMetaData() ResultSet (Anzahl, Typen, Eigenschaften) Void updateXXX(int Überschreibt den Wert der Spalte an der Position columnIndex, XXX x) columnIndex des aktuellen Satzes mit dem Wert x. XXX steht für einen bestimmten Datentyp. Void updateRow() Schreibt den aktuellen Datensatz in die Datenbank. Seite 50 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 6.7.2 Ergebnisabfrage Durch Angabe des Spaltenanmen: String name; int salary; while(rs.next()) { name = rs.getString(“name“); salary = rs.getInt(“salary“); System.out.println(“Name: “+name); System.out.println(“Gehalt: “+salary); } Vorteil: Die Abfrage kann umgebaut werden (Änderung der Reihenfolge der AusgabeSpalten), der Datenzugriff funktioniert weiterhin. Durch Angabe des Spaltenindex: String name; int salary; while(rs.next()) { name = rs.getString(1); salary = rs.getInt(2); System.out.println(“Name: “+name); System.out.println(“Gehalt: “+salary); } 6.7.3 Datensätze ändern Änderung der Cursor-Werte und Update der Datenbank, über die Methode updateRow(): int curSal; while(rs.next()) { curSal = rs.getInt(“salary“); if (curSal > 2000) { curSal = curSal * 0.95; rs.updateInt(“salary“, curSal); rs.updateRow(); } } Vergessen Sie nicht, dass Sie auch direkt über ein Statement.executeUpdate Updates und Inserts ausführen können. Das gleichzeitige Lesen und Ändern der Daten ist eher unüblich. Seite 51 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 6.7.4 Datensätze einfügen Zum Einfügen eines neuen Datensatz in die Datenbank sind folgende Schritte auf dem ResultSet auszuführen: • Verwendung des „Insert“-Buffer (insertRow) • Einstellen des Satzzeigers auf die insertRow mit Methode moveToInsertRow(), mit moveToCurrentRow() wird wieder zurück zur Ergebnismenge gesprungen • Erzeugung des Datensatz mit den Methoden updateXXX(). Jede Spalte der insertRow muß belegt werden. • Aufruf der Methode insertRow() erzeugt den entsprechenden Datensatz in der Datenbank Beispiel: String newName = “Allen“; int newSal = 1250; rs.moveToInsertRow(); rs.updateString(“name“, newName); rs.updateInt(“salary“, newSal); rs.insertRow(); rs.moveToCurrentRow(); Vergessen Sie nicht, dass Sie auch direct über ein Statement.executeUpdate Updates und Inserts ausführen können. Das gleichzeitige Lesen und Ändern der Daten ist eher unüblich. Seite 52 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten 6.7.5 Dipl.-Ing. (FH) D. Schiffer Batch Updates Das Einfügen und Ändern großer Mengen von Daten kostet viel Zeit, da für jede Modifikation ein INSERT oder UPDATE über ein Statement-Objekt abgewickelt werden muss. Eine Verbesserung stellen Batch-Updates dar, die in einem Rutsch gleich eine ganze Reihe von Daten zur Datenbank transferieren. Statt mit execute() und deren Varianten zu arbeiten, nutzen wir die Methode executeBatch(). Damit zuvor die einzelnen Aktionen dem StatementObjekt mitgeteilt werden können, bietet die Klasse die Methoden addBatch() und clearBatch() an. Die Datenbank führt die Anweisungen in der Reihenfolge aus, wie sie im Batch-Prozess eingefügt wurden. Ein Fehler wird über eine BatchUpdate Exception angezeigt. Beispiel Wir fügen einige Einträge der Datenbank als Batch hinzu. con sei unser Connection-Objekt. int[] updateCounts = null; try { Statement s = con.createStatement(); s.addBatch( "INSERT INTO Lieferanten VALUES (x,y,z)" ); s.addBatch( "INSERT INTO Lieferanten VALUES (a,b,c)" ); s.addBatch( "INSERT INTO Lieferanten VALUES (d,e,f)" ); updateCounts = s.executeBatch(); } catch ( BatchUpdateException e ) { } catch ( SQLException e ) { } Nach dem Abarbeiten von executeBatch() erhalten wir als Rückgabewert ein int-Feld mit den Ergebnissen der Ausführung. Dies liegt daran, dass in der Batch-Verarbeitung ganz unterschiedliche Anweisungen vorgenommen werden können und jede davon einen unterschiedlichen Rückgabewert verwendet. Soll der gesamte Ablauf als Transaktion gewürdigt werden, so setzen wir im try-Block den AutoCommit-Modus auf false, damit nicht jede SQL-Anweisung als einzelne Transaktion gewertet wird. Im Fall eines Fehlers müssen wir im catch-Block ein Rollback ausführen. Übertragen wir dies auf das obere Beispiel, dann müssen nur die beiden Anweisungen für die Transaktion eingesetzt werden. try { con.setAutoCommit( false ); Statement s ..... ... } catch ( BatchUpdateException e ) { con.rollback(); } Seite 53 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Aufgabe: Vervollständigen Sie den Quelltext, es sollen alle Daten der Tabelle Auto selektiert werden und als Komma-separierte Liste ausgegeben werden. import java.sql.*; import de.atbits.database.ConnectionHelper; public class MeineDBAnwendung { public static void main(String[] args) { Connection dbConnection = null; try { dbConnection = ConnectionHelper .getConnection(ConnectionHelper.MYSQL); // printing some infos about connection __________________ dbmd = dbConnection.getMetaData(); System.out.println("Connected to:" + dbmd.getURL()); System.out.println("Driver:" + dbmd.getDriverName()); System.out.println("Version:" + dbmd.getDriverVersion()); System.out.println("Username:" + dbmd.getUserName()); // selecting all data from Table Auto PreparedStatement pst = dbConnection .prepareStatement("SELECT * FROM AUTO"); ResultSet rs = pst.executeQuery(); __________________ rsMD = rs.getMetaData(); int columnCount = rsMD.__________________(); while (rs.next() != false) { for (int counter = 1; counter <= ____________; ++counter) { System.out.print(rs.getString(____________)); if (____________ < _____________) { System.out.print(","); } else { System.out.println(); } } } // close all db-ressources ________.close(); ________.close(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } finally { if (dbConnection != null) { try { ________________.close(); } catch (SQLException e) { e.printStackTrace(); } } } } } Seite 54 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 6.8 Fehlerbehandlung 6.8.1 Allgemein Die bisher beschriebenen execute- get-, set- und update-Methoden erzeugen in Fehler- oder Ausnahmesituationen Instanzen der Klassen SQLException oder SQLWarning oder DataTruncation • SQLException Die Klasse SQLException ist Basisklasse aller JDBC-Exceptions. Sie enthält über den Fehler die folgenden Informationen: eine Fehlerbeschreibung sowie eine weitere Beschreibung, die den XOPEN SQL-Status (beschrieben in der SQLSpezifikation) angibt, und zuletzt eine zusätzliche Ganzzahl, die vom Datenbanktreiber kommt. • SQLWarning Die Klasse SQLWarning beschreibt keine kritischen Fehler. Es ist auch keine Exception, die geworfen wird, sondern eine Warnung, die der Programmierer explizit holen muss. Dazu dienen die Funktionen getWarnings() der Klassen Connection, ResultSet und Statement. Werden die Meldungen nicht geholt, dann werden sie mit Connection, ResultSet oder Statement überschrieben. • DataTruncation Die DataTruncation-Klasse ist ein spezieller Typ einer SQLWarnung. Sie wird immer dann erzeugt, wenn Daten während der Schreib- oder LeseOperationen verloren gingen. Die Meldung wird genauso geholt wie SQLWarning (mittels getWarning), nur muss dann, um das Ergebnis zu erfahren, mittels instanceof DataTruncation überprüft werden, ob es sich um DataTruncation handelt. Dies erfordert eine Typumwandlung (Cast) von SQLWarning auf DataTruncation. Seite 55 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Folgende Arten von Informationen stehen zur Verfügung: • SQL State: Methode String getSQLState() liefert einen 5 Zeichen umfassenden Fehlercode • Message: Methode String getMessage() liefert eine Detailbeschreibung des aufgetretenen Fehlers • Vendor Code: Methode int getErrorCode() liefert den DBMSHerstellerspezifischen Fehlercode 0 <0 >0 erfolgreiche Durchführung Fehler aufgetreten Ausnahme aufgetreten Die SQLExceptions werden wie in Java üblich als Exceptions geworfen und mittels try / catch / finally verarbeitet Beispiel: (MS-Access Datenbank) Die fehlerhafte Query „SELECT Firma FROM Kunden WHERE Ort =“ verursacht einen Fehler (SQLException). SQLState Error Message Vendor Code 37000 Syntaxfehler (fehlender Operator) in Abfrageausdruck „Ort =“ -3100 Achtung !!! Zu einem Fehler können mehrere Exceptions bzw. Warnings existieren. Diese sind hintereinander verkettet. Bei der Abarbeitung der Exceptions bzw. Warnings muß dies berücksichtigt werden. Seite 56 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 6.8.2 Abfangen von Fehlern und Ausnahmen • SQLException wird mit throw-Klausel geworfen. D.h. JDBC-Aufrufe müssen in einem try-Block stehen • Programmblock wird bei der Methode javaüblich abgebrochen, die die Exception auslöst • Abfangen der Exception und Behandlung des Fehlers erfolgt javaüblich im catchBlock (evtl. Abarbeitung mehrerer Exceptions!) • Für die Behandlungsreihenfolge mehrerer catch-Blöcke gilt: Spezieller Fall vor allgemeinem Fall (wie immer in Java) • Finally-Block: Schließen von ResultSet und Statement Faustregel: Schließen Sie immer im finally Alle offenen ResultSet’s, Statement’s und Connection’s static public void Connection con Statement stmt ResultSet rs = main (String args[]) { = null; = null; null; try { DriverManager.registerDriver ( new oracle.jdbc.driver.OracleDriver ()); String url = "jdbc:oracle:thin:@<database>"; con = DriverManager.getConnection ( url, “Scott”, “Tiger”); } catch (SQLException ex) { while (ex != null) { System.err.print(“Fehler beim Einloggen:”); System.err.println(ex.getMessage()); ex = ex.getNextException(); } return; } // Query absetzen try { stmt = con.createStatement(); rs = stmt.executeQuery( “...” ); } catch (SQLException ex) { System.err.println(“Fehler beim Absetzen des SELECT Statements:“); while (ex != null) { System.err.println(“Vendor-Code: “+ ex.getErrorCode()); System.err.println(“SQLState: “+ ex.getSQLState()); System.err.println(“Error-Message: “+ ex.getMessage()); ex = ex.getNextException(); } return; } Seite 57 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 6.8.3 Abfragen von Warnungen • Ausnahmen werden nicht mit dem throw-Befehl aufgerufen • SQLWarning betrifft ein Statement oder einen Cursor und wird dort jeweils „angehängt“ • Zugriff auf Warning mit SQLWarning getWarnings() und void clearWarnings() private static boolean checkForWarning (SQLWarning warn) throws SQLException { boolean rc = false; if (warn != null) { rc = true; System.err.println(“+++++ WARNINGS +++++“); while (warn != null) { System.err.println(“Vendor-Code: “+ warn.getErrorCode()); System.err.println(“SQLState: “+ warn.getSQLState()); System.err.println(“Error-Message: “+ warn.getMessage()); warn = warn.getNextWarning(); } } return rc; } try { stmt = con.createStatement(); rs = stmt.executeQuery( “...” ); checkForWarning(rs.getWarnings()); } catch { ... } try { while (!checkForWarning(rs.getWarnings()) && rs.next()) { System.out.println(...); ... } } Seite 58 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 6.9 Verbindungsabbau Connection-, Statement- und ResultSet-Objekte sollten am Ende einer DatenbankSitzung bzw. am Ende der Verarbeitung ihre Verbindung zur Datenbank explizit abgebrochen werden. Wird eine Connection abgebrochen, so erlischt auch die Verbindung aller Statements dieser Connection und auch aller ResultSets, sauberer ist es jedoch auch diese explizit zu schliessen Beispiel: private static closeConnection (Connection conn) { if(conn != null) { try { conn.close(); } catch (SQLException sqlEx) { // Fehlerbehandlung } } } try { // Verbindungsaufbau, Queries, Verarbeitung } catch (SQLException sqlEx) { // Fehlerbehandlung ... return; // Abbruch! } finally { // Wird auf jeden Fall ausgeführt closeConnection (con) } 6.10 Transaktionssteuerung Das allgemeine Prinzip von Transaktionen bei Datenbanken wird an dieser Stelle vorausgesetzt. Transaktionen sind für Datenbanken ein sehr wichtiges Konzept, denn nur so 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 Standardverarbeitung 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 Auto-Commit zurückgesetzt werden. Dann werden die Datenbankmanipulationen ausgeführt, und die Transaktion kann anschließend abgeschlossen (commit) oder zurückgesetzt (rollback) werden. Beispiel Datenbankoperationen sollen in einer Transaktion ausgeführt werden. con.setAutoCommit( false ); // Datenbankmanipulationen vornehmen con.commit(); con.setAutoCommit( true ); // Standard wiederherstellen Seite 59 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 6.11 Fazit Ausgehend von den anfänglichen Fragestellungen ist Folgendes zusammenzufassen: 1. Wie verwendet das Anwendungsprogramm die Funktionalität der Datenbank? Durch Klassen aus dem Package java.sql.* und entsprechende Methodenaufrufe.. 2. Wie findet der Datenaustausch zwischen Anwendung und Datenbank statt? Durch Klassen, die vom JDBC-Teiber erzeugt, und in Form von Objektinstanzen innerhalb der Anwendung zur Verfügung gestellt werden. 3. Wie wird eine Ergebnismenge der Datenbank an die Anwendung übergeben? Das Cursor-Konzept erlaubt es der Anwendung mit den mengenorientierten Daten der Datenbank umzugehen. Cursor stehen in Form von Objekten der Klasse ResultSet zur Verfügung. 4. Zu welchem Zeitpunkt findet welche Aktion der Datenbank statt? Die Reihenfolge der Aktionen, die auf der Datenbank ausgeführt werden erledigt der JDBC-Treiber, und ist für die Anwendung nicht relevant. Innerhalb der Anwendung muss lediglich die richtige Reihenfolge bei der Erzeugung von Objektinstanzen und dem Aufruf entsprechender Zugriffsmethoden eingehalten werden. 5. Welche Art von Kopplung entsteht zwischen Anwendungsprogramm und Datenbank? Die Kopplung findet ausschließlich über die Verwendung der standardisierten Schnittstelle (Klassen, Methoden) aus dem Package java.sql statt. Seite 60 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 6.12 Erweiterte Themen • Kern von JDBC soll einfach und klein sein • Standard-Erweiterungen für JDBC werden in die Standard Extension API ausgelagert. • 3 wichtige Standard-Erweiterungen für JDBC i. Java Naming and Directory Interface • Verwendung von Datenquellen: DataSource ii. Connection Pooling • Wiederverwendung von Verbindungen spart Zeit 6.12.1 JNDI & Connection Pooling Die Arbeit mit dem DriverManager sah bisher so aus, dass er mit der genauen Datenquelle parametrisiert wurde. Es musste also immer der Namen der Datenbank sowie (optional) der Benutzernamen und das Passwort angeben werden. Diese feste Verdrahtung im Quellcode ist unschön, weil Änderungen zu einer zwangsläufigen Neuübersetzung führen – was sich allerdings mit Konfigurationsdateien verändern ließe. Trotzdem stehen die Daten in jedem Fall auf der Client-Seite, wo sie nicht immer gut aufgehoben sind. Besser ist eine zentrale Stelle für die Konfigurationsdaten und auch für die Datenbank. Rüstet bspw. ein Unternehmen von MySQL auf Firebird um, so müsste für ein ClientProgramm an allen Stellen, an der der Datenbanktreiber geladen und die URL für die Datenbank aufgebaut wird, Quellcode geändert werden. Das ist äußerst unflexibel. Daher wurde in Java eine weitere Möglichkeit geschaffen, nämlich die Konfigurationsdaten an einer zentralen Stelle zu hinterlegen – und das heißt in Java Zugriff über JNDI. Im zentralen Namensdienst werden Informationen über Treibername, Datenbankname uns so weiter abgelegt und dann zum nötigen Zeitpunkt erfragt. Wenn sich die Datenbank einmal ändern sollte, dann muss nur an dieser zentralen Stelle eine Änderung eingespielt werden und alle, die anschließend den JNDI-Dienst erfragen, erhalten die neue Information. Verbindung zu einem Datengeber (es muss nicht unbedingt eine Datenbank sein) wird über ein DataSource-Objekt realisiert, was von der JDNI-Zentrale zu erfragen ist. Mit getConnection() wird anschließend das Connection-Objekt besorgt, ab dieser Stelle funktioniert es gleich, wie mit dem DriverManager. Context ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup( "jdbc/datenbank" ); Connection con = ds.getConnection( "benutzername", "passwort" ); Das javax.naming.Context-Objekt und dessen Methode lookup() erfragen das mit dem Namen assoziierte Objekt vom Verzeichnisdienst. Vorher muss natürlich irgendjemand dieses Objekt dort abgelegt – auf Neudeutsch deployen. Mit dem Verweis auf das DataSource-Objekt können wir getConnection() aufrufen und Benutzername und Passwort angeben. Seite 61 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Von der Schnittstelle DataSource gibt es drei unterschiedliche Ausführungen: • Ein Standard-DataSource-Objekt. Mindestens das muss ein JDBC-2.0 kompatibler Treiber anbieten. • Ein DataSource-Objekt, das gepoolte Datenbankverbindungen zulässt (ConnectionPool-DataSource), sodass eine beendete Verbindung nicht wirklich beendet wird, sondern nur in einen Pool zur Wiederverwendung gelegt wird. Damit er zurückgelegt werden kann, muss die Verbindung einfach nur geschlossen werden – ein Vorgang, der in jedem Programm mit Verbindungen zu finden sein sollte. • Ein DataSource-Objekt für verteilte Transaktionen (XADataSource). Das Schöne daran ist, dass der konkrete Typ verborgen bleibt, und mühelos eine Umstellung von einer einfachen DataSource auf etwa eine ConnectionPoolDataSource vorgenommen werden kann. Der Administrator ist nun dafür verantwortlich, dass das DataSource-Objekt, also die Beschreibung der Datenbank-Parameter, im Namensdienst eingetragen ist. Im Allgemeinen macht das der Container über eine XML-Beschreibungsdatei oder über eine Gui, sodass kein Programmieraufwand von Hand nötig ist. Wie die JNDI-DataSource bei Tomcat von MySQL integriert werden kann, zeigt die Webseite http://tomcat.apache.org/tomcat-5.5-doc/jndi-resources-howto.html . Oft existieren für EJB-Server grafische Dienstprogramme, mit denen sich der JNDI-Name für die Ressource setzen lässt. Für den Sun-Standard-EJB-Container ist das etwa das Programm deploytool. Bei Tomcat kann der Namensserver über das admintool administriert werden. Als alternative Möglichkeit zum Connection-Pooling bietet sich das Projekt DBCP von Apache Jakarta Commons an (http://jakarta.apache.org/commons/dbcp/ ). Hier kann der Entwickler das Connection-Pooling steuern und muss sich nicht auf JNDI verlassen. Seite 62 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 7 DAPRO - Best Practices „Fools ignore complexity, experts avoid it; geniuses remove it“ (Alan Perlis) Ich habe mir erlaubt die 11 folgenden Punkte der Autoren dadurch zu ergänzen, dass ich jeweils kursiv das Studienfach, das sich m.A. mit dem entsprechenden Punkt beschäftigt angegeben habe. 11 Best Practices aus dem Buch [16] “Struts Best Practices” für qualitative hochwertige (Web-) Anwendungen 1. Erfülle die Kundenanforderungen effizient durch solide Anforderungsspezifikationen (SWEN) 2. Spare Geld und erhöhe die Zuverlässigkeit mit effektiver Open-Source-Software (permanente Weiterbildung, Lesen von Fachliteratur, etc.) 3. Arbeite iterativ mit einem bewährten Prozess an der Entwicklung (SWEN) 4. Führe Unit-Tests durch (SWEN) 5. Verwende MVC (Model-View-Controller) (GRABO) 6. Nutze objektorientierte Programmierung, um Wiederverwendbarkeit zu maximieren (OOP, SWEN) 7. Verwende DAO (Data Access Objects) (DAPRO) 8. Verwende CRUD-Ereignisse (Create-Read-Update-Delete) (DAPRO) 9. Verwende CMA (Container Managed Authorization) (INET2, Middleware) 10. Führe Belastungstests durch (SWEN) 11. Verwende Qualitätssicherung (SWEN) Arbeiten Sie an einem Projekt? – „Machs nur einmal und gleich richtig“ Neun Schritte zu einem erfolgreichen (Web-)Projekt: 1. Bauen Sie ein (HTML-) Mockup 2. Erstellen Sie die JSP’s / JSF’s 3. Entwicklung von Page-Flow, Tiles, Navigation und Stil 4. Beans-Prototyp 5. Nur-Lesen Version 6. CRUD-Version 7. Validierung 8. Sicherheit 9. Kosmetik, z.B. Drop-Down-Elemente Seite 63 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 7.1 DAO – Data Access Objects Die Datenzugriffsschicht, das „M“ in „MVC“. Vergeuden Sie keine wertvolle Zeit und wertvolles Entwicklerwissen für das Schreiben unnötig komplexer Datenzugriffe. Die Wahl einer soliden Implementierung für den Datenzugriff ist entscheiden, da er in der Regel die Basis der gesamten Anwendung bildet. Wackelt die Basis, wackelt auch das Haus, das Sie darauf aufbauen! Das MVC-Pattern bedeutet eine strikte Trennen von Datenhaltung (Modell), Datenverarbeitung und Logik (Controller) und Darstellung (View). Das bedeutet das SQL- oder datenbankbezogener-Code nichts in Klassen zu suchen hat, die der Darstellung oder der Verarbeitung von Daten dienen – Ein solcher Code sollte immer in separate Klassen gekapselt werden! Hierzu bietet sich das Entwurfsmuster Data Access Objects an. „Einer der Hauptgründe für die Verwendung des Musters, ist es, die Datenzugriffsimplementierung vor der Geschäftslogik, die sie nutzt, zu verbergen bzw. zu kapseln. Die Verwendung des DAO-Patterns bietet verschiedene Vorteile. Erstens können die Datenzugriffsmethoden standardisiert werden, und zawr ungeachtet der Datenquelle (z.B.: eine SQL-Datenbank, ein flache Datei, XML, eine Message-Queue oder Legacy-Systeme). Entwickler können den Datenzugriff viel leichter pflegen, wenn er standardisiert ist. Das trifft auch auf Situationen zu, in denen alle Daten in einer relationalen Datenbank abgelegt sind.“ (vgl. [16]) Wenn die Anwendung zu einem späteren Zeitpunkt auf eine andere Datenbank umgestellt werden soll, so müssen Sie lediglich die DAO-Klassen anpassen. Durch Verbergen der Datenzugriffsimplementierung können Sie diese später ändern, ohne die Geschäftslogik abändern zu müssen. Die normalen Java-Beans (POJO = Plain Old Java Object) sollten nicht wissen, woher ihre Daten kommen und wohin sie gehen. Das wird durch eine getrennte DAO-Klasse, mit der die Bean kommuniziert, sichergestellt. Eine klassische Java-Bean verfügt nur über öffentliche (public) Getter- und Setter-Methoden für ihre Properties und ansonsten über nicht viel mehr. Die Kopplung der Bean mit ihrer DAO-Klasse sollte nur lose über ein Interface erfolgen, so können mehrere Klassen dasselbe Interface implementieren und für dieselbe Bean-Klasse unterschiedliche Datenzugriffsmethoden implementieren, was wiederum den Zugriff auf das DAO-Objekt über eine Factory ermöglicht. Das DAO-Pattern ist vor Allem ein Entwurfsmuster, es gibt nicht konkret die Methoden vor, die ein Interface benutzen soll. Sie können also sowohl Interface-Namen als auch die InterfaceMethoden frei wählen. Seite 64 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 7.1.1 Allgemein: (DAO-Klassendiagramm: aus [18]) (DAO-Sequenzdiagramm: aus [18]) Seite 65 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer BusinessObject Das BusinessObject repräsentiert den Daten-Client. Das Objekt benötigt Zugriff auf die DataSource um Daten zu laden und zu speichern. DataAccessObject Das DataAccessObject ist das Hauptobjekt dieses Entwurfsmusters. Das DataAccessObject abstrahiert von der unterliegenden Datenzugriffsstrategie für das BusinessObject, um einen transparenten Zugriff auf die Datenquelle zu gewährleisten.Das BusinessObject delegiert ausserdem das Laden und die Speicherung der Datenan das DataAccessObject. DataSource Repräsentiert eine Datenquellen-Implementierung, das kann ein RDBMS, OODBMS, XML Repository, das Dateisystem, etc. sein. Eine Datenquelle kann auch ein anderes System sein (legacy/mainframe), ein Dienst (B2B Service oder Kreditkarten-Dienst), oder eine andere Art Repository (LDAP). TransferObject Repräsentiert ein Transfer Object, das als Datenträger verwendet wird. Das DataAccessObject kann das Transfer Object verwenden, um Daten zum Client zu übertragen. Das DataAccessObject kann auch dazu verwendet werden, um Daten vom Client an ein TransferObject zu übertragen, um die Daten in der Datenquelle zu aktualisieren. Factory for Data Access Objects Strategy Das DAO Entwurfsmuster kann hochflexibel umgesetzt werden, indem es mit einem der Pattern Abstract Factory [GoF] oder der Fabrik-Methode [GoF] kombiniert werden. (Implementierung der Factory für die DAO Strategie durch Verwendung einer Fabrikmethode aus [17]) Seite 66 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer (Implementierung der Factory für dieDAO Strategie durch Verwendung einer Abstrakten Fabrik aus [17]) 7.1.2 Am Beispiel: Kundeninformation (Implementierung des DAO Entwurfsmusters aus [17]) Seite 67 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Eine typisches DAO-Interface sieht so aus: // Interface that all CustomerDAOs must support public interface CustomerDAO { public int insertCustomer(...); (...); public boolean deleteCustomer(...); (...); public Customer findCustomer(...); (...); public boolean updateCustomer(...); (...); public RowSet selectCustomersRS(...); (...); public Collection selectCustomersTO(...); (...); ... } Die hierzu passende konkrete DAO-Implementierung für Cloudscape: // Cloudscape concrete DAO Factory implementation import java..sql.*; .*; public class CloudscapeDAOFactory extends DAOFactory { public static final String DRIVER= = "COM.cloudscape.core.RmiJdbcDriver";; public static final String DBURL= = "jdbc:cloudscape:rmi://localhost:1099/CoreJ2EEDB";; // method to create Cloudscape connections public static Connection createConnection() () { // Use DRIVER and DBURL to create a connection // Recommend connection pool implementation/usage } public CustomerDAO getCustomerDAO() () { // CloudscapeCustomerDAO implements CustomerDAO return new CloudscapeCustomerDAO(); (); } public AccountDAO getAccountDAO() () { // CloudscapeAccountDAO implements AccountDAO return new CloudscapeAccountDAO(); (); } public OrderDAO getOrderDAO() () { // CloudscapeOrderDAO implements OrderDAO return new CloudscapeOrderDAO(); (); } ... } Seite 68 von 104 Dipl.-Ing. (FH) D. Schiffer Datenbankprogrammierung Hochschule Ravensburg-Weingarten Und die DAO-Factory: // Abstract class DAO Factory public abstract class DAOFactory { // List of DAO types supported by the factory public static final int CLOUDSCAPE = 1;; public static final int ORACLE = 2;; public static final int SYBASE = 3;; ... // There will be a method for each DAO that can be // created. The concrete factories will have to // implement these methods. public abstract CustomerDAO getCustomerDAO(); (); public abstract AccountDAO getAccountDAO(); (); public abstract OrderDAO getOrderDAO(); (); ... public static DAOFactory getDAOFactory(( int whichFactory)) { switch (whichFactory)) { case CLOUDSCAPE:: return new CloudscapeDAOFactory(); (); case ORACLE : return new OracleDAOFactory(); (); case SYBASE : return new SybaseDAOFactory(); (); ... default : return null; null; } } } Seite 69 von 104 Dipl.-Ing. (FH) D. Schiffer Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 7.1.3 Vorteile Ermöglicht Transparenz: Das Business Objekt kann die Datenquelle benutzen ohne spezifische Details über sie oder ihre Implementierung zu wissen. Der Zugriff ist transparent, weil die Implementierungs-Details innerhalb des DataAccessObjekts gekapselt und versteckt sind. Enables Easier Migration Ein DAO-Layer macht es einfacher eine Applikation von einer auf eine andere Datenquelle zu migrieren. Die Business-Objekte haben keinerlei Kenntnis von der unterliegenden Datenschicht. Daher betrifft der Wechsel der Datenquelle lediglich den DAO-Layer. Weiterhin ist es möglich, wenn ein Factory-Pattern verwendet wird, eine konkrete FactoryImplementierung für jede Datenquelle zu implementieren. In diesem Fall muss für die Migration auf eine neue Datenquelle lediglich eine weitere konkrete Fabrik implementiert werden. Reduzierung der Code-Komplexität in den Business-Objekten Weil die DAO’s die gesamte Komplexität des Datenzugriffs kapseln, wird der Code in den BusinessObjects vereinfacht. Aller datenbankspezifische Code in der Applikation (wie SQL) wird in den DAO’s und nicht den BusinessObjekten gekapselt. Das verbessert die Les- und Wartbarkeit des Codes. Zentralisiert den Datenzugriff in einem separaten Layer Weil alle Daten-Zugriffs Operationen an DAO’s delegiert werden, kann die separate Datenzugriffsschicht als die Ebene betrachtet werden, die den Rest der Appliaktion vom Datenzugriff entkoppelt. Dies macht die Applikation einfacher verwaltbar. Natürlich muss diese extra schicht designed und entwickelt werden, die Vorteile überwiegen jedoch den teilweisen Mehraufwand. Bei Verwendung eine Factory-Strategie erhöht sich der Design und Implementierungsaufwand nochmals, im Gegenzug erhält man die o.g. Vorteile. Weiterführendes auch: • http://www.torsten-horn.de/techdocs/sw-patterns.htm • http://www.corej2eepatterns.com/Patterns2ndEd/DataAccessObject.htm • http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html Seite 70 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Überblick über gängige Entwurfsmuster im Geschäftsapplikationsumfeld: Seite 71 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 8 Embedded SQL 8.1 Allgemein • Datenbank-Zugriffe werden in die Programmiersprache eingebettet, indem eine spezielle Syntax verwendet wird. Gewissermaßen wird die Programmiersprache um weitere Sprachkonstrukte erweitert. • Variablen der Programmiersprache können auch für die Datenbank verwendet werden, indem sie als solche deklariert werden. Innerhalb von Datenbank-Statements werden diese Variablen mit einem Doppelpunkt „:“ als solche gekennzeichnet. • Quellen mit embedded SQL sind keine gewöhnlichen Quellen. Beispielsweise könnte ein C-Compiler normalerweise keine SQL-Sprachkonstrukte übersetzen. Ein spezieller Präprozessor muß diese Konstrukte verarbeiten. 8.2 Beispiel #include <stdio.h> EXEC SQL INCLUDE SQLCA; void main( void ) { EXEC SQL BEGIN DECLARE SECTION; char Author[81]; char Title[81]; char ISBN[14]; EXEC SQL END DECLARE SECTION; EXEC SQL CONNECT TO "azamon" USER "dba" IDENTIFIED BY "sql"; strcpy( ISBN, "0-87930-480-4" ); EXEC SQL SELECT Author, Title, ISBN INTO :Author, :Title, :ISBN FROM Books WHERE ISBN = :ISBN; printf( "%s: %s ISBN %s\n", Author, Title, ISBN ); EXEC SQL DISCONNECT ALL; } Seite 72 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 8.3 Übersetzung eines Programms mit embedded SQL geschieht im Wesentlichen durch folgende 5 Schritte: 1. Programmquelle wird durch den SQL Präprozessor verarbeitet. Der Präprozessor analysiert die Quelle und bearbeitet das eingebettete SQL (Anweisungen die mit „EXEC SQL“ beginnen). 2. Präprozessor erzeugt 2 Ausgabedateien: Eine Ausgabedatei, in der die Aufrufe von „EXEC SQL“ durch Funktionsaufrufe der jeweiligen Datenbank ersetzt wurden. Die zweite Datei ist ein sogenanntes Datenbank Abfrage Modul (nicht bei allen DBn). 3. Die vorkompilierte C-Programmquelle wird wie jede andere C-Quelle kompiliert. 4. Der Linker verwendet die Objekt-Datei und bindet sie zusammen, insbesondere auch mit den Libraries, die Bestandteil der Datenbank-Software sind. 5. Das Datenbank Abfrage Modul (falls verwendet) wird mit einem speziellen BindProgramm der Datenbank übergeben. Bei Ausführung des Statements wird dieses übergebene Modul aufgerufen. 8.4 Datenaustausch zwischen Programm und Datenbank • Die Kommunikation zwischen Programm und Datenbank wird über Hostvariablen durchgeführt. Diese werden im Anwendungsprogramm deklariert und tragen innerhalb der eingebetteten SQL-Statements einen führenden „:“ (Doppelpunkt). • Diese Variablen heißen Hostvariablen, weil sie in der umgebenden (Host-)Sprache definiert sind, d.h. die Datenbank verwendet direkt Speicher, den die Anwendung alloziert hat. • Die Zuordnung zwischen Datentypen in C und SQL wird durch den SQL-Standard geregelt. Er legt in einer Tabelle fest, welche Datentypen sich entsprechen. Seite 73 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 8.5 Cursorkonzept Bewältigung der Diskrepanz zwischen • mengenorientierten Datenbanken und • objekt- bzw. recordorientierten Programmiersprachen Was ist zu tun, wenn eine Abfrage nicht nur eine Zeile, sondern eine Menge von Zeilen liefert? Folgendes Beispiel demonstriert den Einsatz von Cursor in diesem Zusammenhang: #include <stdio.h> EXEC SQL INCLUDE SQLCA; void main( void ) { EXEC SQL BEGIN DECLARE SECTION; char Author[81]; char Title[81]; char ISBN[14]; EXEC SQL END DECLARE SECTION; int NochDatenDa = TRUE; EXEC SQL CONNECT TO "azamon" USER "dba" IDENTIFIED BY "sql"; /* Cursor deklarieren */ EXEC SQL DECLARE C1 CURSOR FOR SELECT Author, Title, ISBN FROM BOOKS; /* Cursor oeffnen */ EXEC SQL OPEN C1; /* Cursor ueber Ergebnismenge bewegen */ while ( NochDatenDa ) { EXEC SQL FETCH C1 INTO :Author, :Title, :ISBN; if ( SQLCODE == SQLE_NOTFOUND ) NochDatenDa = FALSE; else printf( "%s: %s ISBN %s\n", Author, Title, ISBN ); } /* Cursor schliessen */ EXEC SQL CLOSE C1; EXEC SQL DISCONNECT ALL; } Seite 74 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 8.6 Statisches und dynamisches SQL Der Unterschied zwischen statischem und dynamischem SQL besteht in erster Linie aus dem Zeitpunkt, zu dem die SQL-Anweisung feststehen muß. • Statisches SQL steht bereits zum Zeitpunkt der Übersetzung des Programms fest • Dynamisches SQL wird zur Laufzeit konstruiert. Beispiel für dynamisches SQL: #include <stdio.h> EXEC SQL INCLUDE SQLCA; void main( void ) { EXEC SQL BEGIN DECLARE SECTION; char Stmt[200]; EXEC SQL END DECLARE SECTION; strcpy( Stmt, "update Books set Title = ’DDJ Database Development’" "where ISBN = ’0-87930-480-4’" ); EXEC SQL CONNECT TO "azamon" USER "dba" IDENTIFIED BY "sql"; EXEC SQL EXECUTE IMMEDIATE :Stmt; EXEC SQL DISCONNECT ALL; } In diesem Beispiel ergeben sich gegenüber statischem SQL folgende Nachteile: • Die Optimierung des Statements kann erst zur Laufzeit durchgeführt werden • Der Vorgang des Optimierens würde bei jeder Ausführung des Statements neu erfolgen müssen • D.h. es sind Laufzeiteinbußen mit jeder Ausführung des Statements hinzunehmen Seite 75 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 8.7 Kopplung zwischen Anwendung und Datenbank 1. Embedded SQL bietet Datenbankunabhängigkeit auf Nivau der Programmquelle. 2. Bei Verwendung eines neuen Datenbanksystems ist eine erneute Übersetzung des Programms notwendig, da das Programm Aufrufe einer proprietären DatenbankBibliothek enthält. 3. Gleichzeitiger Zugriff auf verschiedene Datenbanksysteme ist sehr mühsam. 4. Veränderungen an der Struktur der Datenbank haben i.d.R. Auswirkungen auf das Anwendungsprogramm. 5. Veränderungen im Anwendungsprogramm (z.B. Wechsel der Programmiersprache) können drastische Auswirkungen haben. 8.8 Fazit Ausgehend von den anfänglichen Fragestellungen ist folgendes zusammenzufassen: 1. Wie verwendet das Anwendungsprogramm die Funktionalität der Datenbank? Durch die Einbettung von SQL-Anweisungen in die Programmiersprache der Anwendung, als wäre SQL eine Erweiterung der Programmiersprache. 2. Wie findet der Datenaustausch zwischen Anwendung und Datenbank statt? Durch Hostvariablen, die im Anwendungsprogramm erzeugt werden und die die Datenbank verwenden kann. 3. Wie wird eine Ergebnismenge der Datenbank an die Anwendung übergeben? Das Cursor-Konzept erlaubt es der Anwendung mit den mengenorientierten Daten der Datenbank umzugehen. 4. Zu welchem Zeitpunkt findet welche Aktion der Datenbank statt? Durch die Möglichkeit sowohl statisches als auch dynamisches SQL auszuführen, kann fallbezogen die optimale Vorgehensweise gewählt werden. 5. Welche Art von Kopplung entsteht zwischen Anwendungsprogramm und Datenbank? Embedded SQL bietet Kompatibilität zu speziellen Datenbanksystemen auf Quellniveau. D.h. soll eine Anwendung für verschiedene Datenbanksysteme angeboten werden, muss jeweils neu compiliert und gelinkt werden. Anwendungen sollen für verschiedene Datenbanksysteme gleichermaßen verfügbar sein. Deshalb ist der Bedarf nach Programmierschnittstellen gegeben, die einen simultanen Zugriff auf verschiedene Datenbanksysteme erlauben. Seite 76 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 9 Open Database Connectivity (ODBC) 9.1 Allgemein Initiator: Microsoft und andere Zweck: Einheitliche API für verschiedene DBMS (API = Application Program Interface) 9.1.1 Unterschiede zu embedded SQL • ODBC ist ein Call-Level-Interface: Menge von Funktionen, die durch Aufruf in einem Programm verwendet werden • Funktionen werden in einer Funktionsbibliothek zur Verfügung gestellt • Datenbankunabhängige Schnittstelle (ohne Neu-Übersetzung der Anwendung kann diese verschiedene Datenbanksysteme ansprechen) • ODBC unterstützt nur dynamisches SQL • ODBC ist für unterschiedliche Betriebssystem-Plattformen verfügbar (Windows, Unix, etc.) 9.1.2 Ziele • Entwicklersicht: Anwendungsprogramme sollen mit möglichst jeder verfügbaren Datenbank funktionsfähig sein. • Datenbankhersteller: Möglichst jedes Anwendungsprogramm soll mit der eigenen Datenbank kommunizieren könne. • Benutzersicht: Eine Kombination aus den beiden oben genannten Sichten. Höchste Flexibilität durch jede erdenkliche Kombination aus Anwendung und Datenbank. 9.1.3 Design-Prinzipien ODBC wurde insbesondere mit dem Blick auf folgende Prinzipien konzipiert: 1. ODBC ist eine API, die SQL verwendet. Dem zu Folge lässt sich prinzipiell jedes Datenmodell anbinden, das sich in SQL ausdrücken lässt. 2. Verbindung zur Datenbank wird abstrahiert und bleibt vor der Anwendung verborgen. Komplexe Mechanismen des Verbindungsaufbaus liegen ausserhalb der Anwendung der Schnittstelle. Basiert auf dem Client-Server Prinzip 3. Flexibilität durch jede erdenkliche Kombination aus Anwendung und Datenbank (sogar noch zur Laufzeit der Anwendung) Seite 77 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 9.2 Architektur Die Architektur von ODBC gliedert sich in vier Komponenten: 1. Das Anwendungsprogramm verwendet als Schnittstelle zur Datenquelle (Datenbank) nur den ODBC-Treiber-Manager. 2. Der Treiber-Manager stellt der Anwendung die ODBC-Schnittstelle zur Verfügung. Die Kommunikation zur Datenquelle übernehmen spezifische ODBC-Treiber. D.h. der Treiber-Manager fungiert lediglich als Brücke zwischen datenbankunabhängiger Schnittstelle und datenbankspezifischem Zugriff auf Datenquellen. Der Treiber-Manager unter Windows liegt in Form einer DLL (odbc32.dll) vor. 3. Der Treiber wickelt die Kommunikation mit der Datenquelle ab. Diese beinhaltet das Management der Datenbank-Verbindung, Durchführung von Datenzugriffen sowie Fehlerbehandlung. Je ein Treiber pro DBMS. 4. Die Datenquelle ist eine Abstraktion, hinter der sich der Zugriff auf eine Datenbank verbirgt. Komponenten von ODBC Seite 78 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten 9.3 Vergleich zu proprietären DB-API’s Proprietäre API: ODBC: Seite 79 von 104 Dipl.-Ing. (FH) D. Schiffer Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 9.4 Programmschritte beim ODBC-Zugriff Verbindung mit der Datenquelle herstellen: • Zugriff auf den ODBC-Treiber-Manager: Handle auf das Environment alloziieren: SQLAllocHandle( SQL_HANDLE_ENV ) • Zugriff auf eine Datenquelle öffnen: Handle auf eine Datenbank-Connection alloziieren: SQLAllocHandle( SQL_HANDLE_DBC ) • Verbindung mit der Datenquelle herstellen: SQLConnect( ConHandle, … ) SQL-Statement aufbauen und ausführen: • Zugriff auf Statement eröffnen: Handle auf Statement alloziieren: SQLAllocHandle( SQL_HANDLE_STMT, …) • Statement durchführen: z.B. SQLExecDirect( StmtHandle, „select …“) Ergebnisse verwerten: • SQLFetch() bewegt den Cursor auf der Ergebnismenge • SQLGetData() liest ein einzelnes Feld aus der aktuellen Zeile der Ergebnismenge • SQLCloseCusor() schließt den Cursor wieder Transkation beenden und Statement freigeben: • SQLEndTrans(…) beendet die Transaktion • SQLFreeHandle( SQL_HANDLE_STMT ) gibt das Statement-Handle frei Verbindung zur Datenbank trennen und Resourcen freigeben: • SQLDisconnect(…) trennt Verbindung zur Datenquelle • SQLFreeHandle ( SQL_HANDLE_DBC ) gibt Verbindungs-Handle frei • SQLFreeHandle ( SQL_HANDLE_ENV ) gibt Umgebungs-Handle frei Seite 80 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 9.5 Beispiel // Includes #include <iostream> #include <windows.h> #include <sql.h> #include <sqlext.h> using namespace std; // Deklaration der Klasse mniODBC class mniODBC { public: mniODBC( void ); // Konstruktor ˜mniODBC( void ); // Destruktor bool Connect( char* DataSource, char* UId, char* Pwd ); void Disconnect( void ); bool AllocStmt( void ); void FreeStmt( void ); bool ExecDirect( char* Statement ); void ShowResults( void ); private: SQLHANDLE m_EnvHandle; SQLHANDLE m_ConHandle; SQLHANDLE m_StmtHandle; // Copy und Zuweisung nicht erlaubt mniODBC( const mniODBC& Orig ); mniODBC& operator=( const mniODBC& Orig ); }; // Implementierung der Klasse mniODBC::mniODBC( void ) : m_EnvHandle( NULL ), m_ConHandle( NULL ), m_StmtHandle( NULL ) { SQLRETURN rc = SQL_SUCCESS; // Alloziere Environment Handle rc = SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE,&m_EnvHandle ); // Setze ODBC Application Version auf 3.x if ( rc == SQL_SUCCESS ) rc = SQLSetEnvAttr( m_EnvHandle, SQL_ATTR_ODBC_VERSION,(SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER ); // Alloziere Connection Handle if ( rc == SQL_SUCCESS ) rc = SQLAllocHandle( SQL_HANDLE_DBC, m_EnvHandle,&m_ConHandle ); } Seite 81 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer mniODBC::˜mniODBC( void ) { // Connection Handle freigeben if ( m_ConHandle != NULL ) (void)SQLFreeHandle( SQL_HANDLE_DBC, m_ConHandle ); // Environment Handle freigeben if ( m_EnvHandle != NULL ) (void)SQLFreeHandle( SQL_HANDLE_ENV, m_EnvHandle ); } bool mniODBC::Connect( char* DataSource, char* UId, char* Pwd ) { SQLRETURN rc = SQL_SUCCESS; // Connection herstellen if ( m_ConHandle != NULL ) { rc = SQLConnect( m_ConHandle, (SQLCHAR*)DataSource, SQL_NTS,(SQLCHAR*)UId, SQL_NTS, (SQLCHAR*)Pwd, SQL_NTS ); // Anmerkung: SQL_NTS steht fuer null terminated string // und bedeutet, dass die Laenge durch die Stringlaenge // gegeben ist. if ( rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO ) return true; } return false; } void mniODBC::Disconnect( void ) { // Connection trennen if ( m_ConHandle != NULL ) { (void)SQLDisconnect( m_ConHandle ); } } bool mniODBC::AllocStmt( void ) { SQLRETURN rc; // Alloziere Statement Handle if ( m_ConHandle != NULL ) { rc = SQLAllocHandle( SQL_HANDLE_STMT, m_ConHandle, &m_StmtHandle ); if ( rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO ) return true; } return false; } Seite 82 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer void mniODBC::FreeStmt( void ) { // Statement Handle freigeben if ( m_StmtHandle != NULL ) (void)SQLFreeHandle( SQL_HANDLE_STMT, m_StmtHandle ); } bool mniODBC::ExecDirect( char* Statement ) { SQLRETURN rc; // Statement ausfuehren if ( m_StmtHandle != NULL ) { rc = SQLExecDirect( m_StmtHandle, (SQLCHAR*)Statement, SQL_NTS ); if ( rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO ) return true; } return false; } void mniODBC::ShowResults( void ) { SQLRETURN rc = SQL_SUCCESS; // Lokale Variablen fuer das Ergebnis SQLCHAR Author[80]; SQLCHAR Title[80]; // Binde die Felder des Ergebnisses an diese Variablen (void)SQLBindCol( m_StmtHandle, 1, SQL_C_CHAR, (SQLPOINTER)Author, sizeof(Author), NULL); (void)SQLBindCol( m_StmtHandle, 2, SQL_C_CHAR, (SQLPOINTER)Title, sizeof(Title), NULL); // Kopfzeile ausgeben cout << "Autor: Titel" << endl << endl; // Ergebnismenge ausgeben while ( rc != SQL_NO_DATA_FOUND ) { rc = SQLFetch( m_StmtHandle ); if ( rc != SQL_NO_DATA_FOUND ) cout << Author << ": " << Title << endl; } // Cursor schliessen SQLCloseCursor( m_StmtHandle ); return; } Seite 83 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer /* -------------------------------------------------------------------Hauptprogramm -------------------------------------------------------------------- */ void main( void ) { bool rc; cout << "Datenbankprogrammierung" << endl; cout << "Select mit ExecDirect" << endl; cout << endl; // Instantiiere ODBC mniODBC myDB; // Stelle Connection her rc = myDB.Connect( "azamon", "dba", "sql" ); if ( !rc ) { cout << "Connection zu amazon konnte nicht aufgebaut werden" << endl; return; } // Alloziere Statement Handle rc = myDB.AllocStmt(); if ( !rc ) { cout << "Statement Handle konnte nicht alloziert werden" << endl; myDB.Disconnect(); return; } // Fuehre das Statement aus rc = myDB.ExecDirect( "select author, title from Books;" ); if ( !rc ) { cout << "Statement konnte nicht ausgefuehrt werden" << endl; myDB.FreeStmt(); myDB.Disconnect(); return; } // Zeige das Ergebnis an myDB.ShowResults(); // und aufraeumen myDB.FreeStmt(); myDB.Disconnect(); return; } Seite 84 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 9.6 Prinzipien von ODBC 9.6.1 Handles Ein Handle ist ein Zugriffsmechanismus, der die konkrete Ausgestaltung eines Bestandteils von ODBC verbirgt. Zugriffe auf Funktionen werden über Handles bereitgestellt. Folgende Arten von Handles bilden in ODBC eine Hiercharchie: • Environment Handle: Globaler Kontext, in dem ODBC in einer Anwendung verwendet wird. Repräsentiert den Zugriff auf den ODBC Treiber-Manager. • Connection Handle: Verwaltet alle Informationen einer Verbindung zur Datenbank. • Statement Handle: Wird verwendet um SQL-Anweisungen durchzuführen und die Ergebnisse der Datenbank zu erreichen. Wird im Context eines Connection Handles erzeugt. Durch die Handles wird somit von den dem ODBC-Treiber zu Grunde liegenden proprietären Funktionen / Structs / Klassen abstrahiert. Diese Abstraktionsschicht ist bei den gängigen relationalen DBM’s (DB2, Oracle, Sybase, PostgreSQL) relativ dünn. Alle diese DBM’s verwenden in ihren eigenen Schnittstellen dieses auf Environment, Connection und Statement basierte Prinzip. 9.6.2 Fehlerbehandlung ODBC bietet ein dreistufiges Konzept im Umgang mit Fehlern und Warnungen: 1. ODBC-Funktionen haben einen Returncode, der Auskunft über Erfolg oder Misserfolg des Aufrufs gibt. z.B. SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_ERROR 2. Die Funktion SQLError() gibt ausführlichere Auskunft, wenn ein Fehler aufgetreten ist. 3. Die Funktionen SQLGetDiagField bzw. SQLGetDiagRec erlaubt es der Anwendung einen kompletten Stack von Meldungen auszuwerten. Seite 85 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 9.7 Dynamisches SQL in ODBC ODBC kann kein statisches SQL ausführen. Dies ist die Folge der Einführung eines CallLevel-Interfaces und der damit verbundenen allgemeineren Kopplung zur Datenbank. ODBC unterstützt diverse Formen dynamischen SQLs: • Direkte Ausführung: Über die Funktion SQLExecDirect() wird eine SQL-Anweisung direkt ausgeführt. • Ausführung in zwei Phasen: Statement wird mit der Funktion SQLPrepare() zunächst von der Datenbank aufbereitet. Zugriffsplan wird erstellt. Mit SQLExecute() wird der Zugriffsplan (ggf. mehrfach) ausgeführt. • Stored procedures: Vor der ersten Verwendung wird auf der Datenbank eine stored procedure erzeugt. Einen entsprechenden Zugriffsplan erstellt die Datenbank automatisch. Beim eigentlichen Aufruf der Prozedur müssen nicht mehr alle Phasen der Verarbeitung durchlaufen werden. Asynchrone Ausführung Datenbankabfragen können mitunter sehr lange dauern. Bei asynchroner Ausführung gibt ODBC sofort nach dem Aufruf der Anweisung die Kontrolle an das Anwendungsprogramm zurück. Die Anwendung kann dann mittels „polling“ prüfen, ob die Datenbank mittlerweile mit der Verarbeitung der Anweisung zu Ende gekommen ist. Dieses Prinzip kann insbesondere bei Multithreaded-Anwendungen oder Applikationen die mehrere Prozesse verwenden von Nutzen sein. 9.8 Retrieval von Informationen in ODBC SQLFetch / SQLGetData ODBC verwendet bei Statements, die Ergebnismengen liefern, das Cursor-Prinzip. In Folge dessen muss nach einem SQLExecDirect() oder SQLExec() stets SQLFetch() aufgerufen werden, damit der Cursor auf die erste Zeile der Ergebnismenge gerückt wird. Mit SQLGetData können dann Daten einer Spalte innerhalb der aktuellen Cursor-Position in eine übergebene Variable kopiert werden. Binden von Variablen Eine Alternative zur Verwendung von SQLGetData ist das Binden von Variablen an erwartete Ergebnisspalten. Dies geschieht vor dem ersten Aufruf der Funktion SQLFetch. Das Binden übernimmt die Funktion SQLBindCol. Seite 86 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 9.9 Fazit Ausgehend von den anfänglichen Fragestellungen ist folgendes zusammenzufassen: 1. Wie verwendet das Anwendungsprogramm die Funktionalität der Datenbank? Durch einheitliche Funktionsaufrufe, die als Interface vom ODBC-Teiber-Manager zur Verfügung gestellt werden. 2. Wie findet der Datenaustausch zwischen Anwendung und Datenbank statt? Durch Datenstrukturen, die vom ODBC-Teiber erzeugt, und in Form von Handles innerhalb der Anwendung zur Verfügung gestellt werden. 3. Wie wird eine Ergebnismenge der Datenbank an die Anwendung übergeben? Das Cursor-Konzept erlaubt es der Anwendung mit den mengenorientierten Daten der Datenbank umzugehen. 4. Zu welchem Zeitpunkt findet welche Aktion der Datenbank statt? Die Reihenfolge der Aktionen, die auf der Datenbank ausgeführt werden erledigt der ODBC-Treiber, und ist für die Anwendung nicht relevant. Innerhalb der Anwendung muss lediglich die richtige Reihenfolge bei der Erzeugung von Handles eingehalten werden. 5. Welche Art von Kopplung entsteht zwischen Anwendungsprogramm und Datenbank? Die Kopplung findet ausschließlich über die Verwendung der standardisierten Schnittstelle des ODBC-Treiber-Managers statt. Über die Einrichtung von sog. DSNs können somit unterschiedliche Datenbank mit derselben Anwendung angesprochen werden. Seite 87 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 10 ActiveX Data Objects.NET (ADO.NET) 10.1 Allgemein Das .NET-Framework ist Microsofts zukünftige strategische Entwicklungsplattform. ADO.NET, als direkter Technologienachfolger von ADO, enthält in diesem Zusammenhang Klassen über die Datenzugriffsdienste bereitgestellt werden. Das neue Datenzugriffsmodell hat Ähnlichkeiten mit dem Objektmodell von ADO, unterscheidet sich jedoch wesentlich in seiner XML-basierten, unverbundenen Architektur. Motivation • Insbesondere Web-Anwendungen benötigen eine lose Kopplung zwischen Anwendung und Daten (auf Grund der Zustandslosigkeit) • XML entwickelt sich zum „universellen Datenformat“ Probleme mit bestehenden APIs • ADO, OLE DB und ODBC wurden für enge Kopplung und dauerhafte Verbindung konzipiert • Remote Data Services (RDS) für nichtverbundene Verbindung: kein Zustandsmanagement Seite 88 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 10.2 Historie Zur Anbindung von Datenbanken sind von Microsoft im Laufe der Jahre diverse Technologien entwickelt worden, die schlussendlich zu ADO.NET geführt haben. 10.2.1 Open Database Connectivity (ODBC) • <Siehe vorherige Kapitel> • ODBC (Open Database Connectivity) bot als erste Technologie Zugriff auf unterschiedliche relationale Datenbanken über eine SQL-gestützte Standardschnittstelle. Anwendungen können dank ODBC mit dem gleichen Code auf verschiedene Datenbanken zugreifen. • Hierzu ist lediglich die Installation des ODBC Treibers für die vom Anwender ausgewählte Datenbank erforderlich. • Der Zugriff erfolgt mittels Handels und Cursor. • Low-Level API eher für C/C++ Programme geeinet, auf Grund des Handles-Konzeptes (Zeiger) 10.2.2 Data Access Objects (DAO) • Im Gegensatz zu ODBC stellt DAO Objekte zur Datenbankanbindung zur Verfügung • Objekte repräsentieren ein Datenbanksystem • Zugriff auf DAO erfolgt über eine Jet Engine • Jet Engine ist für Access Datenbanken optimiert • Über ODBC kann auch auf andere Datenbank zugegriffen werden • Vorteil: Einfacher zu programmieren als mit ODBC • Nachteil: sehr langsamer Zugriff (Übersetzung von Funktionsaufrufen zwischen DAO und ODBC) Seite 89 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten 10.2.3 Remote Data Objects (RDO) • Ähnliches Objektmodell wie DAO • Objekte können direkt auf ODBC zugreifen, ohne über Jet Engine zu gehen • daher Reduzierung des Performance Verlustes gegenüber DAO 10.2.4 OLE DB • Erweiterung auf nicht relationale Datenquellen (z.B. Microsoft Exchange Server) • Grundlegender COM-Baustein für das Speichern und Auslesen von Datensätzen • Einheitlicher Zugriff auf relationale sowie nicht-relationale unstrukturierte Datenquellen Seite 90 von 104 Dipl.-Ing. (FH) D. Schiffer Datenbankprogrammierung Hochschule Ravensburg-Weingarten 10.2.5 ADO - ActiveX Data Objects • OLE DB ist Low-Level-Programmierschnittstelle -> mühsam • Setzt auf OLE DB als einfacheres High-Level-Interface auf • Kombiniert Vorteile von DAO und RDO • Basierend auf COM-Modell (sprachübergreifend, plattformunabhängig) Seite 91 von 104 Dipl.-Ing. (FH) D. Schiffer Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 10.3 Das ADO.NET Objekt Modell ADO.NET unterscheidet prinzipiell zwischen connected und disconnected Objekt Modell. • Data Provider für connected Zugriff • DataSet bietet Funktionalität für disconnected Datenobjekte • Auch Datenzugriffsmodell neu – bisher hat Microsoft immer nur weitere Abstraktionsschichten über ODBC gelegt, die Basis aber beibehalten – Jetzt .NET Provider, nicht mehr ODBC basiert. Auf der untersten Schicht findet man den Datenspeicher. Dies kann natürlich ein DatenbankServer oder eine OLE DB Datenquelle, aber auch jede beliebige relationale oder nichtrelationale Datensammlung sein. Der Datenspeicher ist herstellerspezifisch und im Regelfall unabhängig von .NET. Die Brücke bildet der Managed Provider, der auf dem Datenspeicher aufsetzt und den Anschluss an das .NET-Framework schafft. Wie überall in .NET bedeutet die Eigenschaft „managed“ auch hier, dass. Es handelt sich also um eine Reihe von .NET-Komponenten. Der Managed Provider umfasst die Verbindung zur Datenquelle, das Ausführen von (SQL-) Kommandos, und den Transport der Daten zu und von der nächstfolgenden Schicht, der Datenmenge (Data Set). Die grundlegende Entscheidung für das unverbundene Modell in ADO.NET bedeutet an dieser Stelle, dass der Datentransport gebündelt und explizit abläuft und nicht kontinuierlich und automatisch wie bei einer verbundenen Datenmenge. Beim Ausführen eines SELECT-Kommandos wird eine Verbindung zur Datenquelle hergestellt, die Abfrage abgesetzt, die Ergebnismenge komplett zum Client transportiert und die Verbindung dann wieder geschlossen. Das Ändern von Daten geschieht ganz analog: Seite 92 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Verbindung öffnen, eine Reihe von UPDATEs, INSERTs und DELETEs absetzen, Verbindung wieder schließen. Daraus wird klar, dass an die dritte Schicht des Modells (DataSet) hohe Anforderungen gestellt werden müssen. Die Datenmenge verhält sich für den Anwender wie eine eigenständige kleine "offline-" Datenbank. Sie speichert die von der Datenquelle abgefragten Informationen, bietet sie zur Weiterverarbeitung (z.B. in Steuerelementen) an und registriert und hält Änderungen bis zur Aktualisierung in der Datenquelle. Auf der obersten Schicht sitzen alle Komponenten für die Anzeige und Bearbeitung der in der Datenmenge gespeicherten Informationen. Das Datengitter (Data Grid) ist der klassische Vertreter für diese Gattung von Komponenten. Aber natürlich gehören auch alle anderen Steuerelemente mit Datenanschluss dazu und überhaupt alle Komponenten, die Daten aus Data Sets auslesen und/oder manipulieren. Seite 93 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 10.3.1 Managed Provider Sind eine Sammlung von Klassen, die den Zugriff auf Datenquellen regeln, der Zusatz „managed“ liefert uns einen Hinweis darauf, dass diese Provider ebenfalls in .NETManaged-Code geschrieben sind und daher in der gemeinsamen Laufzeitumgebung (CLR = Common Language Runtime) laufen. .NET arbeitet wie COM sehr stark mit Schnittstellen, so dass in diesem Objektmodell hauptsächlich Beziehungen zwischen Interfaces dargestellt sind. Aufbau eines Managed Providers Das Objektmodell stellt somit allgemeine Beziehungen dar, die für jeden beliebigen Managed Provider gelten. Alle aufgeführten Schnittstellen sind im Namensraum System.Data deklariert. Mit IDbConnection und IDbCommand enthält ein Managed Provider zwei von ADO her bekannte Komponenten. Die Interfaces / Klassen / Komponenten eines Managed Provider: • DBConnection / IDbConnection: Herstellung einer Verbindung zur Datenquelle • DBCommand / IDbCommand: Zum Ausführen der Befehle • DataReader: Liefert die Ergebnisse einer Abfrage • DataAdapter: Verbindet das DataSet mit der Datenquelle, Updates Beispiele für DataProvider: • SQL Server .NET Data Provider - SQL Server 2000, SQL Server 7, MSDE - Verwendet eigenes Kommunikationsprotokoll (TDS) - Zugriff via System.Data.SQL Namespace • OleDb .NET Data Provider - SQLOLEDB – OLE DB provider für SQL-Server - MSDAORA – OLE DB provider für Oracle - Microsoft.Jet.OLEDB.4.0 für Microsoft Jet - Zugriff via System.Data.OleDb Namespace • ODBC .NET Data Provider Seite 94 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 10.4 Connection Objekt • Repräsentiert eine Verbindung zu einer Datenbank • enthält eine Eigenschaft für den ConnectionString und den aktuellen Zustand (offen/geschlossen) sowie • Verbindung zur Datenquelle ändern - Methoden (create, open, close) • Transaktionen handhaben (begin, commit, abort) Beispiel: string cString = "user id=sa;" +"password=;" + "database=northwind;" + "data source=MyComputerName\\NetSDK;" + "connect timeout=5"; // connect to SQL Server SqlConnection myConnection = new SqlConnection(cString); /* string cString2 = "Provider=Microsoft.Jet.OLEDB.4.0;" + "data source=Provider.mdb"; OleDbConnection myConnection = new OleDbConnection(cString2); */ myConnection.Open(); // do s.th myConnection.Close(); 10.5 Command Objekt • Repräsentiert eine Abfrage (Query) einer Datenquelle • enthält einen Verweis auf die zugehörige Datenbank-Verbindung und einen String, der das auszuführende Kommando aufnimmt. Im Unterschied zu ADO, muss in ADO.NET jede Ausführung über ein DbCommand-Objekt ablaufen, die direkte Ausführung z.B. über die Verbindung selbst wird nicht mehr unterstützt • enthält zwei Methoden zum Ausführen von Befehlen. Mit ExecuteNonQuery() wird ein UPDATE, INSERT oder DELETE ausgeführt, mit anderen Worten jedes Kommando, das keine Ergebnis-Menge zurückliefert. Wenn ein SELECT-Statement ausgeführt wird oder ein anderer Befehl, der Daten zurückliefert (ADO.NET ist ja nicht auf SQL beschränkt), benutzt man ExecuteReader() und erhält eine von DataReader abgeleitete Klasse zurück, die den Zugriff auf die Ergebnismenge erlaubt Wichtige Properties • ActiveConnection: Verbindung zur Datenquelle • CommandText: Enthält Query String • CommandType: Wie ist Command Text zu interpretieren (SQL, StoredProcedure, etc.) • CommandTimeout: Sekunden bis zum Timeout Seite 95 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer • RecordsAffected: Anzahl der betroffenen Datensätze Zur Ausführung von SQL-Anweisungen stellt das Command-Objekte folgende Methoden zur Verfügung: ExecuteNonQuery ExecuteScalar ExecuteReader Führt Befehle wie die Transact-SQL-.Anweisung INSERT, UPDATE und SET aus. Dabei wird keine Ergebnsimenge zurückgegeben Liefert einen einzigen Wert zurück. z.b. „SELECT COUNT (*) FROM ORDERS“ Liefert ein SqlDataReader-Objekt zurück. In diesem Objekt stehen die Ergebnisreihen der Abfrage. Führt Befehle aus, die Zeilen zurückgeben. Beispiel: ExecuteNonQuery: string conStr = "CREATE TABLE Abomodell " + "(AMNr LONG NOT NULL, Name CHAR(20) NOT NULL, " + "Grundgebuehr CURRENCY, Zeitgebuehr CURRENCY, " + "FreiStd LONG)"; // OleDBCommand Objekt anlegen OleDbCommand cmd = new OleDbCommand(conStr, conn); // Bei Kommandoausführung werden keine Daten zurückgegeben Int32 RowsAffected = cmd.ExecuteNonQuery(); Beispie: ExecuteScalar // OleDBCommand Objekt anlegen OleDbCommand cmd = new OleDbCommand( "SELECT Name FROM Abomodell " + "WHERE AboID = @Abo_ID", conn); //Übergabeparameter wird erzeugt und initialisiert cmd.Parameters.Add("@Abo_ID", aboID); Name wird zurückgegeben string aboName = (string)cmd.ExecuteScalar(); Seite 96 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 10.6 DataReader Objekt • Bietet die Möglichkeit, einen Ergebnis-Datenstrom von einer Datenquelle zu erhalten • Der Datenstrom kann nur in einer Richtung ausgelesen werden (forward-only, readonly) • Typsicherer Zugriff • Datenzugriffsmethoden: - Typ und Spaltenindex: myRow.GetString(0) - Über Attributnamen: myRow[„Attribut“] - Read: Bewegt den Reader zum nächsten Datensatz • repräsentiert einen unidirektionalen (forward-only), nur-Lesen (read-only) Zugriff auf die Ergebnismenge in Form eines serverseitigen Cursors. D.h. die Daten werden direkt vom Server geholt und erfordern deshalb eine stehende Datenbank-Verbindung. • Die Methode Read() liefert bei jedem Aufruf den folgenden Datensatz in der Ergebnismenge, ohne eine Möglichkeit, wieder auf den vorherigen oder den ersten Datensatz zurückzugehen. Durch diese Einschränkungen wird eine hoch-performante Implementierung des Cursors möglich. Dadurch soll die Dauer der Datenbankverbindung auch möglichst kurz gehalten werden. • Die Verarbeitung der Daten geschieht offline im Data Set. Seite 97 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Klassendiagramm (Auszug) des ManagedProviders Seite 98 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 10.7 DataAdapter Der DataAdapter „weiß“, wie eine Tabelle aus der Datenbank geladen wird und schreibt Änderungen zurück. Der DataAdapter wird explizit für ein bestimmtes Verhalten konfiguriert, das das Recordset implizit zeigt. • Schnittstelle über die in beide Richtungen Informationen ausgetauscht werden können • Enthält die Methoden Fill (DataSet) und Update (DataSet) – hierzu wird jeweils ein DBCommand-Objekt verwendet, eines zum Ermitteln der Daten mittels SELECT und eines für INSERT, UPDATE und DELETE. Zum Füllen der Datenmenge muss der Adapter mit einem DataReader arbeiten, dies ist der einzige Weg ist, die Daten von der Datenquelle abzuholen. Die Updates kann er direkt ausführen, indem er ein ExecuteNonQuery() auf dem entsprechenden DbCommand-Objekt aufruft • Mapping zwischen Tabellen und Spalten • Benutzer kann die voreingestellten Kommandos überschreiben (insert / update / delete), um z.B. StoredProcedures auszuführen • Erlaubt es, ein DataSet aus mehreren Datenquellen zu füllen • DbDataAdapter ist kein bloßes Interface sondern eine abstrakte Basisklasse innerhalb des .NET-Frameworks. • Die speziellen DbDataAdapter-Nachkommen der einzelnen Managed Provider erben somit nicht nur die Verpflichtung, bestimmte Methoden anzubieten sondern auch vorhandene Implementierungen. Eine Schnittstelle IDbDataAdapter gibt es allerdings auch. • Intern verwendet ein DataAdapter ein Command-Objekt für das Abfragekommando und erzeugt damit einen DataReader. Über ihn werden gegebenenfalls Metainformationen zum Aufbau der Zieltabellenstruktur ermittelt und schließlich die Resultatsmenge traversiert, um die Datensätze einzeln in DataRow-Objekte zu übertragen und an die Tabelle anzuhängen. Seite 99 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 10.8 DataSet Objekt • Nicht mehr Bestandteil des ManagedProviders, sondern datenbezogene Komponente (Datenhaltung) • Lokaler Cache für Daten - ähnlich einer relationalen Datenbank, die im Arbeitsspeicher liegt - Daten sind disconnected von der Datenquelle (connect, execute query, disconnect) • Universeller Datencontainer, unabhängig von einer speziellen Datenquelle (nicht nur zur Verwendung mit Datenbanken, auch XML, Email, etc.) Interner Aufbau eines DataSet-Objektes • Keinerlei Bezug zur Datenquelle • Daten können aus XML geholt und zurückgeschrieben werden – also eine Serialisierung in XML durch das DataSet mittels der Methoden: WriteXml(), WriteXmlSchema(), ReadXml() • DataSets enthalten Tabellen, Spalten, Relationen, Bedingungen, Zeilen (wie relationale DB) • Die Objektstruktur des DataSets wird von den DbDataAdaptern erzeugt und gefüllt, kann dann von der Applikation verarbeitet werden und von dem selben DbDataAdapter wieder mit der eigentlichen Datenquelle synchronisiert. • Ein DataSet enthält eine oder mehrere Daten-Tabellen, die wiederum aus Zeilen und Spalten aufgebaut sind. DataRow-Objekte enthalten die Daten eines einzelnen Datensatzes, während DataColumn-Objekt die Spalten einer Datenbank-Tabelle beschreiben (nur MetaDaten, keine Inhalte). • Das wesentlich Neue am DataSet im Vergleich zu einem ADO RecordSet ist, dass es mehr als eine Tabelle aufnehmen kann. Über die DataTableCollection können die einzelnen Tabellen angesprochen werden, und es ist auch möglich, jederzeit neue Tabellen hinzuzufügen. Das DataSet kann also auch völlig ohne Managed Provider als In-Memory-Datenbank betrieben werden. • Zwischen den Tabellen im DataSet können auch Beziehungen in Form von Equate Joins verwaltet werden. Die DataRelationCollection verwaltet beliebig viele DataRelations, von denen jede eine Beziehung zwischen zwei Tabellen herstellt. Die Kopplung wird dabei über die Gleichsetzung der Werte zweier Spalten hergestellt, also nach dem Schema ORDER.Id = ORDERDETAIL.OrderId. Seite 100 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer Beispiel (Befüllen eines DataSets mittels DataAdapter): SqlConnection conn = new SqlConnection(“...”); DataSet ds = new DataSet(); SqlDataAdapter adap = new SqlDataAdapter(“select customerid from customers”, conn); adap.Fill(ds, “kunden”); 10.9 DataTable • Kann auf physische Tabelle in der Datenquelle gemapped sein • Kann durch DataRelations mit anderen DataTables verbunden sein • Neue Zeilen können mit der Add()-Methode einfach zur Tabelle hinzugefügt werden, während vorhandene Zeilen interessanterweise direkt über einen Index angesprochen werden. Bei einer speicherbasierten Datenbank ist das möglich. • Wichtige Properties: - Columns - Rows - ParentRelations - Constraints - PrimaryKey Beispiel: DataSet ds = new DataSet(); // Neues DataTable Objekt erzeugen DataTable dt= new DataTable( "Customer" ); // Spalten erzeugen und einfügen DataColumn dc = new DataColumn( "Cust_id", Int32 ); dt.Columns.Add( dc ); dt.PrimaryKey = dc; dt.Columns.Add( "Name", String ); dt.Columns.Add( "StreetNo", String ); dt.Columns.Add( "City", String ); // DataTable Objekt in DataSet einfügen ds.Tables.Add( dt ); Seite 101 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 10.10 DataRelation • wird für logische Relationen verwendet • erzeugt Relation zwischen zwei DataTable Objekten • benötigt ein DataColumn Objekt von jedem DataTable Objekt • Der DataType beider DataColumns muss gleich sein (Es kann bspw. keine Int32 DataColumn mit einem String DataColumn verbunden werden) Beispiel zur Erzeugung einer DataRelation: • Suche die zu verbindenden DataColumn Objekte • Erzeuge die DataRelation mit den Columns • Füge die Relation in das DataSet ein DataColumn parentCol, childCol; parentCol = DataSet.Tables["Kunden"].Columns["AboID"]; childCol = DataSet.Tables["Abomodell"].Columns["AboID"]; // Erzeuge die DataRelation mit Namen "Kde_Abo" DataRelation dr; dr = New DataRelation("Kde_Abo", parentCol, childCol); // DataRelation zu DataSet hinzufügen ds.Relations.Add( dr ); // Kurzversion zur Erzeugung einer DataRelation ds.Relations.Add("Kde_Abo", ds.Tables["Kunden"].Columns["AboID"], ds.Tables["Abomodell"].Columns["AboID"]); // Navigiere durch die verknüpften Tabellen foreach (DataRow custRow in ds.Tables["Kunden"].Rows){ Console.WriteLine(custRow["KundeID"]); foreach (DataRow orderRow in custRow.GetChildRows(Kde_Abo)){ Console.WriteLine(orderRow["AboID"]); } } Seite 102 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 10.11 XML • DataSet bietet Methoden mit denen man XML lesen und schreiben kann - ReadXml: Liest beliebige XML Schema und Daten in ein DataSet - ReadXmlSchema: Liest XML Schema in ein DataSet - Zum Schreiben: WriteXml, WriteXmlSchema • Schema kann als XSD geladen/gespeichert werden • Schema kann von beliebigen XML Daten automatisch erzeugt werden 10.12 Fazit Ausgehend von den anfänglichen Fragestellungen ist Folgendes zusammenzufassen: 6. Wie verwendet das Anwendungsprogramm die Funktionalität der Datenbank? Durch Klassen / Schnittstellen aus dem Namensraum System.Data und entsprechende Methodenaufrufe.. 7. Wie findet der Datenaustausch zwischen Anwendung und Datenbank statt? Durch das DataSet, das vom ManagedProvider erzeugt, und in Form von Objektinstanzen innerhalb der Anwendung zur Verfügung gestellt werden. Der Datenaustausch beschränkt sich auf kurze Zugriffe, gearbeitet wird auf Kopien der Daten im RAM. 8. Wie wird eine Ergebnismenge der Datenbank an die Anwendung übergeben? Es werden die gesamten Daten auf einmal von der Datenbank in den Speicher der Applikation übertragen, auf diesem Speicher kann dann gearbeitet werden und alle Änderungen können auf einen Schlag zurückübertragen werden. (Nicht wie sonst mittels Cursor) 9. Zu welchem Zeitpunkt findet welche Aktion der Datenbank statt? Die Reihenfolge der Aktionen, die auf der Datenbank ausgeführt werden erledigt der ManagedProvider, und ist für die Anwendung nicht relevant. Innerhalb der Anwendung muss lediglich die richtige Reihenfolge bei der Erzeugung von Objektinstanzen und dem Aufruf entsprechender Zugriffsmethoden eingehalten werden. 10. Welche Art von Kopplung entsteht zwischen Anwendungsprogramm und Datenbank? Die Kopplung findet ausschließlich über die Verwendung der standardisierten Schnittstelle (Klassen, Methoden) aus dem Namensraum System.Data statt. Aufgabenteilung zwischen Datenhaltung und Datenzugriff. Seite 103 von 104 Datenbankprogrammierung Hochschule Ravensburg-Weingarten Dipl.-Ing. (FH) D. Schiffer 11 Literatur: [1] SQL. Der Schlüssel zu relationalen Datenbanken - Gregor Kuhlmann, Friedrich Müllmerstadt ISBN: ISBN-10: 3499612453, ISBN-13: 978-3499612459 Preis: 9,90 EUR [2] Weitere Informationen zu Joins finden Sie unter - Satya Komatineni http://www.onjava.com/lpt/a/4443#1 oder http://www.onjava.com/pub/a/onjava/2004/01/07/SQLJoins.html [3] Vorlesung Datenbanken - Mario Jeckle http://www.jeckle.de/vorlesung/datenbanken/script.html [4] Vorlesung eBusiness - Mario Jeckle http://www.jeckle.de/vorlesung/eBusinessEng/index.html [5] Vorlesung Datenbank-Anwendungen - Mario Jeckle http://www.jeckle.de/vorlesung/DB-Anwendungen/index.html [6] Vorlesung XML - Mario Jeckle http://www.jeckle.de/vorlesung/xml/index.html [7] Impedance Mismatch - Alexandra Schäfer http://www.sigs.de/publications/os/2003/03/schafer_OS_03_03.pdf [8] Trigger Happy - Martin Rennhackkamp http://www.dbmsmag.com/9605d17.html [9] Stored Procedures for Java Programmers - Nic Ferrier http://www.onjava.com/pub/a/onjava/2003/08/13/stored_procedures.html [10] Struts Best Practices - Vic Cekvenich, Wolfgang Gehner ISBN : 3-89864-284-4 Preis : 32,00 EUR http://www.dpunkt.de/buecher/struts.html [11] Oracle PL/SQL Tutorial - Die Programmiersprache von Oracle - Ein Nachschlagewerk auf Basis von Beispielen - TAKTUM Informatik GmbH http://www.datenbank-plsql.de/ [12] PL/SQL http://www.orafaq.com/faq/plsql/ [13] PL/SQL User's Guide and Reference 10g Release 1 (10.1) Copyright © 1996, 2003 Oracle Corporation http://www.oracle-10g.de/oracle_10g_documentation/appdev.101/b10807/toc.htm [14] Design Patterns. Addison-Wesley Professional, 1995, - Erich Gamma et al. ISBN : 0201633612 Entwurfsmuster. Addison-Wesley, 2004, - Erich Gamma et al. ISBN 3827321999 [15] Weitere Links unter : http://fh.atbits.de/ => weiterführende Links [16] Struts Best Practices – Die Erstellung komplexer Webanwendungen, 2004 - Vic Cekvenich, Wolfgang Gehner ISBN: 3898642844 Preis : 32,00 EUR [17] DAO http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html [18] Core J2EE Patterns: Best Practices and Design Strategies 2nd Edition - Deepak Alur, John Crupi and Dan Malks Publisher: Prentice Hall / Sun Microsystems Press ISBN:0131422464; 2nd Edition (June, 2003) http://www.corej2eepatterns.com/Patterns2ndEd/DataAccessObject.htm [19] http://www.torsten-horn.de/techdocs/sw-patterns.htm Seite 104 von 104