MokaByte Numero 10 - Luglio 1997
Foto
 
JDBC - La pratica 
II parte
 
di
Giovanni Puliti
Trattazione teorica delle API di connessione a basi di dati

 


 



Dopo aver introdotto il mese scorso i concetti principali delle API JDBC, vediamo questa volta di entrare nei dettagli delle varie classi e metodi necessari per la gestione di basi di dati.


 

Nel numero scorso di Mokabyte abbiamo parlato delle nuove API JDBC presenti nel JDK 1.1, cercando di introdurre alcuni concetti basilari relativi alla programmazione e gestione di software relativo alla manipolazione di database. Buona parte delle considerazioni fatte allora sono generiche e non necessariamente legate al caso specifico del linguaggio Java. Anche se può essere sembrato leggermente sovradimensionata trale introduzione si è resa necessaria a causa della notevole quantità di concetti legati al mondo delle basi di dati non propriamente collegati a Java. Quindi, sperando di aver colmato ogni lacuna relativamente a tali argomenti, possiamo adesso dedicarci in tutta tranquillità a Java.

Vedremo in questo articolo in dettaglio le principali classi e metodi per la gestione di database e per implementare le cosiddette 2-3 Tier Architecture (in ).

 

Organizzazione delle API

Come prima cosa vediamo come tali API sono organizzate: si possono individuare due sottogruppi principali, anche se per maggiore chiarezza useremo la metafora degli strati, per distinguere il livello vicino all'applicazione e quello vicino al database. Questi due strati sono denominati Driver Layer e Application Layer (vedi Fig1).

Il primo strato è responsabile della interazione diretta col database attraverso chiamate a basso livello verso le tabelle dello stesso, mentre lo strato successivo è quello strettamente legato all'applicazione. Il colloquio fra i due livelli è reso possibile rispettando le specifiche delle API ed utilizzando in pratica la classe Driver.

Principalmente questa parte di codice è cositituita da quattro interfaccie che ogni driver deve implementare per mantenere la compatibilità con le direttive JDBC, più la classe Driver; le 4 interfaccie sono Driver, Connection, Statement, e ResultSet.

Dal punto di vista dello sviluppatore, non è necessario conoscere a fondo tali parti ma solo rispettare le specifiche JDBC ed utilizzare uno oggetto di classe Driver. La frammentazione del package in queste due componenti principali permette, attraverso una suddivsione dei compiti mirata al tipo di soggetto utilizzato, di mantenere la genericità voluta.

Lo strato Driver

Il cuore di questa sezione sono le due interfaccie Driver e DriverManager: la prima è strettamente legata al concetto di database vero e proprio, e di fatto ne rappresenta l'astrazione nell'ambito dell'applicazione.

La interfaccia DriverManager, invece, si preoccupa di caricare e scaricare a run time, i singoli driver, di eseguire la connessione coi singoli driver, e di fornire i vari servizi per il login e logout ai db.Vediamo di analizzare in dettaglio questi oggetti.

L'interfaccia Driver
L'interfaccia Driver rappresenta all'interno dell'applicazione il riferimento al database al quale si connette. Il procedimento di connessione alla base di dati, parte dalla definizione della cosiddetta stringa di connessione, che passata al costruttore della classe permette di definire le varie proprietà di collegamento: ad esempio il tipo di db usato, alcune informazioni strettamente legate alla connessione come host-name, porta, db-name, UID e password, ma anche ulteriori informazioni relative al database e alla sua collocazione.
La stringa di inizializzazione ha una sintassi molto simile a quella utilizzata per gli URL usati dal sistema World wide Web: in effetti i requisiti necessari per la stringa di connessione sono completamente supportati dalla sintassi URL (Vedi riquadro).
Lo psuedo indirizzo url rappresentato dalla stringa segue la seguente sintassi:

dove <subprotocol> definisce il tipo del driver, mentre <subname> definisce il nome del db codificato secondo le specifiche del sistema.
Ad esempio

  jdbc:sybase:impiegati

