Introduzione a Java DataBase Connectivity (JDBC)

1.INTRODUZIONE

1.1 Java e Database

Negli ultimi anni è nato, si è sviluppato e sista rapidamente affermando il linguaggio di programmazioneJava di Sun. Per guadagnare ulteriori consensi èimportante che il linguaggio fornisca al programmatore imezzi per l'accesso e la gestione delle basi di dati.

Fra le caratteristiche più importanti di Javaricordiamo la semplicità e la portabilità, lapossibilità cioè di portare e utilizzare iprogrammi scritti in Java su qualsiasi piattaformacosì come sono, senza la necessità dimodificarli o ricompilarli.

Visto il massiccio utilizzo che si fa delle basi di dati perla memorizzazione di informazioni sia nelle grandi aziendeche nelle realtà più piccole èabbastanza semplice capire come questi due elementi, Java e iDatabase, uniti insieme possano fornire al programmatore unostrumento estremamente versatile e al contempo semplice perla realizzazione di applicazioni volte alla gestione diingenti quantità di informazioni.

Per esempio nel caso di un'azienda che utilizzipiù sistemi differenti, magari in localitàdiverse, il risparmio di tempo nella realizzazione delsoftware è sensibile, data anche la predisposizionenaturale di Java per la rete.

I progettisti di Java devono aver tenuto in considerazionequesti fattori e a tal proposito sono state infattirealizzate le API (Application Programming Interface) JDBC.

L'importanza di JDBC è sottolineata dal fatto diessere inclusa direttamente nella distribuzione standard diJava, e non come libreria separata.

1.2 Caratteristiche delle API JDBC

JDBC è una libreria di Java, quindi un insieme diclassi, che fornisce i metodi per l'accesso e lamanipolazione di basi di dati di tipo relazionale.

Mantenendo la filosofia di Java, le API JDBC sono stateprogettate per ottenere l'accesso ai datiindipendentemente dalla piattaforma e dal particolare tipo didatabase utilizzato; JDBC offre infatti delle interfaccestandard e quindi uniche per qualunque DB; per ciascuno diquesti deve poi esistere un driver che si occupi di tradurrele chiamate JDBC effettuate dall'applicazione inopportune istruzioni per accedere e manipolare i dati di unospecifico database.

Quando l'applicazione richiede l'accesso ad un DB unparticolare componente detto Driver Manager controlla il tipodi DB per cui si è richiesta la connessione e caricail driver appropriato.

In questo modo è possibile cambiare sia piattaformache DB semplicemente cambiando driver, sempre che ne esistauno.

Un driver per essere considerato pienamente compatibile conJDBC deve implementare tutte le interfacce previste dallespecifiche di JDBC. Se questa condizione è soddisfattail driver si dice JDBC-Compliant.

 

1.3 Sull'esempio di ODBC

L'idea che sta alla base di JDBC è la stessa delgià collaudato e diffuso ODBC (Open DataBaseConnectivity) di Microsoft. Data la numerosadisponibilità di driver compatibili ODBC, iprogettisti della Sun hanno ritenuto utile realizzare uncosiddetto driver bridge JDBC - ODBC, un particolare driverche aggiunge uno stadio alla traduzione e converte lechiamate JDBC in chiamate ODBC che quindi le traduce a suavolta in metodi specifici del particolare DB.

Questo driver bridge è stato realizzato principalmenteallo scopo di garantire una discreta compatibilità diJava con numerosi DB già dalla sua nascita.

IMPORTANTE notare però che, nel caso ci si appoggi aquesto driver, viene in qualche modo a cadere lacaratteristica di portabilità, in quanto ODBC ècompatibile solo con alcune piattaforme, in particolareè più probabile che funzioni su sistemiMicrosoft.

1.4 JDBC e SQL

SQL è il linguaggio standard per la creazione emanipolazione di database relazionali. SQL non èperò un linguaggio completo e, oltre al fatto di nonessere in grado di eseguire alcune interrogazioni a causadella mancanza di alcuni costrutti tipici dellaprogrammazione imperativa, non è assolutamente adattoalla realizzazione di vere e proprie applicazioni.

