J !": JAvA E DATABASE. - the

annuncio pubblicitario
JDBC: Java e database.
Introduzione all’architettura e esempi di utilizzo
1.0 INTRODUZIONE ALL’ARCHITETTURA
JDBC è (anche se non ufficialmente riconosciuto da Sun) l’acronimo per Java DataBase
Connectivity. In questa dicitura è racchiusa l’essenza dell’architettura.
JDBC in pratica è una API Java che permette, a partire da un qualsiasi programma scritto in java di
accedere ad una sorgente di dati esterna: nella maggior parte dei casi un DBMS relazionale. Quello
che ha voluto fare Sun con la creazione di JDBC è stato fornire delle api standard che permettessero
l’accesso in maniera uniforme a tipi di dati differenti tra loro.
In pratica JDBC costituisce una sorta di layer che si interpone tra applicazione e database vero e
proprio.
Architettura JDBC
Le applicazioni infatti che utilizzano JDBC nella maggior parte dei casi sono utilizzabili con
differenti tipi di database senza la necessità di particolari modifiche al codice. E’ naturale però che
in fase di progettazione deve essere stata prestata la massima attenzione al fatto che un database
supporti o meno certe caratteristiche, predisponendo di conseguenza adeguati meccanismi di
gestione degli errori e delle eccezioni.
Elevata portabilità quindi e riutilizzo del codice che guarda caso sono proprio due delle
caratteristiche vincenti di Java che come sappiamo basa il proprio successo sul paradigma WORE
(Write Once Run Everywhere).
JDBC svolge il suo lavoro mediante alcune interfacce, le quali vengono implementate da un set di
classi specifico per ogni differente tipo di database andando così a costituire il cosiddetto “driver
JDBC”.
Il programmatore che vuole scrivere applicazioni che utilizzano i database non si deve in alcun
modo preoccupare di come queste classi vengano implementate: a lui interessa usare il driver per
interfacciarsi con i dati e poterli così manipolare a proprio piacimento. Ecco quindi che come
accennavamo sopra JDBC maschera la struttura e l’implementazione di tutto ciò che sta “sotto di
lui”, mostrando una chiara e semplice interfaccia con dei metodi da invocare.
Ma cosa permette di fare JDBC? Sostanzialmente un driver JDBC consente di effettuare tre
operazioni:
1. stabilire una connessione con una sorgente di dati (un database relazionale per es.)
2. inviare comandi SQL come update, insert, select, etc.
3. elaborare e manipolare i risultati ottenuti
Nell’accedere ad una basi di dati possiamo individuare sostanzialmente due tipi di architetture o
modelli che dir si voglia.
Il primo modello è il “two-tier model” mentre il secondo è il “three-tier model”.

Il two-tier model prevede che l’applicazione dialoghi in maniera diretta con il database
sottostante. L’utente in questa maniera sfruttando il driver JDBC adatto agisce direttamente
sulla sorgente di dati. In particolare i comandi dell’utente vengono inviati al database il
quale ritorna all’utente i risultati opportunamente costruiti.
E’ chiaro come questo modello sia l’esempio più classico di architettura client/server in cui
l’host dell’utente è il client, mentre la macchina che ospita il database svolge la parte del
server.
In questo particolare modello proprio perché l’interazione con la sorgente di dati è stretta e
diretta, l’applicazione potrebbe sfruttare eventuali conoscenze sulle specifiche di
implementazione del database per migliorare le performance o utilizzare caratteristiche o
proprietà non standard. E’ logico però, che questa pratica, ha anche l’immediato svantaggio
di limitare fortemente la portabilità e il riuso del codice con sorgenti di dati differenti.
Il two-tier model