definisce un driver per Sybase e un db denominato impiegati.
La parte dedicata al subaname può inglobare informazioni più specifiche relativamente al dabase, come il path assoluto, la porta di accesso al server, l'account e la password, in maniera del tutto analoga a come avviene ad esempio nella definizione di un URL di una pagina WEB. Ad esempio possiamo quindi avere

  jdbc:msq1://mokabyte.programmers.net.+puliti+.+password+:1112/mokadb

dove si é specificato il server sul quale risiede il db, la UID e password, la porta ed infine il path del db relativamente alla macchina specificata.
La definizione di una classe Driver partendo dalla interfaccia relativa, può essere fatta semplicemente implementando i seguenti metodi

  public abstract Connection connect (String url, Properties info) throws SQLExpection

tale metodo deve controllare se l'url passato è esatto e se corrisponde ad un db.

In caso affermativo il driver deve tentare di instaurare una connessione utilizzando il resto delle informazioni contenute in url. Tale metodo deve scatenare una eccezione SQLException solo se il driver riconosce come esatto l'url ma per un qualche motivo non può eseguire una connessione col db.
Se l'url non corrisponde a nessun db il metodo restituisce null senza fallire.
La classe Properties passata in input contiene l'user name e la password.

  public abstract boolean acceptsURL (String url) throws SQLExpection

permette di controllare se un URL è esatto senza controllare se la connessione può esserer eseguita.

Infine il metodo Connect() è il più importante, e viene invocato dal DriverManager per ottenere una connessione attraverso una classe Connection: la classe Connection è il punto di partenza per operazioni JDBC.
 

La classe DriverManager
Questa classe è veramente utile e fornisce tutti i metodi fondamentali per la connessione a database, per settare i parametri di login-logout, ed infine per registrare e deregistrare i vari driver.(vedremo avanti il significato di questa operazione e perché sia tanto importante).

Vediamo di elencare i metodi principali di classe analizzandone le principali caratteristiche: tutti i metodi sono statici ( e devono essere ridefiniti come tali nella ridefinizione della interfaccia) per cui possono essere invocati anche senza instanziare la classe di riferimento che comunque è la java.sql.DriverManager:

Questo metodo e gli altri della stessa famiglia ma con firma diversa, tentano una connessione restituendo un oggetto Connection. Durante la fase di apertura della connessione viene scorsa una lista di driver ed il primo che permette la connessione viene scelto. La lista dei driver viene conservata in un oggetto di tipo Vector.

Le varianti di tale metodo permettono di specificare eventualemente UID e password.

Esistono fondamentalmente tre modi per caricare un driver al DriverManager e renderlo quindi disponibile al DriverManager durante il tentativo di connessione.

Il primo, ed il più consigliato per motivi di genericità e quindi di portabilità, è quello che esegue una chiamata al metodo Class.forName, il quale esplicitamente carica la classe driver in questione e la aggiunge alla lista: supponendo di voler caricare un driver denominato MBPkgs.drivers.MBdriver1, allora possiamo scrivere

  Class.forName("MBPkgs.drivers.MBdriver1");

Se il driver MBPkgs.drivers.MBdriver1 è presente, allora tale metodo causa una istanza dell'oggetto, ed indirettamente una chiamata al metodo DriverManager.registerDriver, con tale oggetto come parametro, in modo da aggiungerlo alla lista dei driver disponibili.

Un secondo modo per ottenere lo stesso risultato consiste nell'aggiungere esplicitamente il nome del driver in questione alla lista di sistema dei driver disponibili contenuta in jdbc.drivers, e che viene automaticamente caricata all'istanziazione di un oggetto DriverManager. Per aggiungere un driver a tale lista si può ad esempio utilizzare una istruzione del tipo

  jdbc.drivers=MBPkgs.drivers.MBdriver1:MBPkgs.drivers.MBDriver2:oracle.sql.Driver

come si vede si tratta di una sequenza di path (comprese le specifiche del relativo package) separati da due punti.

Infine per ottenere lo stesso risultato consiste nel definire direttamente i driver necessari o disponibili per mezzo dei metodi

  public static synchronized void registerDriver(Driver driver) throws SQLException
    public static void deregisterDriver(Driver driver) throws SQLException
i quali permettonmo di aggiungere e di togliere Driver dalla lista di quelli disponibili che vengono ricercati dal DriverManager.
 

 

L'Application Layer

