Basi di Dati e Sistemi Informativi SQL per Applicazioni Home page del corso: http://www.cs.unibo.it/~difelice/dbsi/ SQL per Applicazioni L’accesso tipico ad una base di dati avviene mediante applicazioni integrate nel sistema informativo. Integrazione di SQL in linguaggi di programmazione ad alto livello. Integrazione di SQL applicazioni/interfacce Web Information System, WIS) in (Web SQL per Applicazioni L’integrazione del linguaggio SQL con i normali linguaggi di programmazione di alto livello (C, C++, Java, etc) presenta alcuni ostacoli. Differenze di sintassi tra i linguaggi. Differenze nel meccanismo di gestione dei dati (SQL e’ set-oriented, mentre i linguaggi di programmazione sono per lo piu’ tuple-oriented). SQL per Applicazioni Due soluzioni per consentire l’uso di SQL all’interno di un programma scritto in un linguaggio di programmazione: SQL Embedded il programma sorgente contiene codice misto (es. SQL e Java) Call Level Interface (CLI) il programma sorgente contiene chiamate ad una libreria per la gestione dei dati (es. JDBC). SQL per Applicazioni SQL Embedded Il programma contiene istruzioni SQL, distinte dalle istruzioni del linguaggio sorgente attraverso l’uso di separatori (exec sql e ;) In fase di compilazione, il preprocessore riconosce le istruzioni SQL e le sostituisce con opportune chiamate di libreria del DBMS. SQL per Applicazioni main() { int i; … exec sql insert into Impiegati values(“Marco”,20); … } LIBRERIA ACCESSO DBMS PREPROCESSORE DBMS SQL per Applicazioni Il preprocessore esegue i seguenti task: Verifica la sintassi del codice SQL Sostituisce il codice SQL con le rispettive chiamate di libreria del DBMS. Necessario un preprocessore specifico per una specifica combinazione DMBSlinguaggio-piattaforma (es. Oracle/C/Linux). SQL per Applicazioni #include<stdlib.h> main(){ exec sql begin declare section; char *NomeDip = "Manutenzione"; char *CittaDip = "Pisa"; int NumeroDip = 20; exec sql end declare section; exec sql connect to utente@librobd; if (sqlca.sqlcode != 0) { printf("Connessione al DB non riuscita\n"); } else { exec sql insert into Dipartimento values(:NomeDip,:CittaDip,:NumeroDip); exec sql disconnect all; } } SQL per Applicazioni #include<stdlib.h> main(){ exec sql begin declare section; char *NomeDip = "Manutenzione"; char *CittaDip = "Pisa"; Variabili C condivise int NumeroDip = 20; exec sql end declare section; exec sql connect to utente@librobd; if (sqlca.sqlcode != 0) { printf("Connessione al DB non riuscita\n"); } else { exec sql insert into Dipartimento values(:NomeDip,:CittaDip,:NumeroDip); exec sql disconnect all; } } SQL per Applicazioni #include<stdlib.h> main(){ exec sql begin declare section; char *NomeDip = "Manutenzione"; char *CittaDip = "Pisa"; int NumeroDip = 20; exec sql end declare section; Struttura dati condivisa exec sql connect to utente@librobd; if (sqlca.sqlcode != 0) { printf("Connessione al DB non riuscita\n"); } else { exec sql insert into Dipartimento values(:NomeDip,:CittaDip,:NumeroDip); exec sql disconnect all; } } SQL per Applicazioni #include<stdlib.h> main(){ exec sql begin declare section; char *NomeDip = "Manutenzione"; char *CittaDip = "Pisa"; int NumeroDip = 20; exec sql end declare section; exec sql connect to utente@librobd; if (sqlca.sqlcode != 0) { printf("Connessione al DB non riuscita\n"); } else { exec sql insert into Dipartimento values(:NomeDip,:CittaDip,:NumeroDip); exec sql disconnect all; } Esecuzione della query } SQL per Applicazioni CODICE SORGENTE int main() { exec sql connect to universita user pguser identified by pguser; exec sql create table studente (matricola integer primary key, nome varchar(20), annodicorso integer); exec sql disconnect; return 0; } SQL per Applicazioni #include <ecpgtype.h> #include <ecpglib.h> CODICE PRE-COMPILATO #include <ecpgerrno.h> #include <sqlca.h> int main() { ECPGconnect(__LINE__, "universita" , "pguser" , "pguser" , NULL, 0); ECPGdo(__LINE__, NULL, "create table studente ( matricola integer primary key , nome varchar ( 20 ) , annodicorso integer )", ECPGt_EOIT, ECPGt_EORT); ECPGdisconnect(__LINE__, "CURRENT"); return 0; } SQL per Applicazioni Conflitto d’impedenza sui dati: SQL e’ un linguaggio set-oriented, un’interrogazione puo’ restituire come risultato un’intera tabella (es. SELECT). I linguaggi di programmazione accedono agli elementi di una struttura dati scandendo le righe una per volta. SOLUZIONE: Usare i cursori … SQL per Applicazioni Cursore Iteratore per accedere alla righe di una tabella una alla volta. BUFFER del PROGRAMMA CODICE SORGENTE DBMS Il cursore puo’ muoversi avanti/indietro sulle righe della tabella. SQL per Applicazioni Definizione del cursore declare NomeCursore cursor for Select SQLCode [scroll] declare NomeCursore scroll cursor for SELECT Cognome, Stipendio FROM Impiegati WHERE (Stipendio > 1000) SQL per Applicazioni Esecuzione delle interogazione open NomeCursore Deallocazione delle interogazione close NomeCursore Recupero dei risultati fetch NomeCursore into ListaVariabili SQL per Applicazioni void VisualizzaStipendiDipart(char NomeDip[]) { char Nome[20], Cognome[20]; long int Stipendio; $ declare ImpDip cursor for select Nome, Cognome, Stipendio from Impiegato where Dipart = :NomeDip; printf("Dipartimento %s\n",NomeDip); $ open ImpDip; $ fetch ImpDip into :Nome, :Cognome, :Stipendio; while (sqlcode == 0) { printf("Attuale stipendio: %d\n",Stipendio); $ fetch ImpDip into :Nome, :Cognome, :Stipendio; } $ close cursor ImpDip; } SQL per Applicazioni SQLJ Tecnica per includere codice SQL all’interno di programmi Java. import java.sql.*; public class SimpleDemoSQLJ { public Address getEmployeeAddress(int empno) throws SQLException { Address addr; #sql { SELECT office_addr INTO :addr FROM employees WHERE empnumber = :empno }; return addr; } } SQL per Applicazioni Tecnica vista fin qui: Static SQL Embedded. PROBLEMA: Non sempre le istruzioni SQL da eseguire sono note quando si scrive il programma … Dynamic SQL Embedded Tecnica che permette di eseguire istruzioni SQL costruite dal programma (o addirittura ricevute dal programma attraverso parametri o da input). SQL per Applicazioni Due soluzioni per consentire l’uso di SQL all’interno di un programma scritto in un linguaggio di programmazione: SQL Embedded il programma sorgente contiene codice misto (es. SQL e Java) Call Level Interface (CLI) il programma sorgente contiene chiamate ad una libreria per la gestione dei dati (es. JDBC). SQL per Applicazioni Nella modalita’ CLI, il linguaggio sorgente offre una libreria specifica per interagire con un DBMS (indipendente dal DBMS). INTERAZIONE 1. Si crea una connessione con il DBMS. 2. Si invia un comando SQL con la richiesta. 3. Si riceve la risposta dal DBMS, e la si gestisce per mezzo di cursori. 4. Si chiude la connessione con il DBMS. SQL per Applicazioni Sono disponibili diverse tecnologie di CLI per piattaforme/linguaggi differenti… Tecnologia Piattaforma ODBC Interfaccia standard, multi-piattaforma OLE DB Microsoft, basata sul modello .COM ADO Microsoft, interfaccia record-oriented ADO.NET Microsoft, estensione ADO per piattaforma .NET JDBC Interfaccia Java, multi-piattaforma SQL per Applicazioni Java Database Connectivity (JDBC) insieme di librerie Java per accesso ai dati di un database relazionale, indipendenti dallo specifico DBMS in uso. JDBC denota un’interfaccia implementata da classi dette driver. standard, Un driver e’ specifico di un certo DBMS (es. MySQL Connector/J per MySQL) SQL per Applicazioni JDBC puo’ essere usato in 4 architetture: 1. Bridge JDBC-ODBC: si richiama un driver ODBC, che deve essere disponibile sul client. 2. Driver nativo sul client: si richiama un componente proprietario (non necessariamente Java) sul client. 3. Driver puro Java con server intermedio ("middleware server"): si comunica via protocollo di rete con il server intermedio, che non deve risiedere sul client. 4. Driver puro Java, con connessione al DBMS: si interagisce direttamente con il DBMS. SQL per Applicazioni SQL per Applicazioni Vantaggi di SQLJ su JDBC: SQLJ richiede meno codice. La sintassi delle istruzioni SQL puo’ essere controllata durante la compilazione Vantaggi di JDBC su SQLJ: SQLJ richiede un preprocessore, non sempre disponibile su tutte le piattaforme/DBMS. SQL per Applicazioni Le API di JDBC (java.sql) consentono di: Creare la connessione con un DBMS (supponendo di aver gia’ installato i driver). Eseguire query SQL sul DBMS. Processare il risultato della query SQL. Gestire transazioni ed operazioni batch. SQL per Applicazioni Per creare una connessione, sono disponibili le seguenti classi: Driver interfaccia di collegamento verso il DBMS (implementato da ogni DBMS). DriverManager servizio di base per la gestione dei drivers attualmente presenti. Connection sessione di connessione ad uno specifico DBMS, il cui driver e’ gestito dal DriverManager. SQL per Applicazioni Supponendo di avere i driver del DBMS (es. MySQL) installati sul proprio host, il passo successivo e’ creare la connessione dal DriverManager. static Connection getConnection(String url, String user, String password) url URL del DB, tipo e nome. user, password credenziali d’accesso. SQL per Applicazioni Supponendo di avere i driver del DBMS (es. MySQL) installati sul proprio host, il passo successivo e’ creare la connessione dal DriverManager. String url=“jdbc:mysql://localhost:3306/provadb”; String user=“root”; String password=“root”; Connection con=DriverManager.getConnection(url, user, password); SQL per Applicazioni Per eseguire una query e processarne il risultato, sono disponibili le seguenti classi: Connection sessione di connessione ad uno specifico DBMS, il cui driver e’ gestito dal DriverManager. Statement componente usata per eseguire una query SQL su una Connection. ResultSet tabella di dati che contiene il risultato di esecuzione di uno Statement. SQL per Applicazioni Una volta creata la Connection, e’ possibile creare uno Statement associato, e quindi eseguire una query sul DBMS. Connection con=DriverManager.getConnection(url, password); Statement state= con.createStatement(); String sql=“ SELECT * FROM IMPIEGATI”; ResultSet rs=state.executeQuery(sql); user, SQL per Applicazioni Per gestire il risultato di una query, si utilizza la classe ResultSet tabella di dati generata dall’esecuzione di una query SQL + cursore. ResultSet mantiene un puntatore alla riga corrente della tabella. Tramite il metodo next() e’ possibile spostarsi alla riga successiva; next() ritorna falso quando non ci sono piu’ righe nel ResultSet. SQL per Applicazioni Per gestire il risultato di una query, si utilizza la classe ResultSet tabella di dati generata dall’esecuzione di una query SQL + cursore. ResultSet mantiene un puntatore alla riga corrente della tabella. Tramite il metodo next() e’ possibile spostarsi alla riga successiva; next() ritorna falso quando non ci sono piu’ righe nel ResultSet. SQL per Applicazioni Data una riga del ResultSet, si possono utilizzare metodi tipizzati per accedere ad una specifica colonna, conoscendone l’indice: String getString(int columnIndex) boolean getBoolean(int columnIndex) int getInt(int columnIndex) float getFloat(int columnIndex) Date getDate(int columnIndex) … SQL per Applicazioni Data una riga del ResultSet, si possono utilizzare metodi tipizzati per accedere ad una specifica colonna, conoscendone l’indice: Statement st=con.createStatement(); ResultSet rs=st.executeQuery(“SELECT NOME, STIPENDIO FROM IMPIEGATI”); while(rs.next) { System.out.println(rs.getString(1)); System.out.println(rs.getInt()); } SQL per Applicazioni Eseguire una query senza un controllo preventivo sui parametri forniti dall’utente puo’ essere pericoloso dal punto di vista della sicurezza (es. SQL Injection); per questo, si preferisce: Preparare lo Statement inserendo dei placeholder al posto dei parametri della query. Fornire i valori dei parametri, ed eseguire la query (in maniera sicura). SQL per Applicazioni Esempio di esecuzione di una query SQL mediante PreparedStatement (anziche’ Statement). PreparedStatement pst; String author=“Michele”; pst=con.PreparedStatement(“INSERT IMPIEGATI(Nome) VALUES(?)”); pst.setString(1, author); pst.executeUpdate(); INTO SQL per Applicazioni In Java JDBC, una transazione indica un insieme di comandi SQL cui puo’ essere associata una modalita’ di esecuzione del “tutto o niente”, ossia: Tutti i comandi della transazione sono stati eseguiti correttamente, OPPURE: In caso di errori, e’ possibile fare rollback dell’esecuzione di tutti comandi della transazione. SQL per Applicazioni Per costruire una transazione in JDBC: Si costruiscono le query, e si eseguono mediante executeUpdate() o executeQuery(). Si utilizza il metodo commit() del Connection per eseguire le operazioni sul DBMS in maniera persistente. Si utilizza il metodo rollback() del Connection per annullare le operazioni sul DBMS eseguite dopo la precedente commit. SQL per Applicazioni try { st.executeupdate(“UPDATE IMPIEGATO Name=“Marco” WHERE Name=“Michele”); SET st.executeupdate(“UPDATE IMPIEGATO Name=“Mario” WHERE Name=“Giovanni”); SET con.commit(); } catch(SQLException) { con.rollback(); } SQL per Applicazioni Per poter lavorare con le transazioni in JDBC, e’ necessario impostare a false la modalita’ di autocommit dei comandi SQL. Connection con=DriverManager(url. user, password); con.setAutoCommit(false); Se si utilizza l’autocommit, non e’ possibile fare rollback delle transazioni … SQL per Applicazioni Nel caso si debbano eseguire operazioni di DELETE, UPDATE, INSERT in serie, e’ possibile usare il meccanismo dei batch update in JDBC: Le query sono raggruppate in un unica query, che viene inviata al DMBS (vantaggi in terminimi di efficienza di esecuzione). Il batch restituisce un array con il numero di righe affette da ciascuna query. SQL per Applicazioni Nel caso si debbano eseguire operazioni di DELETE, UPDATE, INSERT in serie, e’ possibile usare il meccanismo dei batch update in JDBC: addBatch(String sql) aggiunge una query SQL all’insieme batch. executeBatch() esegue il batch update, ritorna un array di dimensione pari alle query SQL, con il numero di righe affette da ciascuna. SQL per Applicazioni st=con.createStatement(); st.addBatch(“DELETE FROM Impiegati”); st.addBatch(“INSERT INTO IMPIEGATI(Nome) VALUES(‘Marco’”); st.addBatch(“INSERT INTO IMPIEGATI(Nome) VALUES(‘Michele’”); int counts=st.executeBatch(); con.commit(); SQL per Applicazioni st=con.createStatement(); st.addBatch(“DELETE FROM Impiegati”); st.addBatch(“INSERT INTO IMPIEGATI(Nome) VALUES(‘Marco’”); st.addBatch(“INSERT INTO IMPIEGATI(Nome) VALUES(‘Michele’”); int counts=st.executeBatch(); con.commit(); SQL per Applicazioni In un DBMS, le informazioni sul modello logico di rappresentazione di una tabella si dicono metadati, e sono rappresentati a sua volta tramite tabelle. Nomi delle tabelle Nomi degli attributi di una tabella Tipi degli attributi Vincoli relazionali … SQL per Applicazioni In JDBC, e’ possibile accedere al campo metadati di un ResultSet tramite il metodo getMetaData(). PreparedStatement pst; pst=con.PreparedStatement(“SELECT Nome, Cognome FROM IMPIEGATI”); ResultSet rs=pst.executeQuery(); ResultSetMetaData meta=pst.getMetaData(); String column1=meta.getColumnName(1); String column2=meta.getColumnName(2);