L’architettura three-tier model è studiata con lo scopo fondamentale di fornire migliori
performance, scalabilità e disponibilità di servizi/controlli aggiuntivi: è per questo che è
tipicamente usata per lo sviluppo di applicazioni enterprise.
Il three-tier model
Come si può chiaramente vedere in figura si possono individuare tre componenti
fondamentali:
1. Client tier – essa rappresenta lo strato presentazione con il quale l’utente interagisce (un
browser, una gui di un programma, etc.). Non c’è alcuna necessità che questa
componente conosca dettagli implementativi del database sottostante.
2. Middle tier server – questo strato intermedio comprende tutti quei servizi posti tra base
di dati e utente. A questo livello c’è la possibilità di controllare e gestire l’accesso alla
base di dati e altresì gestire in maniera adeguata le risposte provenienti dalla stessa. I
servizi aggiuntivi vengono per l’appunto forniti da applicazioni di diverso tipo che
possono interagire fra loro.
3. Data Source Tier – il livello a cui sono collocati i dati veri e propri: il database
relazionale o qualsiasi altra sorgente di dati. La cosa fondamentale è che questa base di
dati sia accessibile mediante un driver che rispetti le specifiche JDBC.
Dopo questa breve analisi si sarà capita l’importanza che ha il driver JDBC come intermediario tra
utente e dati. Senza driver infatti bisognerebbe di volta in volta scrivere codice (anche molto
complicato) che dialoga e lavora con la base di dati. Ciò oltre ad essere improponibile non è spesso
possibile, perché richiederebbe una conoscenza dettagliata e approfondita dell’implementazione del
database con il quale di volta in volta si lavora. Come sappiamo questo non è possibile per la
maggior parte dei database commerciali.
Ecco quindi che i driver opportunamente implementati (a seconda del diverso db) permettono al
programmatore di invocare quei metodi esposti dalle API JDBC e di non preoccuparsi di come
queste effettivamente funzionino. Trasparenza prima di tutto!
I driver però non sono tutti uguali e possono essere divisi fondamentalmente in quattro categorie:
1. JDBC-ODBC bridge: si tratta in pratica di driver JDBC che fanno da “ponte”, fornendo
accesso ai driver ODBC di Microsoft.
2. Native-API partly Java driver: questi driver convertono le chiamate JDBC nelle
corrispondenti chiamate dei client dei relativi DBMS (Oracle, Sybase, Informix, etc.).
Questo significa che il driver per effettuare l’accesso alla base di dati contiene codice java
che invoca metodi nativi C/C++ messi a disposizione dai produttori del database.
3. JDBC-Net pure Java driver: è la soluzione forse più flessibile, visto che consente la
connessione a database differenti. Questo perché le chiamate JDBC sono convertite in un
protocollo generico, per poi essere riconvertite lato server nelle API specifiche del database.
In pratica il client invoca mediante i socket una applicazione middleware sul server che
traduce le richieste in base alle specifiche del driver.
4. Native-protocol pure Java driver: questo tipo di driver dialoga direttamente con il DBMS
mediante socket, convertendo le chiamate JDBC nel protocollo di rete utilizzato. Si tratta
della soluzione tipicamente adottata dagli stessi produttori del database, visto che richiede
una conoscenza diretta e approfondita della struttura e dell’architettura del DBMS.
2.0 ESEMPI INTRODUTTIVI ALL’USO DI JDBC
In questa sezione cercheremo di vedere attraverso alcuni esempi pratici, con frammenti di codice i
passaggi fondamentali che un’applicazione dovrebbe seguire (o può seguire) nell’accedere a basi di
dati mediante l’interfaccia JDBC.
2.1 Recuperare una connessione
Prima di tutto occorre creare una connessione in maniera da poter dialogare col database, inviando
comandi sql e ricevendo risultati delle query o delle update.
In questo passaggio sono coinvolte tipicamente una classe (java.sql.DriverManager) e due
interfacce (java.sql.Driver e java.sql.Connection).
La prima parte dell’operazione prevede il caricamento del Driver che si vuole utilizzare: basta una
semplice riga di codice.
Nel caso ad esempio del driver per Mysql:
Class.forName(“com.mysql.jdbc.Driver”);
Questa semplice istruzione non fa altro che registrare presso un’istanza della classe DriverManager
(creata mediante l’invocazione di questo metodo), il driver specificato.
Come è possibile ciò?
Basti pensare al fatto che ogni Driver deve contenere una sezione speciale di codice al suo interno
detta per l’appunto “static initializer” e che è simile a questa:
static {
try{
java.sql.DriverManager.registerDriver(new Driver());
}
catch (SQLException E) {
E.printStackTrace();
}
}
Come si nota viene invocato il metodo registerDriver() della classe DriverManager.
La classe DriverManager ha infatti la funzione di mantenere una lista dei vari driver presenti, che
possono essere usati di volta in volta dall’applicazione.
Oltre al metodo registerDriver(), da segnalare anche deregisterDriver() che consente di rimuovere
dalla lista il driver specificato. Per avere infine una lista completa dei driver registrati presso la
classe DriverManager basta invocare il metodo DriverManager.getDrivers().
Ora che il driver è registrato e caricato, possiamo passare a negoziare la connessione.
Per far questo basta usare una istruzione simile a questa (arricchita in questo caso del controllo delle
eccezioni):
try {
Connection con = DriverManager.getConnection(url_jdbc, username, password);
}
catch (SQLException exc) {
exc.printStackTrace();
}
L’url jdbc è un parametro molto importante ed è legato al tipo di driver che stiamo utilizzando.
Tipicamente ha una sintassi di questo tipo :
jdbc:<protocollo>://<hostname o ipaddress>:<numero_porta>/<nome_db>
Un esempio concreto potrebbe essere questo (sempre rifacendoci a Mysql):
jdbc:mysql://192.168.0.24:3306/utenti
Se tutto va a buon fine e non vengono lanciate eccezioni allora significa che la connessione verso il
DBMS è stata aperta con successo e ora possiamo inviare statement SQL.
2.2 Inviare comandi SQL al server
Ora possiamo creare degli oggetti del tipo Statement o PreparedStatement e inviare al database una
serie di comandi SQL, siano essi semplici query o comandi di update come UDPATE, DELETE,
INSERT.
Prendiamo per esempio il caso di voler creare la nuova tabella STUDENTI. Ogni entry conterrà
svariate informazioni, quali nome e cognome, numero di matricola, indirizzo email e così via.
Vediamo il codice SQL come si presenta:
CREATE TABLE Studenti (
username VARCHAR(8) NOT NULL,
nome VARCHAR(30) NOT NULL,
cognome VARCHAR(30) NOT NULL,
email VARCHAR(20) NOT NULL,
matricola (6) NOT NULL,
PRIMARY KEY (matricola))
Questo è un puro e semplice esempio. Ora non ci resta altro da fare che assegnare quel codice che
abbiamo scritto poco sopra ad una stringa:
String createTableStudenti = codice_sql;
Il prossimo passo fondamentale è quello di creare un oggetto Statement che mi permetta di inviare il
codice SQL al DBMS. Con gli oggetti Statement sostanzialmente facciamo uso di due metodi:
Statement.executeQuery()
per inviare le query e ottenere in risposta un ResultSet.
Statement.executeUpdate()
per inviare comandi di creazione o modifica di tabelle.
Per creare un’istanza di Statement, sfruttiamo l’oggetto Connection, quindi:
Statement stmt = con.createStatement();
Una volta fatto questo, dovremmo passare all’oggetto il codice SQL da inviare al dbms, nel nostro
caso il codice sql definito sopra (la stringa createTableStudenti).
Usiamo executeUpdate() visto che ci interessa creare una nuova tabella.
Stmt.executeUpdate(createTableStudenti);
Fatto questo possiamo passare quindi a inserire i dati all’interno di questa tabella.
Lo possiamo fare semplicemente come segue:
stmt.executeUpdate(“INSERT INTO Studenti ” +
"VALUES (‘mrossi’, ‘Mario’ , ‘Rossi’, ‘[email protected]’, ‘788909')");
E’ chiaro che operazioni del genere possono essere ripetute più volte: nella fattispecie potremmo
voler inserire nel database ad esempio una lista di 100 nuovi studenti (iscritti quest’anno), i cui dati
sono stati recuperati da un apposito file formattato.
E’ chiaro che potremmo tranquillamente studiare, ad esempio un ciclo while con opportuni controlli
che legge dal file e inserisci gli studenti mediante operazioni tipo quella che abbiamo visto sopra.
Esiste tuttavia una maniera molto più efficace e performante per affrontare situazioni tipo questa:
consiste nell’usare oggetti di tipo PreparedStatement.
La caratteristica principale è per l’appunto che oggetti di tipo PreparedStatement, a differenza degli
Statement, quando vengono creati viene passato loro in input del codice SQL. Il vantaggio sta nel
fatto che il codice SQL in questione viene passato al DBMS, e se questo supporta funzionalità di
precompilazione, l’oggetto restituito sarà non un semplice statement ma uno statement con codice
SQL precompilato. Questo implica che il DBMS non dovrà ricompilarlo una seconda volta nel caso
di chiamate del tipo execute() o executeQuery().
Vediamo come si crea un oggetto di tipo PreparedStatement:
PreparedStatement insertStudenti = con.prepareStatement(
“INSERT INTO Studenti VALUES ( ? , ? , ? , ? ,?)”);
Come si nota chiaramente al posto di quelli che dovrebbero essere i valori da inserire nel database
sono presenti dei punti di domanda che indicano la presenza di parametri che dovranno essere
inseriti con opportuni metodi (come vedremo tra breve).
Per impostare i parametri mancanti basta fare quanto segue:
insertStudenti.setString(1, “mbianchi”);
insertStudenti.setString(2, “Marco”);
insertStudenti.setString(3, “Bianchi”);
insertStudenti.setString(4, “[email protected]”);
insertStudenti.setString(5, “789000”);
insertStudenti.executeUpdate();
I parametri vengono impostati mediante l’uso di metodi del tipo
setXXX(<numero_colonna>, <valore_da_inserire>);
dove XXX dipende sostanzialmente dal tipo di dato da inserire nel dbms, anche in relazione al tipo
di dati che questo supporta. Nel nostro caso i VARCHAR corrispondono in pratica al tipo Java
String.
Infine il tutto viene inviato al database mediante il metodo executeUpdate().
E’ chiaro che non ha molto senso utilizzare i PreparedStatement quando il numero di query/update
simili da eseguire sono una o due. Tuttavia quando cominciamo a ragionare in termini di molte
operazioni simili, magari sfruttando l’uso di cicli for o while, allora l’uso dei PreparedStatement è
la soluzione ottimale.
2.3 Elaborare e manipolare i risultati ottenuti
Finora abbiamo visto per lo più comandi di update o modifica al database.
Vediamo invece ora qualche esempio di query sql, che hanno come scopo principale quello di
ritornare un tabella o meglio un istanza di ResultSet contenente i dati che soddisfano i criteri
specificati all’interno della query.
Poniamo ad esempio di voler recuperare le informazioni di tutti gli studenti che si chiamano Marco.
Ecco come potrebbe essere il codice.
String queryName = “SELECT * FROM Studenti WHERE nome = ‘Marco’”;
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(queryName);
Ora che abbiamo a disposizione un ResultSet, su di questo possiamo lavorare e muoverci
consultando i risultati in esso contenuti e eventualmente utilizzarli a nostro piacimento.
Pensiamo al resultset proprio come ad una tabella, o anche volendo ad un array bidimensionale
composto da colonne e righe per l’appunto.
Nel caso di una query come quella che abbiamo presentato precedentemente otterremmo un
ResultSet simile a questo:
username
nome
cognome
email
matricola
mrossi
Marco
Rossi
[email protected]
788999
mivaldi
Marco
Ivaldi
[email protected]
788823
mtorrett
Marco
Torretta
[email protected]
778452
…
…
…
…
…
…
…
…
…
…
msarpi
Marco
Sarpi
[email protected]
786310
Per spostarci all’interno del ResulSet non basta far altro che utilizzare uno dei tanti metodi messi a
disposizioni dalle API JDBC.
Una cosa importante da ricordare è che quando viene creato il ResultSet il cursore è posizionato non
sulla prima riga, ma prima di questa: in un certo senso fuori dal ResultSet stesso.
Per questo tipicamente il primo metodo che si invoca è next() che consente di posizionarci sulla
prima riga del ResultSet e quindi all’interno dello stesso.
Spesso per visitare un result set si utilizza un ciclo for o un ciclo while (preferibilmente
quest’ultimo).
while (rs.next()) {
String username = rs.getString("username");
String nome = rs.getString("nome");
String cognome = rs.getString("cognome");
String email = rs.getString("email");
String matricola = rs.getString("matricola");
//altro codice ... ...
}
Per recuperare i dati dal result set si fa uso di funzioni del tipo getXXX in base al tipo di dato
memorizzato nel database.
Il parametro delle funzioni getXXX può essere a scelta o il numero di colonna del campo da
recuperare oppure il nome della colonna stessa. Nel nostro esempio sopra abbiamo utilizzato
quest’ultima soluzione. Attenzione che quando si fa uso della prima soluzione la numerazione delle
colonne parte da 1, a differenza di come siamo abituati a considerare gli array in cui la numerazione
sappiamo parte da 0.
Esistono molti metodi, che come dicevamo consentono di spostarsi tra le righe del result set.
Metodi per il posizionamento assoluto o relativo, metodi che permettono di spostarsi all’indietro
(invece di next() si usa previous()) e metodi che permettono di posizionarsi direttamente alla prima
o all’ultima riga del result set o addirittura prima o dopo delle stesse.
Questi metodi sono stati fondamentalmente introdotti a partire da JDBC 2.0.
Vediamo i più importanti:


