mercoledì 22 settembre 2010

JDBC: Java DataBase Connectivity, introduzione

Java DataBase Connectivity, da ora in avanti JDBC, è una libreria Java che implementa i driver per l'accesso e la gestione dei database nelle applicazioni Java.vEsistono diverse implementazioni dei driver JDBC, che possiamo classificare così come segue:

  • Driver di tipo 1: sono driver che implementano le API JDBC facendo corrispondere le chiamate ad altre API per l'accesso ai dati. Effettuano cioè un ponte (bridge) verso altre tipologie di driver, traducendo quindi ogni chiamata in chiamate dell'altro driver. Il driver JDBC-ODBC è un esempio di driver di tipo 1;
  • Driver di tipo 2: sono driver che, essendo scritti parzialmente in Java e parzialmente con il codice nativo del database, traducono ogni chiamata in una chiamata interna al database;
  • Driver di tipo 3: sono driver scritti interamente in Java e comunicano con un server middleware usando un protocollo indipendente dal database. Il server comunica, poi, le richieste del client all'origine dei dati;
  • Driver di tipo 4: sono driver scritti interamente in Java che si connettono direttamente all'origine dei dati, senza server intermedio;

I driver di tipo 1 e 2, poiché scritti con codice nativo, possono limitare l'applicazione Java in termini di portabilità. L'impiego di questi driver implica di conseguenza l'installazione delle librerie di supporto usate nel codice nativo del driver. Ad esempio, il driver di tipo 1 JDBC-ODBC dipende anche dal driver ODBC. Il driver di tipo 2 è da preferire a quello di tipo di 1 (ogni chiamata è tradotta in una chiamata interna al database). In ogni caso, se il produttore di database ne ha previsto uno, è preferibile usare un driver scritto interamente in Java.

I driver di tipo 3 e 4 hanno in genere prestazioni migliori, per i motivi scritti sopra. La portabilità dell'applicazione è quindi maggiore (non dovendo installare altro software). I driver di tipo 4 usano un protocollo nativo per scambiare i dati con il database, se possibile si consiglia di usare sempre driver di tipo 4. I driver di tipo 3 usano invece un protocollo di rete per dialogare con un server che interagisce con il database per conto del client. E' proprio quest'ulteriore passaggio a degradare le prestazioni del driver!

L'uso di JDBC prevede in primo luogo la creazione di una connessione verso il database, che può avvenire seguendo uno dei due meccanismi di seguito elencati:

  • DriverManager: carica un driver specifico utilizzando un indirizzo registrato che sintetizza la connessione al database;
  • DataSource: da preferire al precedente meccanismo poiché offre maggiori informazioni sui dati;

Per stabilire una connession sono necessari due passaggi: caricare un driver ed eseguire la connessione (usando il driver). Il caricamento di un driver si risolve in una riga di codice:

Class.forName("NOME_DEL_DRIVER");

Il nome del driver viene indicato nella documentazione del driver. La chiamata a Class.forName realizza automaticamente un'istanza  del driver registrandola con DriverManager. Solo dopo aver caricato un driver si può tentare di aprire una connessione verso il database desiderato. Vediamo come funzionano i due meccanismi.

La classe DriverManager fa parte dell'interfaccia Driver e realizza pertanto i metodi richiesti dalla stessa. Il metodo più usato è getConnection che ritorna un oggetto Connection. Esistono più implementazioni di questo metodo, che ovviamente  differiscono per la firma e quindi dai parametri richiesti dal metodo. La forma base è la seguente:

DriverManager.getConnection(String url);
dove url è l'url al database (nella forma jdbc:subprotocol:subname). Altre varianti di getConnection permettono poi di specificare proprietà, come:
DriverManager.getConnection(String url,Properties info);
dove info rappresenta un set di proprietà per la connessione. In molti casi va specificata la password e il nome utente assegnati allo stesso sul database. Il metodo:
DriverManager.getConnection(String url,String user,String password);
permette di fare proprio questo! In caso di errore tutti e tre i metodi appena visti lanciano un'eccezione di tipo SQLException. Altri metodi utili di DriverManager sono setLoginTimeout(int x) che attende x secondi prima di eseguire il login, setLogWriter(PrintWriter out) che imposta out come standard output per gli eventi di logging e println(String messaggio) che stampa su out un evento utile all'operazione di logging.
Con DataSource anziché fornire informazioni specifiche sul driver è sufficiente fornire un nome logico che punta alla sorgente di dati. Ecco un esempio:
InitialContext ic=new InitialContext();
DataSource ds=ic.lookup("java:comp/env/jdbc/myDB");
Connection con=ds.getConnection();
DataSource ds=(DataSource)org.apache.derby.jdbc.ClientDataSource();
ds.setPort(1527);
ds.setHost("localhost");
ds.setUser("APP");
ds.setPassword("APP");
Connection con=ds.getConnection();

