FUNZIONE QUALITÀ E SICUREZZA Controllo delle copie Il presente documento, se non preceduto dalla pagina di controllo identificata con il numero della copia, il destinatario, la data e la firma autografa del Responsabile della Qualità, è da ritenersi copia informativa non controllata. Guida Metodologica Accesso ai Dati Oracle da Java SQC609007 ver. 2 Guida Metodologica Accesso ai Dati Oracle da Java SQC609007 VER. 2 Pag. 1/14 FUNZIONE QUALITÀ E SICUREZZA Sommario 1 Scopo e campo di applicazione 3 2 Riferimenti 2.1 Controllo del Documento: Stato delle revisioni 2.2 Documenti esterni 2.3 Documenti interni 2.4 Termini e Definizioni 3 3 3 3 3 3 Introduzione 4 4 La scelta del driver JDBC 4.1 Tipologie di Driver JDBC 4.2 I driver JDBC di Oracle 4.3 Linee guida per la scelta del driver 4 4 4 5 5 Modalità di Connessione 5.1 Come connettersi al DB con J2SE 5.2 Come connettersi al DB con J2EE 5 5 6 6 L’oggetto statement e le sue estensioni 6.1 L’interfaccia Statement 6.2 L’interfaccia PreparedStatement 6.3 L’interfaccia CallableStatement 7 7 7 9 7 I result set 10 8 Gestione delle eccezioni 11 9 Strategie di accesso ai dati 9.1 Utilizzo delle Stored Procedure 9.2 Gli oggetti Oracle 9.3 Gli Entity Bean 9.4 ORM: Toplink 12 12 13 14 14 Guida Metodologica Accesso ai Dati Oracle da Java SQC609007 VER. 2 Pag. 2/14 FUNZIONE QUALITÀ E SICUREZZA 1 Scopo e campo di applicazione Lo scopo di questo documento è fornire un set di linee guida da seguire durante la scrittura di una applicazione Java per l’accesso ai dati residenti su un database Oracle. La prima parte del documento sarà dedicata all’analisi delle classi coinvolte, mettendone in risalto le differenze e focalizzando l’attenzione su vantaggi e svantaggi derivanti dall’uso di ciascuna; la seconda parte sarà invece rivolta all’individuazione della migliore strategia di accesso ai dati. Essendo il tema trattato di natura prettamente tecnica, ovvero corredato nella sua esposizione da diagrammi, codici d’esempio, scenari comuni e casi reali, si ritiene necessario che il lettore possieda uno skill di base sull’analisi e la realizzazione di applicazioni object oriented e Java; sono richiesti inoltre conoscenze elementari sui database relazionali, con particolare riferimento ad Oracle, e principi di PL/SQL. 2 Riferimenti 2.1 Controllo del Documento: Stato delle revisioni Vers. 2 Descrizione delle modifiche apportate nella revisione alla versione precedente Revisione Logo Aziendale Cap. modificati N.A. 2.2 Documenti esterni • • • • • • • • • • NORMA UNI EN ISO 9001:2008 - Sistemi di gestione per la qualità – Requisiti; NORMA UNI EN ISO 9004:2000 - Sistemi di gestione per la qualità - Linee guida per il miglioramento delle prestazioni; NORMA UNI EN ISO 9000:2005 - Sistemi di gestione per la qualità – Fondamenti e terminologia Mapping Associations from OODBs to RDBMs (Wolfgang Keller; 1995; sd&m München) Foundations of Object Relational Mapping (Mark L. Fussell; 1997) Java Programming with Oracle JDBC (Donald Bales; 2002; O'Reilly) Oracle9i: Application Developer's Guide - Object-Relational Features (William Gietz; 2002; Oracle Corporation) Thinking in Java, 3rd Edition (Bruce Eckel; 2003) Java Database Interaction Mechanism Using Oracle Reference Cursors (Ravi Kumar Buragapu; 2005) Build Superior Java Applications with Oracle TopLink - An Oracle White Paper (Donald Smith; 2005; Oracle Corporation) 2.3 Documenti interni Manuale della Qualità ACI Informatica; Sistema di Gestione per la Qualità vigente. Guida metodologica Linee Guida Naming & Coding Conventions per Java Guida metodologica Linee Guida - Naming & Coding Convention per Oracle Guida metodologica Java Best Practices Guida metodologica Accesso ai Dati Oracle Mediante Oggetti 2.4 Termini e Definizioni Per i termini, sigle e acronimi contenuti in questo documento si fa riferimento al Glossario dei termini utilizzati nel Sistema Qualità. RDBMS: (ingl.) Relational Data Base Management System. Sistema di gestione di un database relazionale, in cui i dati sono organizzati in tabelle accessibili tramite SQL, un linguaggio di interrogazioni specializzato. ODBC: acronimo per Open DataBase Connectivity. Standard sviluppato verso la fine degli anni ottanta da Microsoft e IBM per lo scambio dei dati tra database diversi. JDBC: (acronimo di Java DataBase Connectivity) interfaccia scritta in linguaggio Java per l’accesso a database relazionali. JNDI: (Java Naming and Directory Interface) JNDI è una gerarchia di risorse organizzate in directory e disponibili per tutte le classi Java. L’interfaccia JNDI consente la separazione tra la definizione delle risorse in uso dalle applicazioni e il codice dell’applicazione stessa. Tra le risorse gestite da JNDI sono compresi DataSources, code JMS ed EJB. Guida Metodologica Accesso ai Dati Oracle da Java SQC609007 VER. 2 Pag. 3/14 FUNZIONE QUALITÀ E SICUREZZA 3 Introduzione Quando si realizza un’applicazione Java che si basa su RDMBS, si utilizza l'API JDBC (Java DataBase Connectivity) per dialogare con il database. La comprensione dei meccanismi di funzionamento del RDMBS che si sta utilizzando è una premessa importante per riuscire a scrivere applicazioni che sono in grado di soddisfare i requisiti prestazionali richiesti. In questo documento saranno fornite delle linee guida da seguire quando la base dati risiede su Oracle, aiutando lo sviluppatore nella scelta delle classi e della strategia migliori. L'obiettivo ultimo che ci si prefigge è quello di ottenere applicazioni robuste, veloci e scalabili. Quando si sceglie un database è importante capire per cosa esso è stato progettato e quali sono le tipologie di applicazioni in cui è veramente conveniente utilizzarlo. Oracle è conosciuto come un database per applicazioni entreprise in cui si gestiscono enormi quantità di dati e di utenti concorrenti; è in questi ambiti che Oracle mostra il meglio di se e soprattutto giustifica il suo prezzo. Lo scenario di riferimento per questo documento sarà quindi quello di un ambiente di produzione multiutente in cui si cercherà di individuare i meccanismi utili ad assicurare prestazioni adeguate al crescere degli utenti connessi al sistema e dei dati salvati nel database. 4 La scelta del driver JDBC Stabilire la connessione al database è uno dei primi passi da eseguire quando un’applicazione deve interagire con una base dati. Questo passo può apparire facile o addirittura banale, ma spesso la scelta del driver JDBC avviene senza cognizione di causa per mancanza di adeguate informazioni. In questo capitolo si discuterà dei differenti tipi di driver e dei vantaggi di ciascuno di essi. I driver JDBC si differenziano per il linguaggio con cui sono implemantati, Java puro o linguaggio macchina (codice nativo), e per il modo con cui la connessione è effettuata. 4.1 Tipologie di Driver JDBC La Sun ha definito quattro categorie di driver JDBC che si differenziano per l’architettura che adottano. • Type 1: JDBC bridge driver Questo tipo di driver implementano un bridge per connettere un client Java a driver di terze parti, quale, per esempio, potrebbe essere un driver ODBC. Il bridge JDBC-ODBC della Sun è l’esempio più illustre di questo tipo di driver. • Type 2: Native API Questo tipo di driver fornisce una interfaccia Java a delle API native. Il driver OCI (Oracle Call Interface) è un esempio di driver di tipo 2. Poiché un driver di tipo due è implementato usando codice nativo è ragionevole aspettarsi prestazioni superiori rispetto ad un driver Java puro. • Type 3: Network Protocol Questo tipo di driver comunica tramite il protocollo di rete con un middle-tier server; subito dopo il middle-tier server inoltra la comunicazione al DB server. Oracle non adotta driver JDBC di tipo tre, tuttavia fornisce un programma, il Connection Manger, che in combinazione con driver di tipo quattro può essere assimilato ad un JDBC driver di tipo tre. • Type 4: Native Protocol Questo tipo di driver, scritto interamente in Java, comunica direttamente con il database. Non necessita di codice nativo sulla macchina client. Il driver JDBC Thin di Oracle è un esempio di driver JDBC di tipo quattro. 4.2 I driver JDBC di Oracle Oracle mette a disposizione degli sviluppatori driver di tipo due e di tipo quattro. Tralasciando i driver usati server-side, ovvero quelli utilizzati all’interno del database, le possibili scelte sono: • JDBC OCI Driver È un driver di tipo due che usa l’interfaccia nativa OCI di Oracle. Ne esistono varie versioni, tante quante sono le versioni della Oracle Call Interface, e richiede l’installazione di Oracle client sulla macchina remota. • JDBC Thin Driver È un driver Java puro e non richiede nessuna installazione di software aggiuntivo sulla macchina client. • JDBC-ODBC bridge Non è propriamente un driver JDBC per Oracle, ma è un driver di tipo uno fornito dalla Sun. Permette la comunicazione tra le applicazioni Java e qualsiasi database (quindi anche Oracle) per cui è stato definito un ODBC. È qui riportato per completezza. Guida Metodologica Accesso ai Dati Oracle da Java SQC609007 VER. 2 Pag. 4/14 FUNZIONE QUALITÀ E SICUREZZA La figura successiva esemplifica la modalità di accesso ad un database Oracle usando le tre diverse opzioni a disposizione. JDBC-ODBC Driver Driver ODBC Oracle Call Interface Oracle RDBMS JDBC OCI Driver Oracle Listener JDBC Thin Driver Figura 1 4.3 Linee guida per la scelta del driver Ciò che immediatamente si nota in Figura 1 è il differente numero di strati software presenti in ciascuna soluzione: si passa da una comunicazione diretta tra Thin Driver e listener ai ben due presenti tra il bridge JDBC-ODBC e Oracle. Alla luce di questa e delle precedenti considerazioni possiamo fornire le seguenti linee guide per la scelta del driver JDBC: 1. Per le applicazioni client/server si consiglia fortemente l’uso del Thin Driver per ragioni di portabilità; questa soluzione non richiede l’installazione di Oracle Client e diminuisce i costi di configurazione e manutenzione del software. 2. Per le applet la scelta è obbligata: il Thin Driver. Non è possibile, infatti, fare assunzioni sul software installato sul client. 3. Per le applicazioni batch che devono lavorare massivamente sui dati si suggerisce l’uso dell’OCI Driver. Infatti, l’uso di codice nativo garantisce prestazioni superiori. La stessa Oracle Corporation raccomanda questa soluzione in scenari di questo tipo. 4. Nelle applicazioni Three-Tier si consiglia l’uso dell’OCI Driver, in quanto la controindicazione dovuta ai costi di configurazione viene meno (il client Oracle è necessario solo nel Tier che effettivamente si collega al DB), mentre resta valida le considerazioni del punto 3 sulle migliori prestazioni del codice nativo. 5 Modalità di Connessione 5.1 Come connettersi al DB con J2SE Una volta individuato il driver JDBC da utilizzare, i passi da seguire per connettersi ad un DB Oracle sono: • Aggiungere le istruzioni di import per puntare le classi necessarie. • Registrare il driver JDBC. • Formattare opportunamente l’URL di connessione al database. • Invocare il metodo getConnection() sulla classe DriverManager. Di seguito è fornito un esempio di connessione ad una base dati: import java.sql.Connection; import java.sql.DriverManager; import java.util.Properties; public class JdbcTest { public static void main(String[] args) Guida Metodologica Accesso ai Dati Oracle da Java SQC609007 VER. 2 Pag. 5/14 FUNZIONE QUALITÀ E SICUREZZA { Connection pConnection = null; Properties pProperties = null; try { pProperties = new Properties(); pProperties.put(“user”, “scott”); pProperties.put(“password”, “tiger”); pProperties.put(“defaultRowPrefetch”, “30”); pProperties.put(“defaultBatchValue”, “5”); Class.forName("oracle.jdbc.driver.OracleDriver"); pConnection = DriverManager.getConnection( "jdbc:oracle:thin:@127.0.0.1:1521:hr", pProperties); System.out.println("Connection done"); } ……… } } Esempio 1 Come si può notare il metodo getConnection usato accetta, oltre che l’URL del DB (o stringa di connessione), un oggetto di tipo Properties. Ciò permette di poter definire, oltre utente e password, anche altri parametri di sessione; tra i più importanti si segnalano defaultRowPrefetch, che indica il numero di righe prefetched, e defaultBatchValue, che indica il numero di query batch che innesca l’esecuzione delle stesse. Un valore ottimale assegnato a questi parametri potrebbe portare a migliorare le prestazioni del sistema. 5.2 Come connettersi al DB con J2EE In ambito J2EE l’accesso al database è mediato dall’Application Server. I parametri di connessione (URL, UserName, Password, …) sono configurati dal deployer. Lo sviluppatore può ottenere la connessione al database “richiedendola” all’Application Server usando un reference (o riferimento). Sarà compito del deployer mappare il riferimento sulla risorsa JNDI appropriata secondo le indicazioni del gruppo applicativo. Connection pConnection = null; Context pContext = new InitialContext(); DataSource pDataSource = (DataSource) pContext .lookup("java:comp/env/jdbc/AppDataSource"); pConnection = pDataSource.getConnection(); pConnection.setAutoCommit(false); ……… pConnection.commit(); ……… pConnection.close(); Esempio 2 Nell’esempio appena presentato il riferimento jdbc/AppDataSource rappresenta il datasource dell’applicazione (di cui lo sviluppatore conosce solo i metadati). Al momento del deploy dell’enterprise application tale reference sarà agganciato alla risorsa JNDI effettivamente utilizzata dell’applicazione in modo del tutto trasparante allo sviluppatore. Qualunque sia l’ambito di utilizzo di JDBC (J2SE o J2EE) è bene ricordare che Oracle offre il supporto alle transazioni. È sempre buona norma rendere esplicito il comportamento delle transazioni. L’istruzione pConnection.setAutoCommit(false) disabilita l’autocommit per la connessione pConnection. Il Guida Metodologica Accesso ai Dati Oracle da Java SQC609007 VER. 2 Pag. 6/14 FUNZIONE QUALITÀ E SICUREZZA comportamento di default di Oracle stabilisce che l’autocommit è abilitato. I metodi commit e rollback della classe Connection effettuano rispettivamente la commit e il rollback delle transazioni pendenti. È buona norma di programmazione non lasciare connessioni pendenti al DB. Tutte le connessioni aperte devono essere rilasciate utilizzando il metodo close della classe Connection. 6 L’oggetto statement e le sue estensioni 6.1 L’interfaccia Statement L’oggetto Statement è creato su una connessione ad un database attiva attraverso il metodo createStatement( ) e permette di eseguire istruzioni SQL DDL (Data Definition Language: CREATE, ALTER, DROP) e DML (Data Manipulation Language: SELECT, INSERT, UPDATE, DELETE); al contrario le istruzioni DCL (Data Control Language: COMMIT, SAVEPOINT, ROLLBACK e SETTRANSACTION) sono eseguite con invocazioni a metodi specifici sull’oggetto Connection. L’oggetto Statement può utilizzare il metodo execute( ) per eseguire ogni possibile istruzione SQL valida. Il metodo execute( ) può ritornare un valore a runtime per determinare se c’è un result set e quindi utilizzare getResultSet( ) per ottenere i dati; è possibile utilizzare il metodo getUpdateCount( ) per determinare a runtime il numero di righe interessate da una operazione di aggiornamento sul DB. Tuttavia, quando si ha la necessità di eseguire degli statement di INSERT, UPDATE o DELETE è preferibile usare il metodo executeUpdate( ) che ha un comportamento specializzato per le operazioni di aggiornamento sul database; per ragioni analoghe è sempre preferibile usare il metodo executeQuery( ) nel caso di query di selezione. Il codice seguente fornisce un esempio d’uso dell’oggetto Statement boolean bResultSet = false; Statement pStatement = null; try { pStatement = pConnection.createStatement( ); bResultSet = pStatement. execute("select 'Hello '||USER from dual"); ……… } Esempio 3 Chi ha esperienza di programmazione Java conosce già l’interfaccia Statement; in ogni caso i JavaDoc, descrivendone in dettaglio gli attributi e i metodi, costituiscono un irrinunciabile punto di riferimento per chiunque. Cosa meno nota è che JDBC definisce anche altre due interfacce, PreparedStatement e CallableStatement, che estendono Statement definendo una gerarchia a tre livelli secondo quanto mostrato dal seguente diagramma delle classi. Figura 2 6.2 L’interfaccia PreparedStatement Analogamente allo Statement, un PreparedStatement può essere usato per inserire, aggiornare, cancellare e selezionare dati. Tuttavia i PreparedStatement sono statement precompilati che possono essere riusati per eseguire in modo più efficiente istruzioni SQL identiche con differenti valori. I Guida Metodologica Accesso ai Dati Oracle da Java SQC609007 VER. 2 Pag. 7/14 FUNZIONE QUALITÀ E SICUREZZA PreparedStatement interrogano Oracle una sola volta per ottenere i metadati della query, mentre gli Statement effettuano una interrogazione per ogni esecuzione. Inoltre, poiché è utilizzato il bind delle variabili, il database compila e conserva in cache l’istruzione SQL riusandola nelle esecuzioni seguenti per migliorare le performance. I PreparedStatement sono anche utili in quanto alcuni tipi di valori, come per esempio I BLOB, le collezione, ecc… non sono rappresentabili come SQL text. Con i PreparedStatement si usano dei puntiinterrogativi (?) come segnaposto all’interno dello statement SQL; successivamente questi sono sostituiti con i valori appropriati usando i metodi accessori setXXX( ), disponibili per ogni tipo di dato. Quanto appena detto potrebbe far credere che l’uso di oggetti PreparedStatement assicuri sempre prestazioni maggiori. Ciò perché gli oggetti PreparedStatement interrogano Oracle una sola volta per ottenere i metadati della query rendendo le query successive alla prima fino al 50% più veloci. Bisogna considerare, però, che il binding delle variabili, la compilazione e il caching della query introducono un certo overhead. I test illustrati da Donald Bales [2.1.3] dimostrano che l’overhead dovuto all’uso di un PreparedStatement viene compensato solo dopo 65 esecuzioni della query. Ne consegue che per un piccolo numero di esecuzioni potrebbe essere vantaggioso usare un oggetto Statement piuttosto che un oggetto PreparedStatement. Il codice seguente costituisce un esempio d’uso dell’oggetto PreparedStatement try { PreparedStatement pPreparedStatement; pPreparedStatement = pConnection. prepareStatement(“insert into “ + ”MYTABLE ( MYFIELD1, MYFIELD1) values ( ?, ? )”); pPreparedStatement setString(1, "AAA"); pPreparedStatement.setInt(2, 123) pPreparedStatement.execute(); ……… pPreparedStatement = pConnection. prepareStatement(“update MYTABLE set MYFIELD1 = ? “ + ”where MYFIELD2 = ?”); pPreparedStatement setString(1, "AAA"); pPreparedStatement.setInt(2, 123) pPreparedStatement.execute(); ……… pPreparedStatement = pConnection. prepareStatement(“delete MYTABLE where MYFIELD1 = ? ”); pPreparedStatement setString(1, "AAA"); pPreparedStatement.execute(); ……… pPreparedStatement = pConnection. prepareStatement(“select ?, MYFIELD2 from MYTABLE “ + ”where MYFIELD2 = ?”); pPreparedStatement setString(1, "AAA"); pPreparedStatement.setInt(2, 123) pPreparedStatement.execute(); ……… } Esempio 4 Si fa notare che il punto interrogativo nella select list dell’ultima query rappresenta un valore costante e non il nome di una colonna. Guida Metodologica Accesso ai Dati Oracle da Java SQC609007 VER. 2 Pag. 8/14 FUNZIONE QUALITÀ E SICUREZZA 6.3 L’interfaccia CallableStatement Un oggetto CallableStatement è usato per invocare stored procedure. In Oracle, con il termine generale stored procedure ci si riferisce all’insieme delle standalone stored function, standalone procedure, packaged function e procedure. Le stored procedure Oracle possono essere scritte in Java o in PL/SQL. Come noto, le stored procedure sono state inventate per eseguire operazioni data-intensive che non possono essere compiute usando solamente SQL. Le stored procedure vengono eseguite all’interno del database; le loro performance non risentono quindi dei tempi di latenza su rete. Per invocare una stored procedure è necessario conoscerne il nome e la natura dei parametri passati alla procedura. Nel caso di una funzione è necessario conoscere anche il tipo del valore di ritorno. È possibile ottenere queste informazioni interrogando il data dictionary di Oracle come mostrato nell’esempio seguente. SQL> select * from user_procedures; OBJECT_NAME PROCEDURE_NAME AGG … ------------------------------ --------------------------- --- … ALIMENTASTORICO ALIMENTASTORICO NO … ALLINEA_SEQUENCE_TIPOLOGICA NO … ASSEGNATUTTIIMETODI NO … CREATE_TYPES ALTERTYPES NO … CREATE_TYPES LAUNCHTYPESCREATOR NO … DATETOCHAR NO … GETCODICETIPOLOGICA NO … GETDATAFINEVALIDITA NO … GETDATAFORINSUPD NO … SQL> desc ALIMENTASTORICO; PROCEDURE ALIMENTASTORICO Nome argomento Tipo In/Out Predef.? ------------------------ ----------------------- ------ -------P_TABLENAME VARCHAR2 IN P_STRISTANZADB VARCHAR2 IN DEFAULT Esempio 5 Chiamare una stored procedure da Java è un processo in cinque fasi. Le operazioni da svolgere sono, nell’ordine: • Formulare un callable statement: cioè preparare una stringa opportunamente formattata per invocare una stored procedure. È possibile usare indifferentemente la sintassi SQL92 o Oracle /* Sintassi SQL92 */ {? = call [schema.][package.] function_name [(?,?,…)]} {call [schema.] [package.]procedure_name[(?,?,…)])} /* Sintassi Oracle */ begin ?:=[schema.][package.]function_name[(?,?,…)]; end; begin [schema.][package.]procedure_name[(?,?,…)]; end; Esempio 6 • Creare un un CallbleStatement un CallableStatement è creato invocando il metodo prepareCall( ) su un oggetto Connetion passando la stringa preparata nel passo precedente. CallableStatement pCallableStatement = null; try { pCallableStatement = pConnection. prepareCall("{ ? = call function_name (?,?,… ) }"); … } Esempio 7 • Registrare ogni parametro di output Guida Metodologica Accesso ai Dati Oracle da Java SQC609007 VER. 2 Pag. 9/14 FUNZIONE QUALITÀ E SICUREZZA Per poter ottenere i dati da una chiamata a stored procedure occorre registrare i parametri di OUT o IN OUT prima di eseguire il CallableStatement. Per far ciò si usa il metodo registerOutParameter( ) dell’oggetto CallableStatement. … pCallableStatement.registerOutParameter(1, Types.DOUBLE, 2); … Esempio 8 • Settare i parametri di input: Analogamente ai prepared statement, questa operazione è eseguita usando i metodi accessori setXXX( ) passando la posizione del carattere segnaposto del callable statement e un opportuno valore. … pCallableStatement.setString(1, sMyString); … Esempio 9 • Eseguire il callable statement: Dopo aver svolto le suddette operazioni preliminari un callable statement può essere eseguito utilizzando il metoto execute ( ) dell’oggetto CallableStatement. … pCallableStatement.execute(); … Esempio 10 Si sottolinea ancora una volta che un CallableStatement è un PreparedStatement (Vedi Figura 2); perciò, tutto quanto detto in precedenza sulle performance dei PreparedStatement rispetto ad un oggetto Statement è integralmente applicabile anche ai CallableStatement. 7 I result set Come abbiamo avuto modo di vedere nel capitolo precedente l’utilizzo del metodo executeQuery( ) della classe Statement ritorna un oggetto di tipo ResultSet. Un ResultSet contiene i risultati di una query su un database. All’interno di un database i dati risiedono in tabelle contenenti righe e colonne; l’organizzazione di ResultSet riflette quella di una tabella. Un ResultSet fornisce un insieme di metodi accessori per navigare attraverso i dati estratti, mentre altri permettono di prelevare i valori (di tipi SQL standard o proprietari Oracle) o i metadati della query. Tali metodi sono ben noti agli sviluppatori Java e non saranno quindi oggetto di questa trattazione. Ad ogni modo i JavaDoc ne forniscono un elenco completo e dettagliato. Quando un oggetto ResultSet è ritornato da uno Statement, il suo cursore è inizialmente posizionato prima della prima riga del set di risultati. Per tale ragione, la prima invocazione del metodo next( ) posiziona il cursore sulla prima riga estratta. Il metodo next( ) restituisce un valore booleano (false se non esiste nessuna riga successiva a quella corrente) e può essere quindi utilizzato per verificare la presenza di dati rispondenti ai parametri di selezione. Il metodo next( ) è tipicamente utilizzato all’interno di un loop, come mostra l’esempio seguente: ResultSet pResultSet = null; Statement pStatement = null; try { pStatement = pConnection.createStatement( ); pResultSet = pStatement. executeQuery("select owner, table_name “ + “from all_tables"); while (pResultSet.next( )) { … } } Esempio 11 Con le specifiche JDBC 1.0 i ResultSet potevano essere in sola lettura e navigabili in avanti (cioè non esisteva il metodo previous( ) e i suoi derivati). Con le specifiche JDBC 2.0 i ResultSet possono essere navigati in qualsiasi direzione e possono anche essere aggiornabili. Per implementare queste funzionalità il metodo createStatement( ) della classe Connection è stato sovraccaricato definendo il metodo Statement createStatement(int resultSetType, int resultSetConcurrency) Guida Metodologica Accesso ai Dati Oracle da Java SQC609007 VER. 2 Pag. 10/14 FUNZIONE QUALITÀ E SICUREZZA Per il parametro resultSetType, esistono tre possibili valori costanti (attributi static final della classe ResultSet): • TYPE_FORWARD_ONLY Crea un ResultSet forward-only. • TYPE_SCROLL_INSENSITIVE Crea un ResultSet navigabile in entrambe le direzioni insensibile alle modifiche apportate al database. Se una qualche modifica viene apportata su Oracle mentre il ResultSet è ancora aperto, l’applicazione Java non “vedrà” tali modifiche a meno che, naturalmente, non apra un nuovo ResultSet. • TYPE_SCROLL_SENSITIVE Contrariamente al precedente, crea un ResultSet in grado di riflettere immediatamente sull’applicazione Java i cambiamenti avvenuti sul DB. Anche in questo caso il ResultSet è navigabile in entrambe le direzioni. Per il parametro resultSetConcurrency, esistono due possibili costanti (attributi static final della classe ResultSet) • • CONCUR_READ_ONLY Crea un ResultSet read-only. CONCUR_UPDATABLE Crea un ResultSet aggiornabile. Oracle implementa la navigabilità dei ResultSet usando una memory cache client-side. Per tale ragione i paramentri TYPE_SCROLL_* dovrebbero essere usati con cautela in quanto potrebbero avere degli impatti negativi sulle performance delle applicazioni a causa dell’elevato uso di memoria della JVM. Per implementare i ResultSet aggiornabili, Oracle usa il ROWID, un valore che identifica univocamente ogni rigo nel database. Sebbene non sia esplicitamente stata richiesta dall’applicazione, questo valore viene estratto per ogni record di un set aggiornabili portando ad un maggiore volume di dati in transito sulla rete e residenti sulla JVM. Anche in questo caso, quindi, i record aggiornabili devono essere usati con cautela. Il metodo createStatement( ) crea ResultSet forward-only e read-only. 8 Gestione delle eccezioni Le eccezioni costituiscono il meccanismo adottato da Java per gestire le situazioni inattese (eccezionali). La gestione delle eccezioni fa parte del bagaglio culturale di base degli sviluppatori Java ed illustrarlo non rientra negli obiettivi di questo documento. Al contrario, questo capitolo si prefigge lo scopo di esaminare più in dettaglio la classe java.sql.SQLException. Una SQLException può esere sollevata a seguito del verificarsi di eventi inattesi sia nel driver che nel database. Quando tale eccezione viene lanciata un oggetto della classe SQLException sarà passato alla clausola catch per gestire l’anomalia. Oltre ai metodi ereditati dalla superclasse, una SQLException espone i seguenti per ottenere informazioni aggiuntive sull’eccezione sollevata: • getErrorCode( ) Restituisce il codice d’errore Oracle associate all’eccezione. • getMessage( ) Restituisce il messaggio d’errore del driver JDBC, se l’eccezione è stata sollevata dal Driver, o il codice d’errore e il messaggio, se l’eccezione è stata sollevata dal database. • getSQLState( ) Restituisce la stringa XOPEN SQLstate. Occorre tenere presente durante la programmazione che questo metodo può anche ritornare null. • getNextException( ) Ritorna il successivo oggetto Exception della catena delle eccezioni. Utilizzando le informazioni restituite dall’oggetto Exception e catturando l’eccezione è possibile far seguire al programma un flusso opportuno. Naturalmente la scelta della strategia per il recupero delle situazioni anomale non può prescindere dal tipo di errore. Per questa ragione è sempre necessario avere a disposizione la lista completa dei codici d’errore di Oracle con la loro spiegazione. Guida Metodologica Accesso ai Dati Oracle da Java SQC609007 VER. 2 Pag. 11/14 FUNZIONE QUALITÀ E SICUREZZA 9 Strategie di accesso ai dati 9.1 Utilizzo delle Stored Procedure Navigando su internet e leggendo gli articoli sulla stampa specializzata, sembra affermarsi il trend dell’utilizzo delle stored procedure per l’accesso ai dati. La stessa Oracle corporation suggerisce questa strategia. In effetti i vantaggi derivanti da questa tecnica sono numerosi ed importanti. Tra i primi segnaliamo che l’uso delle stored procedure permette di astrarre le classi dalla struttura fisica del database. Supponiamo per esempio di voler ottenere la fabbrica, il tipo e la serie di un autoveicolo estraendola da un database composto da una sola tabella con la seguente struttura logica. Figura 3 La nostra stored procedure, chiamata per esempio GET_FTS_DATA, conterrà al suo interno una istruzione SQL del tipo SELECT Fabbrica, Tipo, Serie FROM Veicolo WHERE … Esempio 12 e verrà invocata in ogni punto del codice Java in cui è necessario ottenere queste informazioni. Se nel corso dell’evoluzione dell’applicazione lo schema del database dovesse cambiare (per esempio per esigenze di normalizzazione) diventando il seguente: Figura 4 Il solo cambiamento necessario sarebbe localizzato nella stored procedure dove la select diventerebbe del tipo: SELECT Fabbrica, Tipo, Serie FROM Fabbrica INNER JOIN Veicolo ON Fabbrica.Id_Fabbrica = Veicolo.Id_Fabbrica WHERE … Esempio 13 Guida Metodologica Accesso ai Dati Oracle da Java SQC609007 VER. 2 Pag. 12/14 FUNZIONE QUALITÀ E SICUREZZA Il codice Java rimarrebbe immutato continuando a chiamare la procedura GET_FTS_DATA, ignaro dei cambiamenti nel database. Altre tecniche di astrazione, quali ad esempio file di configurazione contenenti le istruzioni SQL o l’utilizzo di viste, non sono altrettanto sicure o nascondono solo parzialmente la struttura fisica del DB. Le stored procedure evitano problemi derivanti dalla duplicazione del codice SQL. Bisogna infatti considerare che il database può essere condiviso da più applicazioni, non tutte necessariamente in piattaforma J2EE. Evitare la proliferazione e la ripetizione del codice SQL porta ad avere una architettura complessiva delle applicazioni aziendali più stabile e manutenibile e una logica di accesso al database uniforme. Ciò, a sua volta, rende il tuning del database e delle query più agevole e veloce; i benefici di questa attività saranno subito propagati a tutte le applicazioni. Come abbiamo avuto modo di sottolineare in precedenza una query effettuata utilizzando le stored procedure è di solito più veloce: l’hoverhead dovuto alla compilazione delle procedure, sebbene presente, è limitato alla sola prima esecuzione. In uno scenario di applicazioni enterprise e multiutente o batch con esecuzioni ripetute della query i vantaggi sono evidenti. Vantaggi sono presenti anche dal punto di vista della sicurezza: l’utilizzo esclusivo di stored procedure consente di assegnare agli utenti applicativi, i più esposti dal punto di vista della sicurezza, le sole grant di execute. In questo modo, se anche l’utenza e la password dovessero venire intercettate, un utente malizioso avrebbe comunque un accesso limitato alle risorse e ai dati del database. Le stored procedure agevolano anche il monitoraggio da parte del DBA, permettendo di sfruttare a pieno le potenzialità di strumenti quali statspack. In quanto componenti software destinati ad un ambiente di esecuzione diverso da Java, le stored procedure appesantiscono la gestione della configurazione. Anche il ciclo di vita del software richiede una gestione più dispendiosa, dato che i passaggi da un ambiente ad un altro richiedono necessariamente l’intervento del gruppo DBA oltre che dei deployer. Inoltre è sempre concreto il rischio di una loro eccessiva proliferazione: in casi limite si giunti ad avere procedure distinte per query che si differenziano solo per il tipo di ordinamento del set di dati ritornato; l’utilizzo delle stored procedure richiede quindi una analisi della logica di accesso più attenta ed oculata. 9.2 Gli oggetti Oracle L’uso degli oggetti Oracle rappresenta una possibile interessante funzionalità aggiuntiva alle stored procedure. La documentazione Oracle riporta la capacità di immagazzinare tipi di dati definiti dall’utente fin dalla versione 8i. Utilizzando gli oggetti Oracle è possibile fare in modo che le strutture dati di Java relative a business level e data level vadano a coincidere eliminando completamente lo strato di traduzione. Per raggiungere questo obiettivo occorre: 1. Definire un tipo di oggetto oracle in cui gli attributi corrispondano a delle colonne in una tradizionale tabella relazionale. 2. Creare una vista di oggetti per estrarre i dati. 3. Creare un trigger INSTEAD OF per rendere la vista aggiornabile. 4. In alternativa al punto precedente è possibile di definire all’interno degli oggetti Oracle dei metodi che si occupano della serializzazione dell’oggetto. Il mapping tra le proprietà dell’oggetto Java e dell’oggetto Oracle verrà reso attraverso l’implementazione da parte dell’oggetto Java di due interfacce: OraData e OraDataFactory. Tali interfacce definiranno i processi di serializzazione e deserializzazione del dato. La scelta di implementare degli oggetti dotati di metodi per salvare le proprie variabili membro sembra essere la più comoda in quanto assegna ad ogni oggetto la responsabilità di serializzare se stesso sul database e concentra in un’unica struttura Oracle (l’oggetto appunto) i dati dell’oggetto (proprietà) e la logica di persistenza (metodi). In questo caso, tuttavia, è necessario passare l’oggetto ad una stored procedure; questa modalità di accesso ai dati diventa quindi una estensione della precedente. Sebbene le funzionalità necessarie all’uso di questa tecnica siano state rilasciate da tempo solo con la versione 9.2.0.6 del database, Oracle riesce a supportare pienamente questa tecnica. Occorre inoltre sottolineare che la letteratura non riporta ancora casi reali di utilizzo in applicazioni enterprise di grandi dimensioni, ne dati affidabili sulle performance. Ciononostante questa tecnica è stata sperimentata ed utilizzata in azienda in ambito PRA e nelle applicazioni del Centro Servizi (CS). Guida Metodologica Accesso ai Dati Oracle da Java SQC609007 VER. 2 Pag. 13/14 FUNZIONE QUALITÀ E SICUREZZA 9.3 Gli Entity Bean Gli entity bean sono la soluzione J2EE al problema della persistenza. Le specifiche Sun consigliano l’uso degli Entity Bean, Container Maneged Persistence (CMP) o Bean Managed Persistence (BMP), per leggere e scrivere i dati in un database. I vantaggi architetturali derivanti dal loro utilizzo sono evidenti: un entity bean è un componente che incapsula e implementa una entità del business layer. L’entità incapsulata è tipicamente indipendente dall’applicazione client che usa l’entity bean. Come conseguenza uno stesso entity bean può essere usato da molte applicazioni client. Esistono inoltre altri vantaggi pratici derivanti dall’uso degli entity; gli entity bean permettono infatti una gestione fine-grained delle transazioni, senza la necessità di preoccuparsi di fornire i demarcatori di inizio e fine transazione. Inoltre gli entity bean (attraverso il container) offrono anche funzionalità di connection pooling e caching dei bean; l’accesso ai servizi di mail e di messaging è facilitato. Sfruttando l’interfaccia javax.ejb.Timer è possibile creare entity bean temporizzati, oggetti particolarmente utili, anche se di fatto introducono uno strato di business logic all'interno del dominio dei dati, per cui violano in qualche modo alcune delle best practice J2EE, secondo le quali un entity è una rappresentazione OO di dati resi persistenti da qualche parte. Utilizzando gli EJB di entità non è necessario scrivere statement JDBC riducendo i tempi di sviluppo. Se si sceglie di usare i CMP la gestione delle transazioni avviene senza la necessità di scrivere codice e la sicurezza è gestita dall’application server. Esistono tuttavia anche delle controindicazioni: i CMP non gestiscono il join tra tabelle. Inoltre, diversamente dagli oggetti DAO, sia i CMP che i BMP sono disponibili solo su piattaforme J2EE e la portabilità tra application server diversi non è sempre garantita. Dato che l’accesso ai dati è comunque mediato dall’application server si può riscontrare una perdita di performance. Gli oggetti DAO supportano tabelle (con o senza chiave primaria), viste e stored procedure, mentre gli entity bean supportano solo tabelle con chiavi primarie. 9.4 ORM: Toplink Con l’Object Relational Mapping (ORM) la persistenza viene resa utilizzando particolari strumenti, detti motori di persistenza (il più noto nella comunità open source è Hibernate), i quali non sono altro che framework che, in modo meno invasivo possibile, consentono il salvataggio ed il recupero dello stato di un oggetto sulla base dati. La persitenza è ottenuta attraverso la reflection, che consente nella piattaforma Java di ispezionare il contenuto degli oggetti per conoscerne i metodi e gli attributi, e quindi per accedere agli uni o agli altri. Questo tipo di persistenza trasparente somiglia da vicino a quella promessa dei bean di entità CMP, dove è il component container a svolgere una funzione che corrisponde alla descrizione data sopra. La tecnologia EJB, però, richiede dei descrittori più complessi, ed impone l'utilizzo di interfacce e superclassi presenti nel framework EJB, come javax.ejb.EntityBean e javax.ejb.SessionBean. I motori di persistenza lavorano invece con semplici POJO, acronimo che sta per Plain Old Java Object. Un POJO indica un semplice, tradizionale oggetto Java, privo degli orpelli che solitamente vengono imposti dai framework per questa o quella funzione, come le sopracitate interfacce EJB. Anche la Oracle Corporation ha acquistato ed evoluto un motore per l’OR Mapping chiamato Toplink. I maggiori benefici derivanti dall’uso di Toplink sono: • Portabilità: Toplink supporta tutti i maggiori application server e database, fornendo certificazioni e test report per tutte le release e le patch. Tuttavia Toplink mostra il meglio di se quando usato insieme agli altri software della casa produttrice: Oracle Application Server e Oracle DB. • Aumento della produttività: Toplink permette la configurazione dei metadati attraverso dei file XML amministrati graficamente tramite degli editor che ne agevolano la gestione. Le maggiori criticità riguardano: • Performance: le performance di Toplink variano sensibilmente al variare dei parametri di configurazione ed in particolar modo al variare delle dimensioni della shared object cache. Per ottenere prestazioni ottimali è necessaria una attenta fase di tuning. • Gestione: per schema con un numero elevato di tabelle, la gestione dei numerosi file di configurazione può risultare critica. La comunità degli utenti pone generalmente a 50 il limite massimo di tabelle gestibili con un qualsiasi strumento per ORM. • Flessibilità: accessi particolarmente articolati potrebbero essere difficili o impossibili da realizzare con uno strumento di questo genere richiedendo comunque il ricorso a stored procedure invocate usando JDBC. Guida Metodologica Accesso ai Dati Oracle da Java SQC609007 VER. 2 Pag. 14/14