boolean absolute(int row);
void afterLast();

void beforeFirst();

boolean first();



boolean isAfterLast();
boolean isBeforeFirst();
boolean isFirst();

boolean isLast();

boolean last();


boolean previous();
boolean relative();
Il fatto che questi metodi possano essere utilizzati o meno dipende anche dal tipo di ResultSet che
viene restituito, a seconda ad esempio che sia un result set scorrevole (novità JDBC 2.0) o di tipo
forward-only.
Questa parte introduttiva all’uso di JDBC si conclude.
BIBLIOGRAFIA
[1]
“JDBC Data Access API For Driver Writers”: http://java.sun.com/products/jdbc/driverdevs.html
[2]
“JDBC 2.0 Core Api”: http://java.sun.com/products/jdbc/jdbc20.pdf
[3]
“JDBC 2.0 Standard Extension Api”: http://java.sun.com/products/jdbc/jdbc20.stdext.pdf
[4]
M. FISHER, J. ELLIS, J. BRUCE. “JDBC Api Tutorial and Reference. Third Edition”. Addison
Wesley, 2003.
[5]
G. REESE. “Database Programming with JDBC and Java. Second Edition”. O’Reilly and
Associates, 2000.
Questo documento può essere liberamente copiato e distribuito da chiunque, ma a nessuno è
permesso di cambiarlo in alcun modo. Ogni copia o documento derivato dovrà riportare l'indicazione
alla fonte.
Qualsiasi commento o suggerimento è benvenuto e può essere inviato al seguente indirizzo:
[email protected]. Eventuali correzioni di questa pubblicazione saranno disponibili sul sito internet
http://www.techtown.it
Scarica