Accesso ai database con Java e JDBC


Home Page | Commenti | Articoli | Faq | Documenti | Ricerca | Archivio | Storie dalla Sala Macchine | Contribuire | Login/Register

Java consente lo sviluppo di applicazioni portabili da un sistema all'altro (quasi) senza modifiche ne' ricompilazione.

Questa e' senz'altro una bella cosa, finche' non ci troviamo con una applicazione che necessita di memorizzare e/o accedere a delle informazioni strutturate e conservate in un database.

A questo punto abbiamo svariate possibilita': possiamo gestirci tutto il lavoro internamente (scrivendo il codice necessario), oppure possiamo utilizzare le classi specifiche per l'accesso ai database. Queste "classi" costituiscono JDBC: lo standard di accesso ai database per Java.

Nel codice di esempio presentato, viene sviluppata una semplicissima applicazione che consente l'accesso ad un database qualsiasi, l'invio ed esecuzione di interrogazioni ed il recupero di dati.

Per cominciare pero', un po' di teoria.

Esistono svariati tipi di database che possono essere utilizzati, ognuno ha i suoi punti di forza e di debolezza, Java consente di usarli piu' o meno tutti, occorre pero' conoscere le varie caratteristiche per scegliere il database che piu' si adatta alle proprie esigenze.

I cosiddetti "Flat File" memorizzano le informazioni in modo non strutturato in un unico file (di solito leggibile), ma forniscono ben poche funzionalita' a parte la memorizzazione ed il recupero delle stesse informazioni.

