Anwendungsprogrammierung 5. Anwendungsprogrammierung Anwendungen werden typischerweise in imperativen Sprachen entwickelt (C, Cobol, Fortran, Java, C++…) – viel Funktionalität bei der “gewöhnlichen” Programmierung – sehr schwierig für Datenbankprogrammierung – flexibles Datenmodell: basiert auf der Verarbeitung von einzelnen Datensätzen Datenbankanfragesprachen (SQL) sind deskriptiv – wenig Funktionalität für die “alltägliche” Programmierung: Eine einfache Aufgabe wie “Berechne den Flächeninhalt eines beliebigen Polygons” ist in SQL nicht möglich. – sehr komfortabel für die Datenbankprogrammierung – restriktives Datenmodell: basiert auf der Verarbeitung von Mengen (Relationen) Anwendungsprogrammierung Kopplungsmöglichkeiten Evolutionäre Ansätze: CALL-Schnittstelle – Bereitstellung von Bibliotheken Einbettung mit Vorübersetzer – statisch: Struktur der SQL-Befehle ist bereits vorgegeben – dynamisch: beliebige SQL-Befehle Spracherweiterungen – von SQL – von einer imperativen Programmiersprache (z. B. PASCAL) Skriptsprachen – BASIC-ähnliche Sprachen ohne Typkonzept – einfache Anbindung an Window- und Graphikumgebung Frage: Wie kann die Programmierung von Datenbankaufgaben mit der von “gewöhnlichen” Aufgaben verbunden werden (ohne dabei die Vorteile von SQL aufzugeben)? Seite 128 von 159 Seite 129 von 159 Anwendungsprogrammierung 5.1 Prozedurale CALL-Schnittstelle basiert auf der Verwendung einer Bibliothek – im Fall von Oracle: Oracle Call Interface (OCI) Quelldateien Host-Compiler gemeinsam vom AWP und dem Datenbank-Server genutzte Datenstrukturen – zum Aufbau der Kommunikation – zur Bearbeitung einer Anfrage Cursor: im AWP benutzte Datenstruktur zum Zugriff auf Relationen der DB. im AWP: Speicherung der SQL-Anfragen in einem String. Typüberprüfung: nur im AWP möglich Binden der Variablen aus dem AWP an die Datenstrukturen des DBMS-Server Table 1: DBMS-Bibliothek Ablauf eines AWP: Host-Linker AWP Anwendungsprogrammierung Komponenten der CALL-Schnittstelle Aufbau der Verbindung zum DBMS-Server DBMS-Server Initialisieren eines Cursors Datenbank Parsen eines SQL-Statements Binden von Eingabevariablen Schließen des an das SQL-Statement Cursors Ausführen eines Updates/Anfrage Seite 130 von 159 Abkoppeln vom Server Seite 131 von 159 Anwendungsprogrammierung Abfragen der Ausgabeparameter Anwendungsprogrammierung JDBC - eine Call-Schnittstelle in Java Ausführen einer Anfrage (im Detail): Binden der Ausgabe an Variablen des AWP Setzen des Cursors Abbrechen der Anfrage Nachteil bei der Benutzung der CALL-Schnittstelle: komplizierte Programmierung fehleranfällig Vorteile hohe Flexibilität Datenbankprogrammierung in Java unter Benutzung von SQL – Anfragen werden als uninterpretierte Zeichenketten an das DBMS gegeben – Resultate werden über Objekte einer Klasse ResultSet vom DBMS an das AWP geschickt. Client-Server Konzept – DBMS läuft auf einem anderen Rechner als das AWP Einheitliche Schnittstelle für verschiedene DBMS Verwendung von strenger Typisierung (wenn immer möglich) Unterstützung wichtiger Konzepte – statische Anfragen – parameterisierbare SQL Anfragen – Unterstützung großer Objekte – dynamische Anfragen und Metadaten Seite 132 von 159 Seite 133 von 159 Anwendungsprogrammierung Client/Server Kopplung in JDBC Anwendungsprogrammierung ODBC Kopplung Client Native Kopplung JDBC Client JDBC Server Server DBMS ODBC ODBC + Datenbanktreiber DBMS DBMS-Net Spezielle Netzsoftware des DBMS wird auf dem Client installiert. Methoden in JDBC basieren auf den Funktionen der darunterliegenden Netzsoftware. ODBC und entsprechende Datenbanktreiber sind auf dem Client installiert. Methoden von JDBC basieren auf der Funktionalität von ODBC. Vorteil Unabhängigkeit von speziellen Datenbanksystemen Nachteile Nachteil Installation der Netzsoftware auf allen Clients keine Unabhängigkeit vom Datenbanksystem Installation von ODBC + Datenbanktreiber auf allen Clients Overhead von ODBC Seite 134 von 159 Seite 135 von 159 Anwendungsprogrammierung Native Pure Java Client Anwendungsprogrammierung Native-Protocol Pure Java Clients Client Server Client DBMS DBMS-Net JDBC DBMS-Net JDBC + Datenbanktreiber Server DBMS JDBC Download Ähnlich zur ersten Lösung, aber die Netzsoftware ist in Java reimplementiert, die nach Bedarf vom Server geladen wird (siehe z. B. die derzeitige Anbindung an Oracle) Vorteil Ein spezielles in Java implementierte Netzwerkverbindung übermittelt die Anforderungen des Clients an einen Server. Beim Server wird dann die Anforderung an das DBMS weitergereicht. Vorteile Keine Installation von spezieller Software am Client Datenbankunabhängigkeit keine Software auf dem Client Nachteil Abhängigkeit vom Datenbankhersteller Seite 136 von 159 Seite 137 von 159 Anwendungsprogrammierung Anwendungsprogrammierung Wichtige Klassen bei JDBC ResultSet executeQuery getMoreResults exe cut eQu ery X tXX se IS-A Connection t n e m tate getConnection pareS pre getXXX ll Ca re pa IS-A pre Datentypen Erzeugung eines Connection Objekts Connection con = DriverManager.getConnection ("jdbc:oracle:thin:@venus.mathematik.uni-marburg.de:1521:Init_DB", "scott", "tiger"); createStatement Statement PreparedStatement getXXX Aufbau einer Verbindung DriverManager – Erste Zeichenkette entspricht einer URL zur Datenbank – Zweite Zeichenkette ist der Benutzername – Dritte Zeichenkette ist das Paßwort Bevor das Connection Objekt erzeugt wurde, muß noch die entsprechende Treiberklasse angemeldet werden. Class.forName("oracle.jdbc.driver.OracleDriver") ; CallableStatement Die Namen in den Rechtecken entsprechen den Namen der verfügbaren Schnittstellen Die gerichteten Kanten zeigen i. A. wie man ein Objekt einer Klasse (auf die eine Kante gerichtet ist) mit Hilfe der anderen Klasse erzeugt. Seite 138 von 159 Seite 139 von 159 Anwendungsprogrammierung Anwendungsprogrammierung Interpretierte Anfragen Vorübersetzte Anfragen SQL-Anfrage wird interpretiert (übersetzt und gleichzeitig ausgeführt). Das Ergebnis der Anfrage wird einem Objekt der Klasse ResultSet übergeben. Eine nochmalige Ausführung der Anfrage erfordert erneut eine Interpretation. Die Anfrage selbst ist nicht parameterisierbar. Statement stmt = con.createStatement(); Bei diesen Anfragen trennt man die Übersetzung der SQL-Anfrage von der Ausführung der Anfrage. PreparedStatement stmt = con.prepareStatement(“select x, y from Points where x < ? and x > ?”); // Die SQL-Anfrage wird übersetzt. Die Anfrage selbst besitzt zwei Parameter. stmt.setInt(1, 20); // Erzeugung eines neuen Objekts der Klasse Statement ResultSet rs = stmt.executeQuery(“select count(*) from user_tables”); stmt.setInt(2, 10); // Übersetzen der Anfrage und Erzeugen eines neuen Objekts der Klasse ResultSet rs.next(); // Die Parameter der Anfrage werden gesetzt. ResultSet rs = stmt.executeQuery(); // durch die Operation next wird die Funktionalität eines Iterators bereit gestellt System.out.println(“Anzahl der Tabellen: “ + rs.getInt(1)); // Zugriff auf die Datenfelder mit get-Funktionen // Die Anfrage wird ausgeführt. Vorteil Werden Anfragen in ähnlicher Form mehrmals ausgeführt, erspart man sich hier die Zeit für das mehrmalige Übersetzen. Hoher Optimierungsaufwand lohnt sich beim Übersetzen Seite 140 von 159 Seite 141 von 159 Anwendungsprogrammierung Anwendungsprogrammierung Prozedurale Anfragen Dynamisches SQL Fast alle DBMS unterstützen die Möglichkeit Prozeduren zu schreiben, in denen eine Prozedur über Ein- und Ausgabeparameter verfügt. Wir werden in einem späteren Kapitel darauf kurz eingehen. JDBC erlaubt es, Anfragen dynamisch zu formulieren, da als Eingabe zur Ausführung eines SQL-Befehls ein beliebiges Objekt der Klasse String erwartet wird. String str; … ResultSet rs = con.createStatement().executeQuery(str); Als Problem erweist sich hier aber, daß zur Laufzeit des Programms der Typ des Resultats nicht bekannt ist. Um solche Typinformationen zur Laufzeit bereit zu stellen, gibt es die Klasse ResultSetMetaData, in der Operationen bereit gestellt werden, um z. B. die Anzahl der Attribute und die Datenbanktypen des Resultats abzufragen. Ein Objekt der Klasse wird dann durch ResultSetMetaData rsmd = rs.getMetaData(); erzeugt. Danach kann man z. B. durch den Aufruf Seite 142 von 159 Seite 143 von 159 Anwendungsprogrammierung Anwendungsprogrammierung 5.2 Embedded SQL (eSQL, SQLJ) int count = rsmd.getColumnCount(); basiert auf der Verwendung eines Vorübersetzers statische Festlegung der Datenbankoperationen zur Übersetzungszeit – Typenüberprüfung zwischen AWP und Datenbank durch den Vorübersetzer – einfache Übertragung von Daten aus der Datenbank ins AWP Verwendung des Cursor-Prinzips zum Durchlaufen von Relationen die Anzahl der Attribute der Ergebnisrelation abfragen und mit for (int i = 0; i < count; i++) { int sqlType = rsmd.getColumnType(i); … Quelldateien } wird eine ganze Zahl geliefert, die den Typ des i-ten Attributs identifiziert. Für diesen Typ kann dann z. B. die entsprechende get-Funktion aufgerufen werden. Vor-Compiler Zwischencode Host-Compiler DBMS-Bibliothek Host-Linker AWP DBMS-Server Seite 144 von 159 Seite 145 von 159 Anwendungsprogrammierung Syntaktische Kennung von Datenbankoperationen im Java AWP: #sql {SQL-Befehl} Ein SQL-Befehl bezieht sich auf die Objekte der Datenbank. Eine Ausnahme sind die sogenannten Hostvariablen, die zur Datenübertragung zwischen der Datenbank und dem AWP genutzt werden. – Eine Hostvariable wird wie eine gewöhnliche Variable von Java deklariert und genutzt. – Eine Hostvariable kann in einem SQL-Kommando genutzt werden, wobei dann dem Variablennamen ein “:” vorgestellt ist. Man kann nun auch Host-Variable auch dazu nutzen, Ergebnisse einer Anfrage aufzunehmen, wobei nur ein Ergebnis geliefert wird. Dabei wird nun an die selectKlausel das Schlüsselwort into gefolgt von den Hostvariablen angehängt. Beispiele #sql {select A, B from R where B > :x} Hier wird der Wert der Variable x in den SQL-Befehl eingesetzt. #sql {select A, B into :a, :b from R where Id = 7} Das Ergebnis wird nun in die Hostvariablen a und b übertragen (Ann.: Id ist Schlüsselkandidat). Seite 146 von 159 Datenbank Anwendungsprogrammierung Öffnen einer Verbindung zu einer Datenbank Ähnlich wie bei JDBC brauch man in SQLJ einen Bezug zu einer bestehenden Datenbank. Aus diesem Grund können nun ein Kontext definiert werden: #sql context verbinden Danach kann nun verbinden wie eine Klasse benutzt werden, die insbesondere folgenden Konstruktor besitzt: verbinden verbindungsObject = new verbinden("jdbc:oracle:thin:@venus.mathematik.uni-marburg.de:1521:Init_DB", "scott", "tiger"); Dieses Kontextobjekt kann nun bei einem sql-Befehl noch zusätzlich angegeben werden: #sql (verbindungsObject) {select A, B from R where B > :x} Wird bei einem sql-Befehl kein Kontextobjekt angegeben, so wird ein Defaultkontextobjekt genutzt. Beim Übersetzen des SQLJ-Programms können bereits Überprüfungen erfolgen, die bei JDBC erst zur Laufzeit möglich sind. Seite 147 von 159 Anwendungsprogrammierung Anfrageformulierung mit Iteratoren Für SQL-Anweisungen, die mehr als eine Antwort liefern, können nun Iteratoren (Cursor) definiert werden. Es wird unterschieden zwischen positionsbezogene und namenbezogene Iteratoren. Positionsbezogene Iteratoren (Beispiel): – Deklaration: #sql public iterator Pos (String, int); Damit wird ein Iteratortyp Pos mit zwei Komponenten definiert. – Danach kann man eine Variable des Typs deklarieren: Pos x; – und ein sql-Befehl an diese Variable binden: #sql x = {select A, B from R where B > 10}; – Der Zugriff auf die Ergebnismenge erfolgt dann i. A. in einer Schleife: while (true) { #sql {fetch :x into :a, :b}; if (x.endFetch()) break; System.out.println(a + “ verdient “ + b + “ DM”); } Anwendungsprogrammierung Namenbezogene Iteratoren – Deklaration: #sql public iterator Name (String A, int B); – Deklaration einer Variablen Name y; – Anbinden an einen SQL-Befehl: #sql y = {select A, B from R where B > 10}; – Zugriff auf die Ergebnismenge: while (y.next()) System.out.println(y.A() + “ verdient “ + y.B() + “ DM”); Man beachte hierbei, daß der Zugriff auf die Werte über Methodenaufrufe erfolgt, wobei der Name der Methode dem des Attributs entspricht. Mit der Methode next wird auf das nächste Tupel zugegriffen. Seite 148 von 159 Seite 149 von 159 Anwendungsprogrammierung Anwendungsprogrammierung Mengenwertige Änderungs- und Löschoperationen Solche Operationen verwenden auch Iteratoren, wobei die zu ändernde (löschende) Datenmenge an den Iterator gebunden wird. Danach können folgendermaßen die Änderungen vorgenommen werden. #sql public iterator Name (String A, int B); … Name y; … #sql y = {select A, B from R where B > 10}; … while (y.next()) #sql {update R set B = B + 10 where current of :y} 5.3 PL/SQL: eine Spracherweiterung PL/SQL ist eine von Oracle entwickelte Erweiterung von SQL um die Konzepte der imperativen Programmierung Syntax orientiert sich an die Programmiersprache Ada Standardisierung ist in Arbeit (siehe SQL/PSM) Vorteile gegenüber einer Hostsprache homogene Anbindung der imperativen Konzepte an SQL Typkonvertierungen entfallen plattformunabhängige Ausführung Nachteil: Damit wird nun der derzeit angesprochene Datensatz der Menge geändert. imperativen Konzepte sind für eine vollständige Entwicklung von AWP nicht ausreichend. Seite 150 von 159 Seite 151 von 159 Anwendungsprogrammierung PL/SQL Block Ein Block in PL/SQL ist folgendermaßen definiert: [<header> IS] [DECLARE <declarations>] BEGIN <Statements>* [EXCEPTION <exceptions>] END Deklarationsteil: Typdeklarationen – PL/SQL unterstützt insbesondere auch die Definition von Records: type PersonTyp is record // Record-Datentyp (Name varchar(50) Salary int); – Es gibt in PL/SQL Typen wie boolean, die es in SQL nicht gibt. – Weiterhin erlaubt PL/SQL auch noch indirekt über Relationen und Variablen erzeugte Typen (siehe Variablendeklaration) Anwendungsprogrammierung Variablendeklarationen – Eine Variable wird ähnlich wie bei Java durch Voranstellen des Typs deklariert: PersonTyp ang; – Eine Besonderheit ist insbesondere, daß die Datentypen der Relationen bei der Variablendeklaration benutzt werden können: meinBuch Bücher%rowtype; – Entsprechend wird durch deinBuch meinBuch%type; eine Variable vom Typ der Variable meinBuch deklariert. – Ähnlich wie in Pascal erstreckt sich der Gültigkeitsbereich einer Variablen auf den lokalen Block und alle Blöcke, die in diesem enthalten sind. Cursordeklarationen – konstante Cursor cursor AktBuch is // Cursor-Deklaration ähnlich wie in JDBC select * from Bücher; – parameterisierte Cursor cursor interessantePersonen(von int, bis int) is select * from Personen where Salary > von and Salary < bis; Seite 152 von 159 Seite 153 von 159 Anwendungsprogrammierung Anwendungsprogrammierung Verarbeitungskontrolle Prozeduren und Funktionen Imperative Ablaufsteuerung In PL/SQL ist es auch möglich Prozeduren und Funktionen zu deklarieren. – Eine Prozedur ist ein Block, der mit einem Namen versehen ist und über eine Parameterleiste verfügt. – Eine Funktion liefert durch den Befehl return stets ein Ergebnis. function totalSalary(int von, int bis) return int is begin declare p Personen%rowtype; int total; open interessantePersonen(von, bis); … return total; end – Prozeduren und Funktionen können die Parameter mit drei verschiedenen Optionen versehen (in, out, in out) procedure doit (par1 in ParamTyp1, par2 out ParamTyp2, par3 in out ParamTyp3, …) is <PL/SQL-Anweisungen mit Zuweisungen an die OUT-Parameter> bedingte Anweisung if <Bedingung> then <PL/SQL-Anweisung> end if; Schleife for <IndexVariable> in <Bereich> loop <PL/SQL-Anweisung> end loop; Verarbeitung eines Cursors Öffnen eines Cursors open AktBuch; open interessantePersonen(1000, 2000); Verarbeitung einer Antwortmenge – spezielles Schleifenkonstrukt für einen Cursor for meinBuch in AktBuch loop <PL/SQL-Anweisung> end loop; Seite 154 von 159 Seite 155 von 159 Anwendungsprogrammierung Stored Procedures Funktionen und Prozeduren können durch “create” im Datenbanksystem in übersetzter Form abgespeichert werden und entsprechend aufgerufen werden. – Dies hat insbesondere den Vorteil gegenüber dem bisherigen Ansatz von PL/SQL, daß die Anweisungen nicht mehr übersetzt werden müssen. Die Deklaration einer Prozedur (Funktion) folgt dem bereites vorher erläuterten Muster Cursor-Variablen Es ist oft günstig die Ergebnisse einer Stored Procedures durch Cursor-Variablen an das aufrufende PL/SQL-Programm zu geben. Eine Cursor-Variable ist eine Referenz auf eine Liste von Datensätzen. Folgende Typen von Cursor-Variablen gibt es: – starker Typ: type personenCurTyp is ref cursor Personen%rowtype; – schwache Typ: type allCurTp is ref cursor; Die Variablendeklaration wird wie üblich vorgenommen. Man beachte, daß zum Zeitpunkt der Deklaration die Cursor-Variable noch keinen Bezug zu einer Anfrage hat. Anwendungsprogrammierung Anbindung an Anfragen einer Cursor-Variable – Wird ein Cursor geöffnet, wird nun dir Variable an eine Anfrage gebunden: open personenCurTyp for select * from Personen where Sal > 1000; typische Verwendung – Öffnen einer Cursor-Variable in der Stored Procedure (Function) – Übergeben des Cursors an das AWP, wo dann die Datensätze verarbeitet werden. Trotz der vielen Vorteile, die Cursor-Variablen bieten, gibt es derzeit bei der Nutzung in Oracle noch sehr viele Einschränkungen: – Eine Cursor-Variable darf nicht im Update-Modus geöffnet werden. – Der Typ ref cursor gibt es nur in PL/SQL, aber nicht in SQL (siehe unten). – … (siehe Handbücher) Seite 156 von 159 Seite 157 von 159 Anwendungsprogrammierung Anwendungsprogrammierung Stored Functions in SQL Gespeicherte Funktionen können innerhalb von SQL deklariert und auch aufgerufen werden. Es gelten natürlich wieder einige Einschränkungen: – Die Funktion enthält keine Gruppierungsoperationen. – Alle Datentypen der Eingabe und der Ausgabe müssen im Datenbanksystem bekannt sein. Beispiel einer gespeicherten Funktion: create function simple(x in int) return int as begin return x / 101; end simple; Beispiel einer SQL-Anfrage: select Name, simple(Sal) from Personen; Fazit PL/SQL ist eine interessante Erweiterung von SQL, die schon vor Java eine plattformunabhängige Programmierung angeboten hatte. SQL wird insbesondere durch Stored Functions mächtiger Leider sind die Konzepte wie Cursor nicht orthogonal verwendbar. Es gibt viele Einschränkungen, was das Leben bei der Programmierung sehr schwierig macht. Seite 158 von 159 5.4 Weitere Kopplungsmöglichkeiten PASCAL/R: eine Erweiterung von Pascal für relationale Datenbanken Erweiterung des Typsystems von PASCAL: – Datentyp RELATION entspricht “SET OF TUPLE” – Operationen der relationalen Algebra – Iteratoren (wie Cursor) für die Verarbeitung von Relationen Skriptsprachen populärer Ansatz für die Erstellung von AWPs (z. B. Visual Basic) Nachteil – keine strenge Typisierung – keine Standards (schlechte Wartung der Applikationssoftware) – Durchmischung von verschiedenen Konzepten aus dem Bereich Datenbank, imperativen Programmierung, Benutzerschnittstellen und Regeln Vorteil: – schnelle Erstellung von AWP mit graphischen Benutzeroberflächen – komfortable Entwicklungsumgebung Seite 159 von 159