Programmierung von Datenbank Anwendungen ESQL, ODBC, JDBC und co IS: Datenbanken, © Till Hänisch 2000 Methoden ! bisher interaktive Verwendung von SQL ! ! ! Terminal Skripte Ausführen von SQL aus Programmiersprache heraus ! ! proprietäre APIs standardisierte Schnittstellen ! ! statisch (embedded SQL) dynamisch (ODBC, JDBC) IS: Datenbanken, © Till Hänisch 2000 Embedded SQL ! Einbettung von SQL-Statements in Wirts-Sprache ! ! ! i.w. gleiche Syntax wie bei interaktivem SQL, zusätzlich Konstrukte für ! ! ! ! ! C,COBOL, PL/1, FORTRAN, PASCAL,... Vor-Übesetzung des Programms in Wirts-Sprache (precompile) Einbettung der SQL-Befehle Fehlerbehandlung Übergabe von Variableninhalten Übergabe von Query-Ergebnissen Einfache, sprachunabhängige Syntax für Precompiler ! ! EXEC SQL Präfixc für SQL-Kommandos ":" als Kennzeichner für Variablen IS: Datenbanken, © Till Hänisch 2000 Vorgehen example.pc C Source mit eingebettetem SQL Precompiler für C example.c C Source, SQl durch DBMS-spezifische Funktionsaufrufe ersetzt C Compiler example.o Linker example[.exe] Object Code DBMSLibrary ausführbares Programm IS: Datenbanken, © Till Hänisch 2000 Tupelvariablen SQL liefert Tupelmenge, Darstellung in C, PASCAL,... "Impedence mismatch" Typkonzept des RDBMS und der Wirtssprache passen nicht zusammen Lösung: Cursor Iterator, Tupel-Zeiger für satzweise Verarbeitung EXEC SQL DECLARE name CURSOR FOR select statm. Operationen: OPEN führt Abfrage aus CLOSE FETCH name INTO :var1, :var2,... überträgt Werte der Attribute des aktuellen Datensatzes in Variablen und setzt Zeiger eins weiter IS: Datenbanken, © Till Hänisch 2000 prinzipieller Aufbau EXEC SQL BEGIN DECLARE SECTION; Deklaration der Übergabevariablen EXEC SQL END DECLARE SECTION; EXEC SQL INCLUDE SQLCA; EXEC SQL CONNECT :userid IDENTIFIED BY :password EXEC SQL DECLARE c CURSOR FOR SELECT * FROM EMP; EXEC SQL OPEN c; for(;;) { } EXEC SQL FETCH ... EXEC SQL CLOSE c; EXEC SQL DISCONNECT; IS: Datenbanken, © Till Hänisch 2000 Anmerkungen ! statisches SQL ! ! ! ! ! tupelweise Verarbeitung u.U. nicht effizient ! ! wird im Programm fest definiert und kann vom Precompiler ausgewertet werden SQL muß vorher bekannt sein ! Wie ist isql implementiert ? dynamisches SQL (in ESQL nicht möglich) ein Funktionsaufruf pro Tupel - > Array Fetch,... ESQL ist standardisiert ! ! ! wie SQL selbst passende Umgebung muß zum Programm gelinkt werden Geht nicht, wenn Auswahl des DB-Systems erst zur Laufzeit erfolgen soll ! -> ODBC IS: Datenbanken, © Till Hänisch 2000 Native API, Beispiel OCI ! ! ! Oracle Call Interface (CLI) kompliziert, mächtig, Oracle spezifisch bestimmter Funktionen nur mit OCI ! ! mehrere Transaktionen BLOBs static char cmd[] = "INSERT INTO MESSAGE(SEVERITY,CODE) VALUES (:Severity,:Code)"; if (!olog(&lda, hda, (unsigned char *)pszUserid, -1, (unsigned char *)pszPassword, -1, (unsigned char *)pszNetAlias, -1, (ub4)OCI_LM_DEF)) if (!oopen(&cda, &lda, (text *) 0, -1, -1, (text *) 0, -1)) if (!oparse(&cda,(unsigned char *) cmd,-1,0,2)) ProcessMessage(&msg); /* normaler C-Code */ if ((!obndrv(&cda,(unsigned char *)":Severity",-1,(unsigned char *) Severity, strlen(Severity), VARCHAR2_TYPE,-1,0,0,-1,-1)) || (obndrv(&cda,(unsigned char *)":Code",-1,(unsigned char *)Code, strlen(Code),VARCHAR2_TYPE,-1,0,0,-1,-1))) if (!oexec(&cda)) IS: Datenbanken, © Till Hänisch 2000 Embedded SQL ! ! ! ! SQL wird in Standard C (COBOL,...) eingebettet Quellcode datenbankunabhängig Precompiler, der OCI erzeugt ausführbares Programm ist datenbankabhängig EXEC SQL BEGIN DECLARE SECTION; VARCHAR pszUserid[20]; VARCHAR pszPassword[20]; VARCHAR Severity[5]; VARCHAR Code[10]; EXEC SQL END DECLARE SECTION; EXEC SQL CONNECT :username IDENTIFIED BY :password; ProcessMessage(&msg); /* normaler C-Code */ EXEC SQL INSERT INTO MESSAGE(Severity, Code) VALUES (:Severity, :Code); IS: Datenbanken, © Till Hänisch 2000 Warum ODBC ? Am Anfang waren die Daten, sie waren unformatiert, und Dunkelheit herrschte auf der Erde. Und Codd sagte: „Es werde ein relationales Datenmodell“. Und so geschah es. Und Codd sagte: „Die Daten sollen von den Datenstrukturen der Applikationsprogramme getrennt werden, so daß eine Datenunabhängigkeit entstehe“. Und es war gut. Und die DBMS-Hersteller sagten: „Wir wollen fruchtbar sein und uns mehren“. Und so geschah es. Und die Benutzer sagten: „Wir wollen Applikationen einsetzen, um auf die Daten von allen DBMS-Herstellern zuzugreifen“ Und die Applikationsentwickler senkten die Häupter und sagten: „Wir müssen durch das finstere Tal mit den Precompilern oder CLI‘s, Kommunikationsstacks und Protokollen aller Hersteller wandern“. Und es war nicht gut ... Und so entstand ODBC (Kyle Geiger, Inside ODBC) IS: Datenbanken, © Till Hänisch 2000 ODBC-Architektur Anwendung ODBC Treibermanager ODBC Treiber ODBC Treiber ODBC Treiber Datenbank Datenbank Datenbank IS: Datenbanken, © Till Hänisch 2000 ODBC ! ! ! Open Database Connectivity Industriestandard (Microsoft, IBM,...) datenbankunabhängig static char cmd[] = "INSERT INTO MESSAGE(SEVERITY,CODE) VALUES (?,?)"; rc=SQLAllocEnv(&henv); rc=SQLAllocConnect(henv,&hdbc); rc=SQLConnect(hdbc,“Kurs",SQL_NTS,ODBC_USERNAME,SQL_NTS,ODBC_PASSWORD,SQL_NTS); rc=SQLAllocStmt(hdbc,&hstmt); rc = SQLPrepare(hstmt,cmd,SQL_NTS); rc = SQLBindParameter(hstmt,1,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_VARCHAR, strlen(Severity),0,Severity,0,NULL); rc = SQLBindParameter(hstmt,2,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_VARCHAR, strlen(Code),0,Code,0,NULL); rc = SQLExecute(hstmt); IS: Datenbanken, © Till Hänisch 2000 JDBC ! Java Database Connectivity import java.sql.*; class Employee { public static void main (String args []) throws SQLException { DriverManager.registerDriver(new com.sybase.jdbc.SybDriver()); Connection conn = DriverManager.getConnection ("jdbc:sybase:Tds:vaio:9898", "ba", "isdb00"); Statement stmt = conn.createStatement (); ResultSet rset = stmt.executeQuery ("select empno,ename from emp"); // Iterate through the result and print the employee names while (rset.next ()) System.out.println (rset.getInt(1) + " " + rset.getString (2)); } } IS: Datenbanken, © Till Hänisch 2000 JDBC - Statement Statement stmt = conn.createStatement (); Stmt.executeQuery(“SELECT * FROM EMP“); PreparedStatement ps = conn.prepareStatement( „SELECT * FROM EMP WHERE EMPNO = ?“); ! ! Kann SQL-Anweisungen ausführen Spezialfall: PreparedStatement: ! ! Bei mehrfacher Ausführung bleibt SQL-Text gleich, muß nicht bei jeder Ausführung analysiert werden Bietet u.U. bessere Performance IS: Datenbanken, © Till Hänisch 2000 JDBC - ResultSet ResultSet rset = stmt.executeQuery ("select empno,ename from emp"); while (rset.next ()) System.out.println (rset.getInt(1) + " " + rset.getString (2)); } ! ! ! ! ! executeQuery liefert ResultSet-Objekt zurück Kapselt Cursor, kann Ergebnis zeilenweise durchgehen Steht nach executeQuery vor dem ersten Datensatz next() geht einen Datensatz weiter, liefert true zurück, solange aktueller Datensatz gültig Zugriff auf Spalten mit getXXX (getInt, getString,...) IS: Datenbanken, © Till Hänisch 2000 JDBC - PreparedStatement PreparedStatement ps = conn.prepareStatement( „SELECT * FROM EMP WHERE EMPNO = ?“); for(...) { ps.setInt(1,4711); ResultSet rset = ps.execute(); ... } ! Platzhalter werden mit setXXX mit Werten belegt ! Erster Parameter ist Index des Platzhalters IS: Datenbanken, © Till Hänisch 2000 JDBC - Transaktionskontrolle conn.setAutoCommit(false); Conn.commit(); Conn.rollback(); Conn.setTransactionIsolation( Connection.TRANSACTION_SERIALIZABLE); ! AutoCommit führt nach jedem execute ein commit durch (oft Standard, sinnvoll ?) ! ! Manuelle Commit-Steuerung oft sinnvoller Nicht alle Isolationlevel werden von allen Datenbanken unterstützt (siehe Datenbanken II) IS: Datenbanken, © Till Hänisch 2000 Performance SQL> describe lagerbewegung Name ----------------------------------------NUMMER ART VORGANGSNUMMER BESTELLNUMMER TEILENUMMER MENGE LAGERORT DATUM SQL> select count(*) from lagerbewegung; COUNT(*) ---------159804 IS: Datenbanken, © Till Hänisch 2000 Null? -------NOT NULL NOT NULL NOT NOT NOT NOT NULL NULL NULL NULL Type ----------------NUMBER NUMBER(2) VARCHAR2(10) NUMBER VARCHAR2(15) NUMBER(10,3) VARCHAR2(12) DATE Szenario ! ! ! Lesen aller Records aus einer Tabelle und schreiben in andere, zunächst intern durch DB Dann verschiedene Programmiertechniken Idee: Bearbeitung der Records nötig, z.B. ergänzen von Werten ... SQL> create table fastest as select * from lagerbewegung; Table created. Elapsed: 00:00:03.91 IS: Datenbanken, © Till Hänisch 2000 Einschub PL/SQL ! ! ! ! 3G Sprache von Oracle ! Andere etwa: Transact SQL (Microsoft/ Sybase) SQL PL (IBM) An ADA angelehnt Direkte Einbettung von SQL-Statements in Programm ! Normalerweise statisches SQL Wozu ? ! Definierte Schnittstelle zum Zugriff auf Daten ! Wird (nach compile) in der DB gespeichert und dort ausgeführt (Performance) ! Konsistenz, z.B. direkter Zugriff auf Datentypen in DB ! User defined functions, z.B. ! „select finance.AuftragTotal('01-1691/01') from dual;“ Usw. Erstellung von stored procedures usw. ! ! IS: Datenbanken, © Till Hänisch 2000 Stored procedure create or replace procedure perf_proc as cursor c is select * from lagerbewegung; c_rec c%rowtype; i integer; begin open c; i := 0; fetch c into c_rec; while c%found loop insert into perf(nummer,art,vorgangsnummer,...) values (c_rec.nummer,c_rec.art...); i := i + 1; if ((i mod 1000) = 0) then commit; end if; fetch c into c_rec; end loop; close c; SQL> execute perf_proc end; PL/SQL procedure successfully completed. / Elapsed: 00:01:56.90 IS: Datenbanken, © Till Hänisch 2000 Java, dynamisches SQL ResultSet rset = stmt.executeQuery ("select nummer, ... where rownum < 10000"); // Zunächst nur 10.000 records .... while (rset.next ()) { sql = "insert into perf(nummer,art...)" + “values (" + rset.getInt(1) + "," + ...)"; ins.execute(sql); NumberOfRecords++; if ((NumberOfRecords % CommitInterval) == 0) conn.commit(); } oracle$ java Perftest elapsed time: 38.146 seconds für 10.000 records, Entspricht insgesamt (extrapoliert) ca. 11 Minuten IS: Datenbanken, © Till Hänisch 2000 Prepared statement PreparedStatement ps = conn.prepareStatement("insert into perf(..., values (?,?,?,?,?,?,?,to_date(?,'yyyy-mm-dd hh24:mi:ss'))"); ResultSet rset = stmt.executeQuery ("select nummer ... "); while (rset.next ()) { ps.setInt(1,rset.getInt(1)); ... ps.execute(); NumberOfRecords++; if ((NumberOfRecords % CommitInterval) == 0) conn.commit(); } oracle$ java PerftestPrepared elapsed time: 17.573 seconds für 10.000 records, Entspricht insgesamt (extrapoliert) ca. 5 Minuten ! IS: Datenbanken, © Till Hänisch 2000 Vergleich Technik Dauer Table Copy ohne Transaktion Stored procedure 4 Sekunden Java mit dynamischem Statement Java mit prepared statement 11 Minuten 2 Minuten 5 Minuten IS: Datenbanken, © Till Hänisch 2000 Besser ... (DB-spezifisch) create or replace procedure perf_bulk as type recs is table of lagerbewegung%rowtype; data recs; cursor c is select * from lagerbewegung; i integer; begin open c; loop fetch c bulk collect into data limit 10000; forall i in 1..data.count insert into perf values data(i); commit; exit when c%notfound; end loop; close c; end; SQL> execute perf_bulk PL/SQL procedure successfully completed. Elapsed: 00:00:13.36 IS: Datenbanken, © Till Hänisch 2000 NB: Commit-Frequenz Commit alle n records Dauer (sec) 1 1132 10 384 100 285 1000 280 10000 275 Eine Transaktion 259 (*) (*): möglicherweise phys. Speicher zu klein IS: Datenbanken, © Till Hänisch 2000 Aktive Datenbanken ! ! „normale“ Datenbanken speichern Daten Aktive Datenbanken führen Aktionen aus (ECA-Modell) ! Event ! ! Condition ! ! Z.B. Änderung von Daten, Zeitpunkt,... Bedingung, unter der Action ausgeführt wird Action IS: Datenbanken, © Till Hänisch 2000 Wozu ? ! ! ! ! ! Denormalisierte Relationen Protokollierung Replikation Materialized views Einfache Integritätsbedingungen durch constraints, komplexere ? ! ! Business rules Z.B. „Fakturierte Aufträge dürfen nicht geändert werden“ IS: Datenbanken, © Till Hänisch 2000 Beispiel (Oracle Trigger) CREATE OR REPLACE TRIGGER TUpdAuftrag BEFORE UPDATE ON Auftrag FOR EACH ROW BEGIN IF ((:old.Status = Globvar.Stat_Auftrag_abgerechnet) AND (USER <> '&1')) THEN Error.raise_error(Error.en_Abgerechnet); END IF; END; CREATE OR REPLACE TRIGGER TDelAuftrag BEFORE DELETE ON Auftrag FOR EACH ROW BEGIN IF (:old.Status = Globvar.Stat_Auftrag_abgerechnet) THEN Error.raise_error(Error.en_Abgerechnet); IS: Datenbanken, © Till Hänisch 2000 END IF; END;