In genere l'accesso ai dati e' estremamente veloce, ma occorre ritrovare le informazioni utilizzando una ricerca di tipo "preciso" (non si puo' dire "cercami tutto quello che e' simile a..."), inoltre raramente supportano la multiutenza (piu' utenti contemporaneamente collegati).

Solitamente, i database sono utilizzati per memorizzare informazioni in modo "strutturato", utilizzando tabelle, records (singole righe di una tabella) e campi (singoli elementi all'interno di un record). Questo tipo di struttura consente un facile accesso/reperimento delle informazioni, migliorandone la gestione, inoltre permette la costruzione di indici che accellerano le ricerche.

Un Database Relazionale (RDBMS) permette la composizione di interrogazioni o query che collegano piu' tabelle, stabilendo delle "relazioni" tra i contenuti delle singole tabelle. Solitamente l'interrogazione del database viene fatta usando uno specifico linguaggio denominato SQL, a seconda delle capacita' (e del costo spesso) del database, possono essere presenti ulteriori capacita', quali la possibilita' di impostare controlli automatici di coerenza tra i dati (il dato nel campo X della tabella Y deve essere presente in uno dei record della tabella Z), l'esecuzione di procedure interne al database stesso quando richiamate esplicitamente (Stored Procedure) o in automatico quando si verificano cambiamenti ad un record in una tabella (Trigger).

Un database Object Oriented (OODBMS), gestisce le informazioni considerandole come "oggetti" con varie "proprieta'", invece che records con campi. Il risultato finale e' sempre lo stesso, ma viene svolto con differente orientamento.

Un database Object-Relational (ORDBMS), combina la gestione di un RDBMS con l'orientamento agli oggetti di un OODBMS, pertanto ha le forze e le debolezze di entrambi.

Per "livelli" (tier) si intende quelli che passano tra la propria applicazione ed i dati che sono gestiti.i

In una applicazione "single-tier", l'applicazione stessa gestisce i suoi dati direttamente. Questo tipo di applicazioni sono (in genere) altamente specializzate e richiedono un'accesso ai dati ultra-veloce. Fanno solitamente uso di database flat-file, che vengono letti/scritti usando delle librerie specializzate compilate nell'applicazione stessa.

Una applicazione "two-tier" indica che il programma stesso "parla" con un server di database che si preoccupa di gestire i dati e ritornare i risultati. Questo tipo di applicazioni e' il piu' comune. La "comunicazione" con il database puo' essere fatta tramite librerie particolari o tramite un driver caricato a run-time dall'applicazione stessa. Questa seconda possibilita' in genere implica che il database puo' essere cambiato senza troppe modifiche al programma stesso.

Una applicazione "three-tier" (o di piu') implica che tra la stessa ed il server di database ci sono uno o piu' "application server" che aumentano il livello di isolamento. L'applicazione cioe', invia le proprie richieste ad un'altra applicazione server, la quale parla con un'altra applicazione, la quale parla con un'altra applicazione... alla fine qualcuno parla con il database.

In generale piu' livelli ci sono tra l'applicazione ed il database e meno questi due elementi sono interdipendenti. Il che e' un bene dal punto di vista della possibilita' di cambiare database (o struttura dello stesso) senza cambiare l'applicazione, ma e' un male dal punto di vista della velocita' dell'applicazione nel reperire le informazioni.

Teniamo sempre conto poi che una parte dipendente dal database ci deve essere da qualche parte.

Una architettura "multi-tier" permette anche di distribuire il carico di lavoro tra varie macchine, migliorando quindi l'utilizzo di tutta la rete, ma e' anche dipendente da tutti i problemi che possono risultarne (dal cracking fino alla saturazione della rete stessa).

Anche nota come JDBC, e' un "livello di astrazione" definito da JavaSoft che fornisce ad una applicazione (java ovviamente) un'SQL standard ed un'insieme di classi per l'accesso e l'utilizzo di database.

Che significa tutto cio'? Molto semplice. Quando da un'applicazione Java vogliamo usare un database, invece di smazzarci il collegamento diretto, invochiamo la creazione di una classe driver, che ci fornira' una serie di classi standard, tramite i metodi di queste classi (metodi che sono specificati dalle interfaccie indicate nello standard JDBC), siamo in grado di accedere ai nostri dati, leggerli, modificarli etc. etc. Il tutto senza preoccuparci piu' di tanto del tipo di database o dello specifico dialetto SQL utilizzato. Il nostro driver si occupera' di tradurre il tutto e fornirci i risultati.

JavaSoft ha definito quali interfaccie devono essere implementate dai vari driver, e quali metodi sono inclusi in ogni interfaccia, sta' poi ai vari "produttori" fornire un driver che segua le specifiche.

A seconda del funzionamento, i driver si dividono in vari tipi:

Driver di tipo 1 (type 1 driver)

Questo tipo di driver traduce i metodi di chiamata JDBC in chiamate dirette verso un'altro driver fornito da un'altro produttore, cio' significa che entrambi i driver devono essere presenti perche' l'applicazione possa funzionare. Inoltre la presenza di livelli multipli tra i due driver puo' peggiorare le prestazioni del sistema. Un tipico esempio di driver di questo tipo e' il bridge Jdbc/Odbc.

Driver di tipo 2 (type 2 driver)

Questo tipo di driver traduce le chiamate JDBC in chiamate alle API native del database stesso. Le prestazioni sono solitamente migliori di un tipo 1. Possono esserci pero' dei problemi di distribuzione in quanto il driver puo' richiedere la presenza di librerie del database stesso (che non sempre possono essere distribuite senza un'apposita licenza) oppure l'installazione sul client di software specifico del database (client software) che potrebbe essere piuttosto costoso.

Driver di tipo 3 (type 3 driver)

Questo tipo di driver e' il piu' comune da trovare, il driver traduce le chiamate JDBC in chiamate ad un database-server usando le API di comunicazione sulla rete (socket). L'unico problema e' che e' richiesta la presenza di un database server o di una applicazione che ne emuli il funzionamento. La distribuzione di questo tipo di driver non presenta (di solito) problemi, la configurazione purtroppo richiede la conoscenza dell'indirizzo IP del server o del suo nome (se e' presente un DNS nella rete) e della porta che il server usa per ricevere chiamate.

Driver di tipo 4 (type 4 driver)

Questo tipo di driver traduce le chiamate JDBC in chiamate dirette al server usando le API della rete. Il driver e' (in sostanza) specifico per il singolo database. Questo tipo di driver presenta gli stessi problemi del tipo 3 sopramenzionato.

Non tutti i driver rientrano in uno di questi tipi, alcuni driver sono (per esempio) 100% java ed accedono direttamente un database locale. Questo tipo di driver sono ottimi per applicazioni desktop, ma non rientrano esattamente in nessuno dei tipi sopra esposti. Alcuni produttori forniscono JDBC driver che prevedono determinati vantaggi in varie applicazioni.

Il tipo di database scelto per la propria applicazione puo' impattare sulla difficolta' di codifica e sulla complessita' dell'applicazione stessa. Sicuramente pero' impatta sulla velocita' ed abilita' nel reperire le informazioni. Inoltre puo' essere utile spostare determinate funzioni all'interno del database stesso (in Stored Procedre o in Trigger).

Per utilizzare completamente un design Object-Oriented, in molti casi e' preferibile l'uso di un database Object-Oriented.

Altri fattori da tenere in considerazione nella scelta del database sono la robustezza dello stesso, il formato nel quale sono memorizzati i dati, l'effettivo supporto per piu' utenti contemporanei (multiutenza), la disponibilita' su piu' piattaforme (portabilita') ed ovviamente il costo dello stesso.

Robustezza

Con "robustezza" si intende, non solo l'attitudine del database a bloccarsi e/o danneggiarsi autonomamente, ma anche la possibilita' o facilita' nel recuperare le informazioni nel malaugurato caso di crash della macchina o danneggiamento della struttura dati.

Il supporto (ad esempio) di backup "a caldo" (senza dover arrestare il database stesso), aumenta di molto le possibilita' che i dati possano essere recuperati senza grossi problemi anche in caso di crash del sistema.

Formato del database

Un database puo' essere standard o no, un db che non e' standard utilizzera' propri tools per accedere alle informazioni e consentirne la modifica.

Tutti (o quasi) i database server utilizzano un formato di memorizzazione dei dati che e' proprietario, per accedere ai dati in genere questi database richiedono una login, cioe' l'utente deve fornire una password, inoltre la visibilita' di certe informazioni e' modificabile da parte di un amministratore che puo' scegliere se mostrare o no certe informazioni.

Certi database flat-file utilizzano una struttura proprietaria, il che implica che per accedere al database stesso e' richiesto uno specifico driver e certe informazioni (struttura delle tabelle), altri flat-file usano invece una struttura standard che consente il reperimento delle informazioni usando tools standard (dBase III per esempio).

Benche' quest'ultimo formato sia meno "sicuro", e' certamente preferibile nel malaugurato caso di crash dell'applicazione, perche' consente sempre l'accesso ai dati.

Multi-user o Single-user

Occorre fare molta attenzione alla possibilita' di utenti multipli che utilizzano lo stesso database nello stesso momento. Il blocco di record e tabelle e' in genere, supportato da tutti i database, ma ognuno con metodi e finalita' diverse.

L'applicazione deve comunque essere scritta in modo da trarre vantaggio di questi meccanismi (transazioni, lock etc.).

Portabilita'

Dato che e' uno dei punti di forza di Java, e' obbligatorio considerare la portabilita' del proprio sistema. In particolare la presenza di driver per vari sistemi (Windows/Linux/Unix/Mac), ed eventualmente l'esistenza di database multipiattaforma.

Costo

Che dire riguardo a questo ?

JDBC e' composto da varie classi che "cooperano" per fornire l'accesso ai dati del database.

In prima linea viene il Driver, che interpreta tutte le funzioni richiamate e le 'traduce' per il database, il Driver deve essere caricato in memoria, una volta fatto questo e' possibile utilizzarne le funzioni per creare una Connection, che incapsula il vero e proprio collegamento al database. Una Connection e' necessaria per poter fare qualunque altra cosa.

Una volta ottenuta la Connection siamo in grado di usare quest'ultima per produrre Statement, che verranno a loro volta usati per ottenere ResultSet, i quali (puff puff) contengono i dati.

Non e' sempre necessario l'uso di un ResultSet. Se vogliamo semplicemente eseguire delle modifiche sui dati (usando una UPDATE, INSERT o DELETE), possiamo usare il metodo executeUpdate() direttamente sullo Statement.

Il driver viene caricato in memoria in due modi: o usando un caricamento esplicito:


Class.forName("nome.della.classe.del.driver");

oppure usando DriverManager per eseguire il caricamento:


DriverManager.registerDriver(new nome.della.classe.del.driver());

In entrambi i casi il nome della classe del driver e' necessario, tale nome e' (di solito) riportato nella documentazione che accompagna il driver stesso.

Una volta che il driver e' in memoria possiamo usare DriverManager per ottenere una connessione al database, per fare cio' ci serve un URL al database.

Questo e' il "percorso" per accedre al database. Un URL e' composto in genere da vari parametri separati da ":" o ";". Uno di questi parametri e' l'indirizzo IP o il nome del server in questione. Altri parametri sono tipici del database stesso e non possono essere previsti. Anche per questo, tutti i parametri sono descritti nella documentazione che accompagna il driver.

Un esempio di URL potrebbe essere il seguente:


jdbc:inetdae:172.20.118.106:1433

In alcuni casi il database richiede anche un'autenticazione, cioe' un nome utente ed una password che siano validi. In genere questi sono parametri aggiuntivi da passare nell'URL del database stesso.

Una volta che abbiamo il nostro URL possiamo usare la DriverManager.getConnection() per ottenere una connessione al database. Se qualche cosa e' sbagliato (URL o altro), otterremo una bella (si fa per dire) eccezione.

Che ci si fa con la connessione ? Tutto quello (o quasi) che vogliamo.

Una volta ottenuta la connessione possiamo interrogare il database inviandogli una SELECT:

Per prima cosa costruiamo un PreparedStatement:


String sql="SELECT * FROM nomedellatabella WHERE nomecampo=?";
PreparedStatement p=conn.prepareStatement(sql);

Una volta ottenuto il PreparedStatement possiamo inserire il parametro di filtratura usando uno dei metodi setXxxxxx(), a seconda che il nostro parametro di filtro sia un numero o una stringa. L'utilita' del PreparedStatement rispetto al costruire una stringa SQL "a mano" e' che gestisce lui le conversioni di ' in '' e dei vari formati numerici. Inoltre aggiunge i vari apici se necessario.


p.setString("questo e' il filtro");

Una volta impostati tutti i parametri, possiamo eseguire la query ed ottenre un ResultSet in risposta:


ResultSet r=p.execute();

Il ResultSet potrebbe essere vuoto o contenere 'n' record, per controllarne il contenuto si verifica se il metodo next() ritorna true o false:


while(r.next()) {
	// eseguo il controllo dei dati
}

Attenzione: per motivi di portabilita' tra i vari driver, i campi da un ResultSet possono essere letti solo nell'ordine in cui sono dichiarati nella SELECT ed una sola volta. Esistono driver che consentono di aggirare tali limitazioni, ma per evitare problemi e' sempre meglio seguire le specifiche.

L'accesso ai singoli campi del ResultSet puo' essere fatto sia usando il nome del campo, sia usando un numero progressivo:


rs.getXXXX(1);	// ritorna il primo campo
rs.getXXXX("nomecampo");	// ritorna il campo "nomecampo"

Le funzioni getXxxxx() convertono automaticamente il tipo di campo nel tipo richiesto.

Una volta concluso l'utilizzo del ResultSet, possiamo chiuderlo per distruggere il corrispondente cursore e liberare risorse, ricordiamoci che il ResultSet e' connesso ad uno Statement, quindi chiudere lo Statement equivale a chiudere anche il cursore. Ma fare le cose pulite e' sempre meglio, quindi:


r.close();	// chiude il ResultSet
s.close();	// chiude lo Statement

Il metodo getMetaData() ritorna un'oggetto di tipo MetaData che contiene informazioni relative al tipo ed al numero di campi contenuti nel ResultSet. Analogamente possiamo ottenere informazioni sul contenuto dell'intero database tramite la Connection.

L'accesso a questo tipo di informazioni e' utile se stiamo scrivendo una funzione di accesso generico a database o se vogliamo controllare l'esistenza di una certa tabella prima di eseguire una qualche query.

Il codice di esempio e' una semplice interfaccia generica per l'accesso ad un qualunque database di cui sia fornito il driver JDBC.

Non e' niente di complicato ne' sofisticato, quindi non aspettiamoci niente di straordinario.

Qualche spiegazione sul codice:

1. configurazione
Dato che il programma e' generico, il driver e l'URL sono definiti esternamente, nel corrispondente .conf file. In questo file i parametri sono definiti come =, il .conf fornito e' di esempio e deve essere modificato in funzione del vostro database e driver jdbc.

2. classpath
Il programma si aspetta di trovare il driver jdbc nel classpath, quindi o mettiamo il .jar nella $JAVA_HOME/jre/lib/ext, o passiamo il classpath sulla riga di comando di java durante l'esecuzione con java -classpath .:path/per/driver.jar DBI (nota, su Windows occorre usare ';' invece di ':' per separare i path).

3. funzionamento
Quando richiamata, la classe tenta di aprire una connessione al database usando i parametri specificati nel file di configurazione, se riesce nell'intento presenta un prompt sql> ed aspetta che l'utente digiti una query. La stessa viene passata al database ed i risultati vengono mostrati di seguito.

Il programma fa' una minima elaborazione dei dati per presentare i nomi dei campi e gli stessi con un minimo di struttura, ma non piu' di tanto.

Download del codice

Contenuto dell'archivio:

dbi.jar e' il codice compilato ed "impacchettato" in un'unico archivio compresso.
DBI.java, Config.java sono le due classi che compongono l'applicazione, Config.java e' una classe di supporto usata per leggere i parametri di configurazione.
dbi.conf e' il file di configurazione d'esempio fornito a corredo.

Esecuzione del programma

Per eseguire il programma richiamare la classe di avvio DBI specificando il classpath per il driver JDBC da usare.


I commenti sono aggiunti quando e soprattutto se ho il tempo di guardarli e dopo aver eliminato le cagate, spam, tentativi di phishing et similia. Quindi non trattenete il respiro.

2 messaggi this document does not accept new posts
stecolnastecolna Di stecolna - postato il 24/09/2008 09:26
Ottimo articolo!

Davide, non che potresti postare un piccolo articolo sull'uso dei RecordSet JDBC disconnessi? Se chiedo troppo ti chiedo scusa. Ciao


me lo segno


stecolnastecolna Di stecolna - postato il 24/09/2008 12:39

Thanks!!!

Precedente Successivo

Davide Bianchi, lavora come Unix/Linux System Administrator presso una societa' di "sicurezza informatica" (aka: $networkgestapo) di Haarlem. Contatti: mail: davide AT onlyforfun.net , Jabber: davideyeahsure AT gmail.com,

Volete contribuire? Leggete come!.
 
 

Il presente sito e' frutto del sudore della mia fronte (e delle mie dita), se siete interessati a ripubblicare uno degli articoli, documenti o qualunque altra cosa presente in questo sito per cortesia datemene comunicazione (o all'autore dell'articolo se non sono io), cosi' il giorno che faccio delle aggiunte potro' avvisarvi e magari mandarvi il testo aggiornato.


Questo sito era composto con VIM, ora e' composto con VIM ed il famosissimo CMS FdT.

Questo sito non e' ottimizzato per la visione con nessun browser particolare, ne' richiede l'uso di font particolari o risoluzioni speciali. Siete liberi di vederlo come vi pare e piace, o come disse qualcuno: "Finalmente uno dei POCHI siti che ancora funzionano con IE5 dentro Windows 3.1".

Web Interoperability Pleadge Support This Project
Powered By Gort