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