Per questo motivo è necessario utilizzarlo insieme adun altro linguaggio di programmazione che si occupi di tuttiquegli aspetti che non hanno a che vedere con lamanipolazione della base di dati.

Molti linguaggi incorporano SQL e si parla quindi diembedded-SQL. In questi casi il programmatore, nel punto incui deve accedere al database, "smette" di scriverenel linguaggio ospite e inserisce le istruzioni da eseguirein SQL direttamente nel codice.

Per ottenere il programma eseguibile il codice deve essereprima esaminato da un particolare traduttore che traducel'SQL nel linguaggio ospite; solo a questo punto ilcodice ottenuto può passare al compilatore e quindi allinker.

L'approccio di Java è invece diverso. JDBC mette adisposizione dei metodi ai quali è possibile passaredelle stringhe contenenti del codice SQL.

Il codice SQL non viene quindi tradotto prima dellacompilazione ma viene semplicemente passato al driver JDBCdurante l'esecuzione.

Questa soluzione garantisce una maggiore flessibilità,visto che le istruzioni SQL possono essere memorizzate instringhe variabili, e quindi più facilmentemodificabili a run-time rispetto alle soluzioni tradizionali.

 

2. UTILIZZARE JDBC

2.1 Il package java.sql

Le classi e le interfacce JDBC si trovano nel packagejava.sql, contenuto nella distribuzione standard del jdk(Java Development Kit).

Le classi e le interfacce più importanti del packagejava.sql sono:

DriverManager
Connection
Statement
PreparedStatement
CallableStatement
ResultSet
DatabaseMetaData
ResultSetMetaData
Types

La classe DriverManager gestisce i driver JDBC e seleziona ildriver apposito per il database in uso.

L'interfaccia Connection rappresenta una sessione dilavoro sul database e permette di ottenere informazioni sullabase di dati. Le operazioni sul database avvengonoall'interno di una sessione.

Le interfacce Statement, PreparedStatement eCallableStatement permettono di eseguire interrogazioni eaggiornamenti sulla base di dati.

L'interfaccia ResultSet fornisce l'accesso alletabelle.

DatabaseMetaData e ResultSetMetaData consentono di ottenereinformazioni sullo schema del database.

La classe Types definisce tutta una serie di costanti cheidentificano i tipi di dati SQL.

Con queste classi e interfacce è possibile avere uncontrollo pressochè totale della base di dati.

2.2 Creazione di un Database

Le interfacce JDBC permetterebbero di creare un database dazero e modificarne lo schema. In realtà questapossibilità in diversi casi è preclusa daidriver che non supportano operazioni di modifica allo schema.

In molti casi è perciò consigliabile utilizzaregli strumenti forniti dal produttore del database pereseguire queste operazioni.

2.3 Connessione ad un Database

La prima cosa da fare per iniziare a lavorare sul databaseè caricare il driver JDBC adatto. Per farlo ènecessario forzare il caricamento della classe cherappresenta il driver utilizzando il metodo forName dellaclasse Class.

Supponendo di voler utilizzare il driver bridge ODBC, ilcaricamento si farà tramite l'istruzione:

Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

A questo punto si stabilisce la connessione fral'applicazione Java ed il database chiamando il metodogetConnection della classe DriverManager nel modo seguente:

Connection con = DriverManager.getConnection("jdbc:odbc:database");

Il metodo getConnection richiede come parametro una stringache contiene il nome della sorgente di dati nella forma:

jdbc:<subprotocol>:<subname>

dove subprotocol indica il driver da utilizzare e subnameè il nome della fonte di dati.

2.4 Estrazione e modifica dei dati del Database

Per eseguire delle interrogazioni o modifiche sulla base didati si crea per prima cosa un oggetto di tipo Statementutilizzando il metodo createStatement dell'oggetto ditipo Connection ottenuto in precedenza.

