La programmazione Angelo Chianese, Vincenzo Moscato, Antonio Picariello, Lucio Sansone Basi di dati per la gestione dell'informazione 2/ed McGraw-Hill Capitolo 8 Appunti dalle lezioni Sistemi informativi e basi di dati La Progettazione Concettuale Il modello relazionale La Progettazione Logica Utilizzo di un DBMS Reale La Progettazione Fisica SQL come DDL SQL come DML SQL come DCL Strumenti CASE Forme normali Programmazione Transazioni e tecnologie di supporto Basi di dati direzionali Basi di dati distribuite 1 Uso diretto SQL Linguaggi ad hoc Stored Procedures Triggers ODBC In linguaggi “classici” Call Level Interface JDBC SQL-embedded I comandi SQL sono immersi direttamente nel linguaggio ospite. Un precompilatore o preprocessore esamina prima di tutto il codice sorgente del programma per identificare le istruzioni di interazione con la base di dati ed estrarle per l’elaborazione con il DBMS. Queste vengono sostituite nel programma da chiamate di funzione al codice generato dal DBMS. La comunicazione dei risultati elaborativi del DBMS all’applicazione avviene a mezzo di apposite variabili condivise (e.g., SQLCODE, SQLSTATE), mentre le variabili del programma possono essere usate come parametri nelle istruzioni SQL 2 ESEMPIO EXEC SQL BEGIN DECLARE SECTION; varchar nome [50]; varchar cognome [50]; varchar matricola [10]; int SQLCODE; EXEC SQL END DECLARE SECTION; printf(’’Immettere matricola: ’’); scanf(’’%s’’,&matricola); EXEC SQL; SELECT NOME, COGNOME into :nome, :cognome FROM STUDENTI WHERE MATRICOLA=:matricola; if (SQLCODE==0) printf(’’Studente: %s %s\n’’, nome, cognome); else printf(’’Matricola inesistente’’); SQL-embedded Il linguaggio SQL-embedded risulta un approccio di programmazione delle basi di dati statico: il testo dell’interrogazione è scritto all’interno del programma e non può essere cambiato senza ricompilare o rielaborare il codice sorgente Inoltre poiché ogni applicazione per interfacciarsi ad un DBMS fa riferimento a delle proprie librerie (di solito scritte C), se questa ultima deve utilizzare per qualche ragione un nuovo database, diverso dal precedente, occorre riscrivere tutto il codice di gestione dei dati. 3 SQL/CLI (Call Level Interface): Apposite libreria di funzioni sono rese disponibili al linguaggio ospite per l’interfacciamento alla base di dati. Vi sono funzioni per la connessione al database, per l’esecuzione di interrogazione, per l’aggiornamento dei dati, e così via. Accesso mediante SQL/CLI Standard a “livello di chiamata” Approccio dinamico per la programmazione delle basi di dati: Per l’accesso alla base di dati viene usata una libreria di funzioni che da un lato fornisce maggiore flessibilità e non richiede la presenza di alcun preprocessore Comporta, però, che la verifica della sintassi e altri controlli sui comandi SQL avvenga solo al momento dell’esecuzione del programma. 4 SQl/CLI Basandosi sullo standard SQL/CLI, ogni DBMS mette a disposizione apposite interfacce di programmazione (API) per i principali linguaggi che permettevano alle applicazioni scritte in quel linguaggio di “interrogare” ODBC Col passare degli anni nasce poi l’esigenza di standardizzare l’interfaccia attraverso la quale un’applicazione poteva accedere ad una qualsiasi base di dati relazionale garantendo l’interoperabilità. Nel 1991 la Microsoft propone uno standard per la comunicazione con database remoti noto col nome di ODBC (Open Database Connectivity). 5 ODBC Nell’architettura ODBC il collegamento tra un’applicazione client e il server della base di dati, richiede l’uso di un driver, ovvero di una libreria che viene collegata dinamicamente all’applicazione e da essa invocata quando si vuole accedere alla base di dati. Ogni driver maschera le differenza di interazione legate non solo al DBMS, ma (in parte) anche al sistema operativo e ai protocolli di rete utilizzati. I driver astraggono dai protocolli specifici dei produttori dei DBMS, fornendo un’interfaccia di programmazione delle applicazioni comune ai client del database ODBC L’ applicazione Il driver manager I driver La sorgente dati o data source Application ODBC driver manager Driver (DBMS/OS/network) Data Source 6 Esempio ODBC #include <stdio.h> #include <string.h> #include <windows.h> #include <sql.h> #include <sqlext.h> #define MAX_DATA 100 void main() { HENV henv; HDBC hdbc; HSTMT hstmt; RETCODE rc; // Environment handle // Connection handle // Statement handle // Return code for ODBC functions SQLAllocEnv(&henv); SQLAllocConnect(henv, &hdbc); rc = SQLConnect(hdbc, (unsigned char *) "esempioODBC", SQL_NTS, NULL, SQL_NTS, NULL, SQL_NTS); SQLAllocStmt(hdbc, &hstmt); if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO)) { printf("Connessione non riuscita!\n"); exit(0); } printf("Connessione riuscita!\n"); 7 Esempio ODBC rc=SQLExecDirect(hstmt, (unsigned char *) "select * from studenti", SQL_NTS); if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO)) { /*Qui va il codice per la gestione dell'errore*/ printf("Query non riuscita!\n"); exit(0); } printf("Query eseguita correttamente!\n"); short int cols; /*Numero di colonne nel risultato*/ rc = SQLNumResultCols(hstmt,&cols); if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO)) { printf("Impossibile estrarre il numero di colonne!\n"); exit(0); } printf("%d colonne nel risultato\n",cols); Esempio ODBC rc1 = SQLFetch(hstmt); printf("\nRisultato della query\n"); while (rc1 == SQL_SUCCESS) { int i=1; while ((rc2 = SQLGetData(hstmt, i, SQL_CHAR, szData, MAX_DATA - 1,&cbData)) == SQL_SUCCESS) { if (cbData > 0) printf("%s\t", szData); else printf("NULL\t", szData); i++; } rc1 = SQLFetch(hstmt); printf("\n"); } printf("\n"); SQLFreeStmt(hstmt, SQL_DROP); SQLDisconnect(hdbc); SQLFreeConnect(hdbc); SQLFreeEnv(henv); } 8 JDBC JDBC é un interfaccia di programmazione (API) ad oggetti basata sullo standard CLI e definita dalla Sun Microsystems per l’accesso ad un database in maniera indipendente dalla piattaforma. JDBC è divenuto oramai uno “standard de facto” per lo sviluppo di applicazioni JAVA “DBoriented” e fa parte del pacchetto software JDK dalla versione 1.1. Vantaggi: Astrazione dal DB Riuso Sintassi semplice http://download.oracle.com/javase/tutorial/jdbc/index.html 9 4 passi fondamentali Apertura della connessione Esecuzione di comandi SQL Elaborazione dei risultati Chiusura della connessione 10 JDBC: 4 Tipi Tipo 1: Tipo 2: Scritti in parte in Java ed in parte in un altro linguaggio. Di fatto usano librerie native delDBMS (scritte ad esempio in C) Oracle's OCI (Oracle Call Interface). Tipo 3 Implementano un mapping verso un’altra API JDBC-ODBC Usano un middleware server con un protocollo indipendente dal database. Tipo 4: Sono scritti completamente in JAVA Il driver JDBC PostgreSQL Esempio JDBC:ODBC import java.sql.*; public class JDBCApp { public static void main (String args[]) { try { String driver = "sun.jdbc.odbc.JdbcOdbcDriver"; Class.forName(driver); Creiamo la stringa di connessione String url = "jdbc:odbc:myDataSource"; Connection con = DriverManager.getConnection (url, "myUserName", "myPassword"); Statement cmd = con.createStatement (); String qry = "SELECT * FROM myTable"; ResultSet res = cmd.executeQuery(qry); while (res.next()) { System.out.println(res.getString("columnName1")); System.out.println(res.getString("columnName2")); } res.close(); cmd.close(); con.close(); } catch (SQLException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); }}} 11 il package java.sql Interfaccia Driver rappresenta il punto di partenza per ottenere una connessione ad un DBMS. I produttori di driver JDBC devono implementare un’interfaccia Driver (mediante un’opportuna classe) affinchè un’applicazione possa interfacciarsi con un tipo particolare di DBMS. Classe DriverManager è la classe che contiene tutti i driver di database registrati Interfaccia Connection un oggetto di tipo Connection rappresenta una connessione attiva ad un database 12 il package java.sql. Interfaccia Statement: un oggetto di tipo Statement viene utilizzato per inviare query semplici SQL, che non fanno uso di parametri Interfaccia PreparedStatement un oggetto di tipo PreparedStatement viene utilizzato per inviare query che presentano parametri di input. Interfaccia CallableStatement usato per costruire query parametriche con parametri di input e output. Consente di eseguire anche stored procedure memorizzate sul server. Interfaccia ResultSet un oggetto ResultSet rappresenta il risultato di una query di selezione (insieme di record o tuple). Stringa di connessione In JDBC ogni database viene univocamente identificato da una particolare stringa di connessione detta JDBC URL. Tale stringa adotta un formalismo simile alla definizione degli URL e serve a definire dove si trova e come accedere al database. jdbc:<driver>:<database> 13 Stringa di connessione In connessioni di tipo JDBC-ODBC il terzo termine coincide con il nome della sorgente dati utente o di sistema jdbc:odbc:dbsegreteria Stringa di connessione Nel caso di connessioni dirette con driver java puri, il terzo termine invece definisce i parametri di accesso dell’istanza di database. jdbc:oracle:thin:@db.unina.it:1521:dbsegreteria jdbc:postgres://localhost/DISCHI 14 Nota bene L’operazione di connessione “costa” in termini di tempo Per questo solitamente si usa, all’inizio dell’applicazione, aprire tutte le connessioni necessarie per poi chiuderle alla fine dell’applicazione Le operazioni vere e proprie sui DB verranno fatte con altre operazioni OGGETTO STATEMENT Permette di fare rapidamente query Connection connection= DriverManager.getConnection(url,"root",""); Statement stm = connection.CreateStatement(); ResultSet rst = stm.executeQuery(query); Per interrogazioni int a = stm.executeUpdate(query); per statement di modifica (insert, update, delete) o DDL (create table o drop table). Viene restituito un numero intero (contatore di aggiornamento) rappresentante il numero di righe che sono state nserite/aggiornate/cancellate il valore 0 per statement di tipo DDL. 15 Statement Esiste anche il metodo execute() Viene invocato quando non si è certi della natura della query Restituisce true nel caso vi siano delle tuple di ritorno Tali tuple sono reperibili invocando il metodo getResult() PreparedStatement Permette di sottomettere comandi SQL che prevedono la presenza di parametri In questo modo la stesso comando SQL può essere richiamate diverse volte con diversi valori I parametri sono indicati con il simbolo ‘?’. L’interfaccia PreparedStatement estende l’interfaccia Statement ereditandone tutte le funzionalità con dei metodi aggiuntivi per gestione dei parametri. 16 PreparedStatement Anche questa interfaccia prevede i metodi executeQuery e executeUpdate per l’esecuzione del comando SQL Prima della loro invocazione devono essere valorizzati i parametri attraverso i metodi del tipo setXXX che ricevendo la coppia nome valore contestualizzano la query Il suffisso XXX indica un tipo di dato primitivoin Java (int, float, double, ..) PreparedStatement String query = ’’INSERT INTO STUDENTI VALUES (?,?,?)’’; Prepared Statament pstmt=conn.prepareStatement(query); pstmt.setString(1,’’010/000010’’); pstmt.setString(2,’’Diego Armando’’); pstmt.setString(3,’’Maradona’’); rs=pstmt.executeUpdate(); pstmt.setString(1,’’010/000011’’); pstmt.setString(2,’’Francesco’’); pstmt.setString(3,’’Totti’’); rs=pstmt.executeUpdate(); …. 17 CallableStatement Supporta la chiamata a stored procedure È possibile utilizzarle solo quando il driver ammette questo tipo di query Utilizza il metodo Connection.prepareCall() (simile al prepareStatement()) Ogni DBMS ha una propria sintassi per l’invocazione delle stored procedure In JDBC ho due sintassi standard – {call procedure_name[(?[,?…])]} {? = call procedure_name[(?[,?…])]} L’associazione placeholder valori avviene come nelle PreparedStatement ResultSet Rappresentano il mezzo per accedere alle tuple risultato di una query next() previous() first() last() beforeFirst() afterLast() absolute(int pos) relative (int pos) A secondo di come definisco il ResultSet posso usare alcuni dei modi di spostamento 18 CreateStatement public Statement createStatement() throws SQLException public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException resultSetType RestltSet.TYPE_FORWARD_ONLY ResultSet.TYPE_SCROLL_INSENSITIVE ResultSet.TYPE_SCROLL_SENSITIVE resultSetConcurrency ResultSet.CONCUR_READ_ONLY ResultSet.CONCUR_UPDATABLE ResultSet ResultSet.TYPE_FORWARD_ONLY (default): Permette solo la navigazione in un senso attraverso il metodo next(). identico al caso precedente ma sensibile alle modifiche alla base dati ResultSet.CONCUR_READ_ONLY (default): Si possono navigare in qualunque modo anche con accesso diretto. Eventuali modifiche alla base dati fatte da altri utenti non saranno visibili ResultSet.TYPE_SCROLL_SENSITIVE Solo rieseguendo la query io possono ritornare all’inizio. ResultSet.TYPE_SCROLL_INSENSITIVE il lock fatto sulle tuple è di tipo read e viene utilizzato solo nel caso il cursore non sia modificabile ResultSet.CONCUR_UPDATABLE: il lock è di tipo write e quindi blocca accessi da parte di altri utenti 19 Esempio di modifica: Cancellazione Attraverso il metodo ResultSet.deleteRow() viene cancellata la tupla al momento puntata rs.absolute(15); rs.deleteRow(); Esempio di modifica: update public void modifyPrices(float percentage) throws SQLException { Statement stmt = null; try { stmt = con.createStatement(); stmt = con.createStatement( ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATA ResultSet uprs = stmt.executeQuery("SELECT * FROM COFFEES"); while (uprs.next()) { float f = uprs.getFloat("PRICE"); uprs.updateFloat("PRICE", f * percentage); uprs.updateRow(); } } catch (SQLException e ) { JDBCTutorialUtilities.printSQLException(e); } finally { if (stmt != null) { stmt.close(); } } } 20 Esempio di modifica: insert 21