Equivalentemente a quanto troviamo nel layer del driver, anche nello strato del JDBC troviamo tre interfaccie fondamentali la cui implementazione permette il mantenimento dello standard di comunicazione dei dati. Queste tre interfaccie sono Connection, Statement, e ResultSet

La connessione è l'oggetto base con cui è possibile interagire col db; un oggetto connection e' ottenibile come servizio dalla classe driverManager attraverso il metodo driverManager.getConnection(); nel momento in cui si dispone di una connessione è possibile creare statements a partire da questo oggetto e quindi operare sui record delle varie tabelle.
 

Connection
Rappresenta una sessione di collegamento col db resa possibile per mezzo del Driver utilizzato.
Prima di procedere vorrei brevemente fare una parentesi a proposito di alcuni concetti basilari riguardo l'interazione con una base di dati, quali ad esempio transaction, rollback e commit.
Una transazione rappresenta l'esecuzione di una operazione di modifica dei dati contenuti nell'archivio. In genere in una architettura Client/Server o in un sistema a tre strati, l'effettiva modifica viene lasciata in sospeso finché non viene invocata una operazione di commit, che forza l'effettiva modifica dei dati.

Questa suddivisione delle varie fasi permette ripensamenti o coordinamenti in ambienti distribuiti fra una transazione ed una commit per mezzo dell'esecuzione di una rollback che in genere rimette le cose a posto come erano prima della transaction.

Per default, al momento della creazione, una Connection JDBC si trova impostata in autocommit mode, per cui le modifiche vengono eseguite automaticamente dopo ogni transazione. E' possibile modificare tale proprietà passando come parametro false al metodo setAutoCommit(boolean b).
Nel caso in cui sia disabilitata la modalità di autocommit, ha senso utilizzare i metodi Connection.commit() e Connection.rollback(), per controllare meglio il flusso dei dati.
Il livello di isolamento al momento dell'invio della transazione dipende dalla implementazione della struttura C/S (vedi 2,3 Ties).

Vediamo alcuni dei metodi più importanti della interfaccia Connection

    public abstract Statement createStatement() throws SQLException
    public abstract PreparedStatement prepareStatement(String sql) throws SQLException
    public abstract CallableStatement prepareCall(String sql) throws SQLException

Come è facilmente intuibile dal loro nome, questi tre metodi permettono di creare statements, cioé istruzioni da inviare al dbms, sia per eseguire operazioni sui dati, sia più semplicemente per estrarre informazioni. Più avanti sarà più chiaro l'uso che è possibile fare di tali metodi in relazione sopratutto al concetto di statement.
Infine abbiamo i metodi di cui si è parlato in precedenza di cui ormai è chiaro il significato.

    public abstract void commit() throws SQLException
    public abstract void rollback() throws SQLException
 

Statements
La manipolazione di un database per mezzo di un linguaggio ad alto livello può essere fatta sia utilizzando istruzioni direttamente disponibili nel linguaggio stesso (vedi le vaire opendatabase, CreateDynaset, Update, etc.. del VisualBasic), oppur, facendo uso di una strategia più a basso livello, far uso di istruzioni in linguaggio SQL inviate al DBMS che penserà a processare tali comandi ed a restituire i risultati.
Nel caso di Java per svolgere tale compito si deve far uso della classe statement che con tutte le sue varianti permette di inviare tali istruzioni SQL al database.
Prima di analizzare le varie interfaccie per la definizione di istruzioni SQL, analiziamo dettagliatamente la più semplice, elencandone i metodi principali.

  public abstract ResultSet executeQuery(String sql) throws SQLException

Esegue una semplice istruzione SQL passata in input e resituisce il recordset dei risultati sotto forma di un oggetto della classe ResultSet.

  public abstract int executeUpdate(String sql) throws SQLException

Esegue un update secondo quanto scritto nella stringa SQL passata; non restuisce nessun set di risultati, ma solo il numero di righe (record) affetti da cambiamento.

  public abstract ResultSet getResultSet() throws SQLException

Dopo aver eseguito una operazione sul db è possibile leggere il buffer contenente i risultati.