Statement st = con.CreateStatement();

L'interfaccia Statement fornisce una serie di metodi comeexecuteQuery ed executeUpdate per eseguire rispettivamentedelle operazioni di lettura o modifica sulla base di datitramite istruzioni SQL. Il metodo executeQuery verràutilizzato per eseguire delle istruzioni SELECT mentre ilmetodo executeUpdate per l'esecuzione di istruzioniINSERT, UPDATE e DELETE.

Per esempio, per eseguire una query si faràcosì:

ResultSet rs = st.executeQuery("SELECT * FROM Tabella1");

Il parametro richiesto dal metodo executeQuery è unastringa contenente il codice SQL da eseguire. Questo metodonon fa altro che passare le istruzioni SQL al driver JDBC esuccessivamente ricevere da esso il risultato della querysalvandolo in un oggetto di tipo ResultSet. L'utilizzo diResultSet è spiegato in seguito (par 2.5.1).

Se invece si vogliono inserire dei dati in una tabella siutilizzerà un codice dimile al seguente:

st.executeUpdate("INSERT into Tabella1 (campo1, campo2) values(1,2)");

Il metodo executeUpdate non ritorna un oggetto ResultSet, leistruzioni INSERT, UPDATE e DELETE non danno infatti comerisultato una tabella.

Il valore ritornato è invece un intero che indica ilnumero di record modificati.

Un altro esempio potrebbe essere quindi:

int nRows = st.executeUpdate("DELETE * FROM Tabella2 WHERE campo1 > 0");System.out.println("Sono stati eliminati " + nRows + " record");

Queste due righe di codice eliminano i record della tabellaTabella2 in cui il valore del campo campo1 è maggioredi 0 e successivamente visualizzano il numero di recordcancellati.

Oltre all'interfaccia Statement esistono anche leinterfacce PreparedStatement e CallableStatement.

L'utilizzo di PreparedStatement è conveniente nelcaso di query eseguite ripetutamente, in quanto consente dispecificare parzialmente delle query e richiamarlesuccessivamente modificando solamente i nomi dei parametri.In questo modo l'esecuzione è più rapida,in quanto il PreparedStatement conosce già la formadell'interrogazione e deve solo cambiarne i parametri.

Inizialmente bisogna quindi creare l'interrogazioneindicando con il carattere '?' i parametri il cuivalore sarà inserito.

PreparedStatement ps = con.prepareStatement("INSERT into Tabella3 (parametro1,parametro2) values (?,?)");

Per inserire i valore mancanti si utilizzano i metodi setXXX(dove XXX è un tipo di dato) a cui vanno passati dueparametri: il primo è l'indice del parametro dellaquery che si vuole specificare, il secondo è il valoreche gli si vuole dare.

ps.setString(1,"Ciao");ps.setInt(2,15);ps.execute();

La prima riga di quest'esempio specifica che il primoparametro dell'interrogazione è una stringa il cuivalore è "Ciao" , nella seconda riga invecesi specifica che il secondo parametro dell'interrogazioneè un intero di valore 15.

L'ultima riga esegue l'interrogazione sul databasecon i parametri specificati.

L'interfaccia CallableStatement consente invece dieseguire delle procedure memorizzate all'interno deldatabase.

Per prima cosa è necessario creare un oggettoCallableStatement indicando, come per PreparedStatement, iparametri con un punto di domanda.

CallableStatement cs = con.prepareCall(" { ? = Procedura1 ? }");

Da notare che il primo '?' non indica un parametro diinput ma uno di output. Prima di eseguire la procedurabisogna specificare esplicitamente di che tipo è ilvalore di ritorno della procedura tramite il metodoRegisterOutParameter.

cs.RegisterOutParameter(1,Types.INTEGER);

Questa istruzione specifica che il primo parametro del metodoprepareCall chiamato in precedenza è un parametro dioutput ed è un intero.

Per eseguire la procedura:

cs.setInt(2,10);cs.execute();