L'interfaccia ResultSet fornisce i metodi per recuperare e manipolare i risultati generati dalle query eseguite. Gli oggetti ResultSet (una struttura dati in grado di ospitare i dati generati da una query) possono avere diverse funzionalità e caratteristiche. Tali caratteristiche possono stabilire il tipo di dati, la concorrenza e i cursori per lo scorrimento dei risultati. Il tipo di un oggetto ResultSet stabilisce i modi in cui il cursore può essere manipolato e le modifiche concorrenti che possono essere apportate ai dati. Esistono tre tipologie di ResultSet:

  • TYPE_FORWARD_ONLY: non è possibile scorrere a piacimento il risultato ottenuto (eseguendo una query), il cursore si sposta sempre e solo in avanti, dalla prima all'ultima riga;
  • TYPE_SCROLL_INSENSITIVE: è possibile scorrere il risultato in entrambe le direzioni oppure usare scostamenti relativi per l'accesso a una riga del risultato;
  • TYPE_SCROLL_SENSITIVE: è possibile scorrere il risultato in entrambe le direzioni oppure usare scostamenti relativi per l'accesso a una riga del risultato. La differenza con l'altro tipo di cursore risiede nella possibilità di rendere visibili i cambiamenti nei dati ad una applicazione;
JDBC restituisce i risultati in un oggetto ResultSet, quindi la nostra applicazione deve necessariamente dichiarare un'istanza di tale classe per raccogliere i risultati. Tale oggetto viene restituito ad esempio quando eseguiamo una chiamata a executeQuery o getResultSet. I metodi appena citati appartengono alla classe Statement, che permette di definire oggetti per l'esecuzione di codice SQL (il cui risultato è quindi ospitato in un oggetto ResultSet).
Statement stmt=con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);
ResultSet srs = stmt.executeQuery("SELECT * FROM contatti");
Il primo argomento è una delle tre costanti definite in ResultSet per indicare il tipo di cursore: TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE e TYPE_SCROLL_SENSITIVE. Il secondo argomento specifica (con due costanti definite sempre in ResultSet) se un set di risultati è di sola lettura oppure aggiornabile: CONCUR_READ_ONLY e CONCUR_UPDATABLE. Un altro metodo della classe Statement è int executeUpdate(String sql) che esegue codice SQL che non produce oggetti ResultSet (come operazioni di INSERT, UPDATE o DELETE). Ricorda che se specifichi il tipo di cursore devi sempre precisare, poi, se questo è di sola lettura oppure aggiornabile. L'istanza di un semplice oggetto Statement, con createStatement, invece, dichiarerà di default un  oggetto ResultSet di tipo TYPE_FORWARD_ONLY e accessibile nella modalità CONCUR_READ_ONLY.
Avete notato la variabile srs nel codice precedente? Si tratta di un oggetto ResultSet che racchiude al suo interno il risultato della query! Tale oggetto può essere manipolato attraverso svariati metodi. L'istanza di tale oggetto posiziona di default un puntatore per lo scorrimento della tabella (il risultato di un query è in ogni caso una tabella) sulla prima riga del risultato. Particolarmente utili sono i metodi:
  • next(): sposta il cursore sulla riga successiva a quella puntata, se esiste;
  • previous(): sposta il cursore sulla riga precedente a quella puntata, se esiste;
  • first(): sposta il cursore sulla prima riga dell'oggetto ResultSet;
  • last: sposta il cursore sull'ultima riga dell'oggetto ResultSet;
  • relative(int n): sposta il cursore di n righe (in avanti o dietro, dipende dal segno di n) rispetto alla riga puntata;
  • absolute(int n): sposta il cursore di n righe a partire dalla prima riga dell'oggetto ResultSet;
Tutti i metodi detti sopra ritornano un valore booleano, true in caso di successo, altrimenti false. Altri metodi che solitamente utilizzo sono: deleteRow() che cancella la riga puntata, int findColumn(String ColumnName) che ritorna l'indice per accedere alla riga che contiene la colonna ColumnName e Array getArray(String ColumnName) che ritorna un array composto da tutte le occorrenze della colonna ColumnName. Come posso leggere i campi dati di una riga?

Finché il puntatore dell'oggetto ResultSet insiste su una riga dell'oggetto è possibile accedere ai campi dati attraverso uno dei tanti metodi getXXX (fornendo il nome della colonna come parametro). Esistono metodi per estrarre valori numerici (come int getInt(String ColumnName), float getFloat(String ColumnName), double getDouble(String ColumnName) e long getLong(String ColumnName)) e stringhe (come String getString(String ColumnName)). Tutto dipende dal tipo di informazione da estrarre (esistono altri metodi per altri tipi di dati, vi consiglio di dare uno sguardo alla documentazione in linea). Quasi tutti i metodi per la ricerca dei campi dati hanno anche la versione basata sulla ricerca numerica della colonna (anziché usare il nome della colonna viene usato un numero che fa riferimneto all'ordine numerico della colonna nella tabella).

while(srs.next()){
 String username=srs.getString("username");
 System.out.println("Username: "+username);
}
Se usiamo l'ultima versione di NetBeans IDE possiamo sviluppare fin da subito applicazioni JDBC, in caso contrario dobbiamo necessariamente scaricare il driver e configurarlo all'interno del nostro classpath.

Nessun commento:

Posta un commento