Se è il buffer è gia' stato letto o se è stata eseguita una operazione come UpDate che restituisce solo un nuemero, la lettura del buffer restituisce null.

  public abstract int getUpdateCount() throws SQLException

Restituisce lo stato di una operazione di aggiornamento update,insert, delete. Il valore resitituito è il mumero di righe che hanno subito il cambiamento, o -1 se non è disponibile nessun update count o se è stata eseguita precedentemente una operazione di update che non restituisce un update count ma un recordset.

  public abstract boolean getMoreResults() throws SQLException

qaesto metodo si muove sul risultato successivo nel caso in cui si abbia a che fare con un insieme di risultati multipli. Questo risultato restituisce true se il successivo risultato è un oggetto di tipo ResultSet.
Nel caso in cui si necessiti di ottenere in uscita un singolo recordset, si può utilizzare il metodo executeQueryche restituisce un singolo oggetto ResultSet, altrimenti il più generico execute() permette di avere sia un singolo recordset che multipli recordsets. Nel caso si decida di utilizzare risposte multiple, per ottenere informazioni sui singoli recordset si possono utlizzare i soliti metodi getResultSet() getMoreResultSet(), e getUpdateCount().
L'interfaccia PreparedStatement deriva da Statement e offre una serie di meccanismi preparati nel caso in cui si debbano eseguire una serie di operazioni ripetitive.
La differenza fra uno statement ed uno prepared, è che quest'ultimo utilizza istruzioni SQL precompilate in cui si ha la possibilità di lasciare alcuni parametri generici e di settarli al momento della chiamata attraverso i metodi setType(): questo obiettivo viene perseguito istanziando i valori dei jolly nella posizione specificata.
I valori in un preparedStatement vengono mantenuti fino a che non viene eseguta una nuova chiamata ad un metodo setXXX() o non si effettua un reset di tutti i valori con clearParameters().

Come ultima cosa possiamo menzionare l'interfaccia CalleableStatement che deriva da Preparedstatements, e che permette di eseguire procedure SQL memorizzare.
L'uso di tali procedure dipende dalla particolare implementazione del database per cui una approfondita trattazione risulta essere utile solo in funzione del database scelto, ed in questa sede tralasceremo i dettagli di queste operazioni.
L'interfaccia ResultSet definisce i metodi per accedere ai dati ottenuti come risultato dall'esecuzione di uno statement. I valori relativi ai vari campi possono essere ricavati sia facendo riferimento al nome del campo sia al suo indice (la numerazione parte da 1).

Oltre al metodo next(), necessario per skippare al risultato successivo, questa interfaccia ci offre il metodo getMetaData() che restituisce un oggetto di tipo ResultSetMetaData(), col quale possiamo ricavare una serie di informazioni basilari sulla struttura del recordset ottenuto, come ad esempio il numero di colonne, il tipo di ogni colonna e altro ancora. In genere questa interfaccia, come la DatabaseMetaData, non sono molto utilizzate in quanto l'applicazione conosce la struttura del recordset ritornato, ma nel caso si voglia implementare una applicazione generica, questi metodi sono molto utili.
 

 

JDBC Drivers

Molti sono attualmente i driver disponibili per le API JDBC realizzati da aziende produttrici di database o da terze parti. JavaSoft cataloga i vari driver disponibili in 4 categorie:
 


 
 

Tecnologie alternative alla JDBC

Alternativamente od in abbinamento ad un driver JDBC è possibile associare una soluzione che faccia uso di tecnologie alternative come ad esempio RMI, CORBA o anche semplicemente HTTP.

Meno si utilizza JDBC e più si possono integrare tali tecnologie nella progettazione e realizzazione della struttura 1,2,3 stratificata. Dato che questi argomenti ci porterebbero ben lontano rimandiamo la loro trattazione.

In questa seconda parte dell'articolo abbiamo affrontato tecnicamente come sono strutturate le API JDBC, affrontando il problema dal punto di vista dell'applicazione e non dello sviluppatore del driver di gestione del database. In quest'ottica conluderemo il mese prossimo con un esempio in cui vedremo che sia semplice implementare una mini applicazione di interazione con un archivio mdb. A tutti buona lettura.
 
 
  

 

MokaByte rivista web su Java

MokaByte ricerca nuovi collaboratori
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it