La prima istruzione specifica che il parametro passato allaprocedura (quindi il secondo '?' nella prepareCall)è un intero di valore 10.

La seconda istruzione esegue la procedura con i parametrispecificati.

Per leggere il valore ritornato dalla procedura si utilizzail metodo getXXX (dove XXX indica il tipo di dato).

int val = cs.getInt(1);

Si era specificato in precedenza che il valore ritornatodalla procedura era di tipo intero, utilizziamo quindi ilmetodo getInt per leggerlo e lo salviamo in una variabile ditipo int.

2.5 Elaborare i dati

Oltre ad eseguire delle interrogazioni sulla base di dati,molto probabilmente si vorranno anche visualizzare irisultati ottenuti. A differenza di SQL però, unlinguaggio procedurale come Java non è pensato peroperare su tabelle.

A questo scopo sono quindi presenti le interfacce ResultSet,ResultSetMetaData e DatabaseMetaData.

2.5.1 ResultSet e cursori

Un oggetto di tipo ResultSet fornisce l'accesso ad unatabella.

Poichè una tabella può essere composta da due opiù colonne (campi) e righe (record), per accedere aidati si utilizza un cursore.

Un cursore è simile ad un puntatore che indica undeterminato record di una tabella; è possibile quindileggere i valori dei campi del record selezionato. In ognimomento è possibile spostare il cursore sul recordsuccessivo o precedente a quello corrente oppure èpossibile posizionarlo su un qualunque altro recordspecificandone l'indice.

Per esempio per visualizzare i dati di una tabella èpossibile procedere come segue:

while(rs.next()){    String var1 = rs.getString("campo1")    System.out.println(var1);}

Quando l'oggetto ResultSet viene creato il cursore puntaal record "precedente al primo" , quindi inrealtà non punta a niente. In questo modo peròè possibile scrivere un codice più elegante perscorrere tutta la tabella utilizzando un ciclo while.

Il metodo next sposta il cursore sul record successivo aquello corrente e ritorna vero se il nuovo record selezionatoè valido, falso se non ci sono altri record nellatabella.

Il metodo getString richiede come parametro il nome di uncampo della tabella e ritorna il valore di quel campo nelrecord corrente. Questo metodo legge il valore del campo comese fosse un campo di testo ma esiste un metodo per ogni tipodi dato (getInt, getFloat, getDate, getObject...).

2.5.2 I metadati

Negli esempi precedenti si è lavorato con ilpresupposto di conoscere a priori lo schema del DB. In alcunicasi comunque questo non avviene e, per esempio, si ha lanecessità di visualizzare il contenuto di una tabellasenza sapere quanti campia ha, quali sono i loro nomi e chetipo di dati contengono.

Per risolvere il problema si possono utilizzare le interfacceResultSetMetaData e DatabaseMetaData che consentono diottenere informazioni sullo schema del database.

Ad esempio, se volessimo visualizzare l'intestazione diuna tabella che non conosciamo potremmo utilizzare ilseguente codice:

ResultSetMetaData rsMeta = rs.getMetaData();int nColumn = rsMeta.getColumnCount();for(int i=1; i <= nColumn; i++){    System.out.println(rsMeta,getColumnName(i) + " ");}

Con il metodo getMetaData creiamo un oggetto di tipoResultSetMetaData che ci fornisce i metadati della nostratabella.

Il metodo getColumnCount ritorna il numero di campi dellatabella; il metodo getColumnName ritorna il nome del camponella posizione i-esima.

Questo frammento di codice, quindi, prima legge quanti campicontiene la tabella e poi li scorre uno a uno visualizzandoneil nome.

L'interfaccia ResultSetMetaData consente di otteneremolti altri dati sullo schema di una tabella mentrel'interfaccia DatabaseMetaData fornisce dati genericisulla base di dati come tipo di database, version,grammatiche SQL supportate, istruzioni supportate (ad esempiose è supportata o meno l'istruzione UNION)eccetera.