MokaByte Numero 31  - Giugno 1999
Le transazioni in Java
di 
Roberto Bagnoli
II  Parte


 

Nell'articolo precedente sono stati introdotti alcuni concetti teorici sulle transazioni, validi in generale, indipendentemente dal DBMS e dal linguaggio di programmazione utilizzato. In questa seconda parte si cercherà di analizzare l'API JDBC alla ricerca delle primitive per la gestione delle transazioni.

Supporto per le transazioni

In JDBC tutti i servizi di supporto alla gestione delle transazioni sono concentrati a livello dell'interfaccia java.sql.Connection. (Per chi avesse dei dubbi riguardo la creazione ed il funzionamento delle connessioni JDBC può utilizzare la breve appendice in coda a questo articolo, mentre per una spiegazione più approfondita sull'uso del JDBC si può far riferimento agli altri contributi sull'argomento apparsi nei precedenti numeri di MokaByte).
Dando uno sguado alla documentazione dell'interfaccia Connection fornita a corredo con il JDK 1.1.x, si possono individuare i seguenti metodi:
 
public void setAutoCommit(boolean autoCommit) throws SQLException
public boolean getAutoCommit() throws SQLException
public void commit() throws SQLException
public void rollback() throws SQLException
public void setTransactionIsolation(int level) throws SQLException
public int getTransactionIsolation() throws SQLException

 
 

I metodi setAutoCommit() e getAutoCommit()

La specifica JDBC prevede che per default un oggetto di tipo Connection deve essere posto nello stato di auto-commit. Questo significa che una connessione esegue automaticamente un operazione di commit al termine dell'esecuzione di ogni statement SQL (in realtà il momento in cui viene effettivamente terminata la transazione viene deciso in modo un po' più complesso, ma la sostanza non cambia). In altre parole, per default un oggetto di tipo Connection non permette di racchiudere più comandi SQL all'interno di un unica transazione: ogni comando è una transazione a sè.
Il metodo setAutoCommit() permette di modificare tale comportamento. Esso riceve un parametro booleano:
  • con il valore true si richiede che l'oggetto di tipo Connection chiuda automaticamente le transazioni al termine dell'esecuzione di ogni comando SQL (vale a dire, il comportamento di default);
  • con il valore false si richiede che il commit o l'abort delle transazioni avvenga solo in modo esplicito, tramite l'invocazione dei metodi commit()e rollback().
Con il metodo getAutoCommit() è possibile interrogare una Connection per determinare se essa si trova nello stato di auto-commit mode oppure no.
 
 
 

I metodi commit() e rollback()

Una volta che l'oggetto Connection è stato posto in modalità non auto-commit, si può cominciare a realizzare transazioni complesse, composte da un numero generico di comandi SQL. Ovviamente, non occupandosene più la connessione in modo automatico, sarà compito del programmatore decidere quando una transazione deve essere terminata con successo oppure quando deve essere abortita. Ma non è proprio questo che si voleva?
Nel seguente spezzone di codice (assolutamente insignificante e privo di una qualsiasi utilità pratica! :-) ) si può vedere un esempio d'uso dei metodi commit() e rollback():
 
try{

// Si crea la connessione 
Connection conn = DriverManager.getConnection(...);

// Attivazione del supporto delle transazioni. 
conn.setAutoCommit(false);

// Occorre chiedere alla Connection un oggetto di tipo Statement 
// per poter eseguire dei comandi SQL.

Statement stmt = conn.createStatement();

// Esecuzione di comandi.

int updateCount = stmt.executeUpdate("UPDATE ..."); 
updateCount = stmt.executeUpdate("INSERT ..."); 
updCount = stmt.executeUpdate("DELETE ...");

if (<la transazione può essere terminata>) 
   conn.commit(); // Rende permanenti tutte le modifiche. 
else 
conn.rollback(); // Annulla tutte le modifiche.

// Da questo punto in poi può avere inizio una nuova transazione.

...

// E' buona norma chiudere le connessioni quando si è 
// terminato di utilizzarle! :)

conn.close();

}

catch (SQLException e){ 
  ... 
}


Quello che è importante sottolineare di questo esempio è che vi sono diversi statement SQL di modifica eseguiti sulla stessa Connection (o per meglio dire, su oggetti Statement di una stessa Connection) e che a seconda del risultato dell'elaborazione di tali comandi viene presa la decisione se annullare tutte le modifiche oppure se renderle permanenti. In entrambi i casi, la transazione viene terminata, quindi tutti i lock sul database (vedi articolo precedente) associati alla Connection vengono rilasciati. Ovviamente, su una Connection aperta possono essere eseguite molte transazioni in sequenza, ma occorre tenere presente che l'isolamento transazionale è a livello di Connection: processi diversi che debbano avviare transazioni concorrenti devono eseguirle su oggetti di tipo Connection distinti. 
 
 
 

I metodi setTransactionIsolation() e getTransactionIsolation()

Come spiegato nella prima parte di questo articolo, sono possibili diversi livelli di isolamento per le transazioni, ognuno dei quali permette di prevenire alcune o tutte le anomalie che possono presentarsi a seguito dell'esecuzione concorrente di più transazioni, vale a dire:
  • perdite di aggiornamenti (lost updates o anomalie write-write);
  • letture non ripetibili (non-repeatable reads o anomalie read-write);
  • letture improprie (phantom read o anomalie write-read).
Partendo da quello più basso, nel quale non si è garantiti contro alcuna delle possibili anomalie, si arriva per livelli successivi al massimo isolamento possibile, vale a dire quello in cui tutte le transazioni sono completamente isolate le une dalle altre.
Occorre ora ricordare che passando dal livello più basso di isolamento a quello più alto aumenta di conseguenza il numero di risorse che il DBMS deve impiegare per evitare le anomalie dovute alla concorrenza, ed in particolare aumenta il numero di lock che una connessione deve acquisire per portare a termine una transazione. Tutto ciò ha ripercussioni dirette sulle prestazioni e sul grado di concorrenza ottenibile da un applicazione, quindi non è strano che alcuni DBMS forniscano la possibilità di stabilire di volta in volta i livello di isolamento desiderato per una transazione.
A livello dell'API JDBC tutto questo si traduce nell'avere a disposizione due metodi (un setter e un getter) per impostare il livello di isolamento. In particolare
  • il metodo setTransactionIsolation() permette di modificare il livello di isolamento delle transazioni. Esso riceve un parametro intero che può assumere uno dei valori costanti definiti nella stessa interfaccia Connection, cioè:
  • TRANSACTION_NONE: le transazioni non vengono supportate. Passare questo parametro a setTransactionIsolation() è equivalente ad una chiamata al metodo setAutoCommit(true);
  • TRANSACTION_READ_UNCOMMITTED: nessun livello di isolamento è garantito, quindi possono presentarsi tutte le anomalie;
  • TRANSACTION_READ_COMMITTED: vengono prevenute solo le perdite di aggiornamenti. Le altre anomalie possono ancora presentarsi;
  • TRANSACTION_REPEATABLE_READ: vengono prevenute le perdite di aggiornamenti e le letture non ripetibili, mentre possono presentarsi letture improprie;
  • TRANSACTION_SERIALIZABLE: è il massimo livello di isolamento. Nessuna anomalia può presentarsi.
Come è facile immaginare, questo metodo non può essere invocato su una connessione sulla quale sia in corso una transazione.
Il metodo getTransactionIsolation() è il duale del precedente e permette di determinare il livello di isolamento transazionale della connessione. Non riceve alcun parametro e ritorna un intero corrispondente ad una delle costanti descritte sopra.
 
 

Determinare le caratteristiche del DBMS: il metodo getMetaData() e l'interfaccia DataBaseMetaData


Se da una parte l'API JDBC si muove verso la creazione di un'insieme di primitive generalizzate per l'accesso ai DBMS, dall'altra si assiste ad una proliferazione di gestori di database, ognuno con le proprie peculiarità ed estensioni proprietarie del linguaggio SQL. Inoltre si deve considerare che la maggior parte dei DBMS oggi in commercio sono nati anche molto tempo prima della diffusione di Java. Un'immediata conseguenza di ciò è che non tutte le primitive JDBC sono supportate da tutti i DBMS, ed in particolare non tutti i database supportano le transazioni e non tutti quelli che le supportano permettono l'impostazioni di tutti i livelli di isolamento previsti da JDBC.
E' quindi compito del programmatore che usa l'API JDBC verificare le effettive capacità del DBMS in uso. A tal fine l'interfaccia Connection mette a disposizione il metodo getMetaData() invocando il quale è possibile ottenere un oggetto di tipo DataBaseMetaData.
L'interfaccia DataBaseMetaData fornisce una cospicua quantità di metodi (sono davvero tanti!) attraverso i quali si possono reperire informazioni riguardanti il database: la grammatica SQL supportata, la descrizione delle tabelle, le stored procedure, ecc.
In particolare, per quanto riguarda il discorso transazioni, DataBaseMetaData dichiara due metodi:

  • supportsTransactions(): restituisce true se le transazioni sono supportate dal database, false se non lo sono. Se questo secondo caso è verificato, la documentazione afferma che il metodo commit() non esegue alcuna operazione e il livello di isolamento è sempre TRANSACTION_NONE.
  • supportsTransactionIsolationLevel(): permette di sapere se il DBMS supporta il livello di isolamento transazionale specificato come parametro (secondo le costanti definite dall'interfaccia Connection descritte sopra).

 

Il lock ottimistico

Si supponga ora di dover realizzare un'applicazione gestionale in multiutenza con accesso a database. In applicazioni di questo tipo è molto frequente il caso in cui un utente accede ad un record di una tabella del database, ne controlla il contenuto e successivamente decide se apportare delle modifiche, cancellarlo oppure aggiungere e/o cancellare record ad altre tabelle correlate. 
Forti delle conoscenze acquiste sulle API JDBC e avendo a disposizione un DBMS che lo permette, si decide di implementare le funzionalità applicative usando le transazioni al loro massimo livello di isolamento.
Affichè tutto funzioni nel modo corretto ogni interazione utente-applicazione dovrebbe essere costruita secondo il seguente schema:
  1. apertura di una transazione;
  2. accesso in lettura al record richiesto dall'utente (comando SQL SELECT);
  3. visualizzazione del contenuto del record in una form di modifica;
  4. in caso di conferma delle modifiche da parte dell'utente:
    1. si prelevano i dati modificati dalla form (o dalla griglia);
    2. si eseguono i comandi SQL di UPDATE necessari;
    3. si esegue il commit della transazione;
  5. in caso di annullamento delle modifiche oppure se qualche controllo di integrità non viene rispettato, si esegue il rollback della transazione.
Lo schema riportato è evidentemente incompleto (ad esempio non si è considerato il caso dell'inserimento e della cancellazione di un record), ma la scelta di proporlo in questo modo è motivata dalla volontà di mantenere la trattazione il più semplice possibile, focalizzandosi sui concetti più che sulle funzionalità applicative.
Ora, non vi sarebbe nulla di sbagliato, almeno dal punto di vista logico, nella scelta di progetto abbozzata: l'operatività di ogni utente risulterebbe completamente isolata da quella di tutti gli altri proprio perchè tutto il codice di accesso al database sarebbe sempre racchiuso all'interno di una transazione al massimo livello di isolamento. Si avrebbe quindi l'innegabile vantaggio di poter scrivere i programmi come se fossero delle semplici applicazioni monoutente, semplicemente ricordandosi di "aprire" e "chiudere" le transazioni al momento giusto.
In realtà, una soluzione di questo tipo non viene quasi mai impiegata perchè può avere conseguenze nefaste a tempo di esecuzione: un utente che accede ad un record di un database ne causa implicitamente il lock, e questo verrà rilasciato solo al termine della transazione.
Cosa succede se un operatore attiva una form di accesso ad un record e prima di confermare o annullare le modifiche decide che è ora di andare a fare colazione? Semplice: quel record, al quale si è acceduto dall'interno di una transazione, rimarrà bloccato e nessun altro vi potrà accedere fino a quando l'utente non deciderà di rientrare dalla pausa!
In realtà, per causare pasticci di questo tipo non è necessario aspettare l'ora di pranzo, in quanto non è raro che in un applicazione gestionale trascorra un apprezzabile lasso di tempo tra l'acquisizione di un record e la sua effettiva modifica: l'utente potrebbe aver bisogno di controllare diversi documenti cartacei prima di confermare le modifiche, oppure potrebbe essere semplicemente chiamato al telefono. Un altro caso molto importante è quello delle applicazioni con interfaccia Web, dove non è raro che un utente inizi una transazione e nel bel mezzo di essa decide che si è stufato e chiude il browser. Se a tutto questo si aggiunge che non è raro che un'applicazione multiutente di un certo rilievo debba poter servire diverse decine (se non centinaia o migliaia) di utenti contemporaneamente, si capisce bene che non è da trascurare la probabilità di avere carenze di risorse sul DBMS (con conseguente degrado delle prestazioni) oppure contesa di record tra client.
Qual'è dunque la soluzione? Sembra strano a dirsi, ma l'unico rimedio consiste nell'evitare l'uso delle transazioni, o meglio, nel cercare di mantenerle aperte per il più breve intervallo di tempo possibile. A tal fine si adotta la tecnica del lock ottimistico, la quale si basa sull'assunto che, se si considera il rapporto tra numero totale di accessi al database e numero totale di anomalie, queste ultime non si presentano poi così di frequente.
Il principio di funzionamento del lock ottimistico può essere compreso osservando le modifiche che è necessario applicare allo schema di funzionalità abbozzato sopra, il quale si trasforma in questo modo:
  1. si accede in lettura al record richiesto dall'utente (comando SQL SELECT);
  2. si mantiene una copia di riserva del contenuto originale del record letto;
  3. si visualizza il contenuto del record in una form di modifica;
  4. in caso di conferma delle modifiche da parte dell'utente:
    1. si apre una transazione;
    2. si rilegge il record da modificare;
    3. se il contenuto del record letto coincide con quello della copia originale salvata in precedenza, allora si può procedere all'aggiornamento, quindi:
      1. si prelevano i dati modificati dalla form;
      2. si eseguono i comandi SQL di UPDATE necessari;
      3. si esegue il commit della transazione;
    4. se il record non esiste più sul database o se differisce in qualche modo da quello originale, ciò significa che si è verificata una anomalia, la transazione deve essere abortita e deve essere fornita una segnalazione all'utente;
  5. in caso di annullamento delle modifiche non si deve fare nulla;
Come si può vedere, nel nuovo schema applicativo le transazioni non vengono aperte al momento della lettura dei record, ma solo quando giunge il momento di modificarli o cancellarli: non dovendo attendere le inevitabili pause di consultazione dei record da parte dell'utente, ogni transazione rimarrà attiva per il solo tempo necessario all'effettivo svolgimento delle operazioni di modifica sul database. 
Ora l'utente è libero di andarsene a pranzo senza preoccuparsi (se mai lo fosse stato!) di bloccare il lavoro di altri: al suo ritorno la copia del record eventualmente visualizzata sul suo client potrebbe non essere più allineata con quella presente sul database perchè altri potrebbero averlo nel frattempo modificato e se tentasse di modificarlo e di salvarlo, riceverebbe un errore da parte dell'applicazione.
Tutto questo ovviamente si paga! Si è infatti costretti a mantenere una copia del contenuto originale del record. Inoltre, ogni transazione necessita sempre di due letture dello stesso record (la prima per acquisirlo, la seconda per il controllo delle eventuali anomalie). Vi è comunque da considerare che, proprio grazie al fatto che è l'applicazione ad eseguire i controlli necessari ad evitarle, le anomalie read-write e write-read non devono essere gestite dal database, quindi si può (anzi, si deve!) abbassare il livello di isolamento transazionale del database, portandolo quindi a READ_COMMITED (quando possibile). In caso contrario dall'impiego del lock ottimistico non si avrebbe alcun guadagno in termini di prestazioni e livello di concorrenza, ma anzi, si avrebbe un degrado prestazionale dovuto alle doppie lettura necessarie al completamento di ogni transazione.
 
 
 

La tecnica del timestamp

Come si è detto, il principio sul quale si fonda il lock ottimistico consiste nell'assicurarsi, prima dell'effettivo aggiornamento, che il record posseduto dall'applicazione non sia stato modificato sul database in un momento successivo alla sua acquisizione. Se questo controllo dovesse essere fatto tutte le volte su ogni campo che compone un record, l'overhead sarebbe veramente inaccettabile, tantopiù in presenza di campi contenenti dei large object. Per ovviare a tale inconveniente, si adotta la tecnica del timestamp, la quale consiste nell'inserire in ogni record un campo aggiuntivo di tipo timestamp, il cui unico scopo sia quello di mantenere traccia dell'istante in cui è avvenuto l'ultimo aggiornamento di un record.
In questo modo, se ad ogni aggiornamento di un record sul database corrisponde anche l'aggiornamento del corripondente campo timestamp, si avrà a disposizione una marcatura sulla quale basarsi implementare il lock ottimistico. Ecco allora la versione definitiva dello schema di funzionalità applicative che implementa il lock ottimistico con la tecnica del timestamp:
  1. si accede in lettura al record richiesto dall'utente (comando SQL SELECT);
  2. si mantiene una copia del timestamp associato al record letto;
  3. si visualizza il contenuto del record in una form di modifica;
  4. in caso di conferma delle modifiche da parte dell'utente:
    1. si apre una transazione;
    2. si rilegge il timestamp del record da modificare;
    3. se il timestamp letto coincide con la copia originale salvata in precedenza, allora si può procedere all'aggiornamento, quindi:
      1. si prelevano i dati modificati dalla form;
      2. si eseguono i comandi SQL di UPDATE necessari;
      3. si esegue il commit della transazione;
    4. se il record non esiste più sul database o se il suo timestamp differisce da quello originale, ciò significa che si è verificata una anomalia, la transazione deve essere abortita e deve essere fornita una segnalazione all'utente;
  5. in caso di annullamento delle modifiche non si deve fare nulla;

 

Conclusioni

Mi pare di poter affermare che l'API JDBC sia davvero molto semplice da utilizzare, ma questa semplicità non deve trarre in inganno: realizzare applicazioni robuste significa adottare opportuni accorgimenti in fase di design che vanno al di là del semplice impiego di una libreria di classi.
La gestione delle transazioni è uno di quegli argomenti che non può certo esaurirsi nella semplice elencazione delle classi e dei metodi che Java mette a disposizione per utilizzarle. In questo articolo si è quindi cercato di fornire anche una breve panoramica dei problemi che devono essere considerati ed affrontati quando si deve scrivere un'applicazione con accesso a database che debba supportare la multiutenza. 
In realtà la questione è ancora più complessa, in quanto vi è un intrinseca incompatibilità tra mondo ad oggetti e mondo relazionale la quale, per essere compensata, richiede un non trascurabile sforzo di analisi e design. Object-relational mapping, Enterprise Java Beans sono nomi che per alcuni possono non essere famigliari e che purtroppo non possono essere spiegati in un solo articolo, ma costituiscono un chiaro segnale che per ottenere applicazioni robuste e scalabili occorre molto di più di una semplice (ma potente!) API per l'accesso ai database.
 
 
 

APPENDICE - La creazione delle connessioni JDBC

In questa appendice dell'articolo si entrerà nei dettagli relativi alla creazione di una connessione JDBC, in quanto tale argomento può essere utile alla comprensione del supporto delle transazioni, il quale viene fornito da JDBC proprio a livello delle connessioni. 

I driver JDBC

L'API JDBC, racchiusa nel package java.sql.*, fornisce un'interfaccia unificata per l'accesso ad un qualunque database, "mascherando" le peculiarità di ogni singolo DBMS introducendo il concetto di driver. Per capire meglio cosa questo significhi basta dare un'occhiata alla documentazione del pacchetto java.sql: ci si accorge immediatamente che la maggior parte delle funzionalità vengono fornite tramite interfacce (cioè tipi puramente astratti), mentre le classi vere e proprie sono davvero poche. Cosa implica tutto ciò? Semplicemente, l'API JDBC si limita in gran parte a dichiarare le funzionalità che un'interfaccia generalizzata per l'accesso ad un database dovrebbe avere, delegando l'implementazione delle interfacce ai driver che devono fornire l'accesso ai singoli DBMS. Quindi, compito di un driver JDBC è fornire un insieme di classi che implementino tutte le interfacce dichiarate nel pacchetto java.sql.

Ora, si supponga che una certa classe Java debba accedere ad un database la cui istanza è chiamata MioDb gestito dal DBMS IperDBdella software house PincoPallo. Per poter eseguire una qualunque operazione su un database occorre per prima cosa ottenere una connessione ad esso: in termini JDBC ciò equivale a richiedere alla classe java.sql.DriverManager un oggetto istanza di una classe che implementa l'interfaccia java.sql.Connection
Ad esempio si potrebbe scrivere:
 

import java.sql.*;

public MiaClasse{

public static void main(String[] args){
 ....
 // Richiede una connessione al database MioDb
 Connection conn = DriverManager.getConnection(...);
 ....
 }
}


Come si può notare, il codice manca di alcune parti fondamentali in quanto non è ancora chiaro come possa la classe java.sql.DriverManager determinare il particolare driver JDBC al quale richiedere un oggetto di tipo java.sql.Connection utile per l'accesso al database MioDb.
Da quanto detto in precedenza, dovrebbe essere chiaro che la ditta PincoPallo, per supportare la connettività JDBC, dovrà fornire un insieme di classi che implementano le interfacce dichiarate dall'API JDBC, ponendole in un package appropriato (ad esempio com.pincopallo.iperdb) e distribuendole in un archivio JAR (ad esempio iperdb.jar). Tra tutte le classi presenti nell'archivio iperdb.jar ve ne sarà una in particolare che effettivamente implementa il concetto di driver JDBC: supponiamo che il suo nome completo sia com.pincopallo.IperDBDriver.
 
 

La registrazione dei driver JDBC

Le specifiche JDBC redatte dalla Sun prevedono che, per poter essere utilizzato, un driver JDBC deve essere caricato in memoria, e una volta che ciò è avvenuto, il driver stesso ha il compito di registrarsi presso il Driver Manager (d'ora in avanti solamente DM), il quale ha quindi il compito di tenere traccia dei driver JDBC disponibili, in modo da poter costruire correttamente le istanze delle classi che implementano l'interfaccia java.sql.Connection quando queste vengono richieste dalle applicazioni. 
Dunque, ammesso che il percorso in cui è presente l'archivio iperdb.jar sia accessibile dal CLASSPATH, il codice introdotto sopra diventa:
 

public MiaClasse{

public static void main(String[] args){

 // Carica in memoria il driver JDBC per l'accesso al DBMS
 // IperDB (in questo modo il driver si registra al DM). 
 class.forName("com.pincopallo.IperDBDriver"); 
 // Richiede al DM una connessione al database MioDb

 Connection conn = DriverManager.getConnection(...);
 ... 
 } 
}


Cosa accade quando un driver JDBC si registra al DM? Il driver fornisce un elenco di identificatori (chiamati subprotocol) che il DM dovrà riconoscere come alias per l'accesso del driver stesso. Nell'esempio in esame, il driver JDBC com.pincopallo.IperDBDriver potrebbe registrarsi indicando come subprotocol la chiave "iperdb".
 
 
 

Gli URL JDBC

Le chiavi subprotocol registrate dai driver vengono utilizzate all'interno degli URL JDBC, passati dalle applicazioni al DM all'atto della richiesta di una connessione. Un JDBC URL deve rispettare la seguente sintassi:
 
jdbc:<subprotocol>:<subname>


nella quale i tre campi hanno il seguente significato:

  • jdbc è una parte fissa che identifica un URL JDBC;
  • <subprotocol> è una chiave tra quelle registrate dai driver JDBC presso il DM;
  • <subname> è una parte variabile, il cui scopo è fornire al driver corrispondente al subprotocol sufficienti informazioni per localizzare il database. Driver diversi possono richiedere sintassi diverse in questo campo.
Nell'esempio che si sta proponendo, il driver della PincoPallo potrebbe richiedere una sintassi del <subname> di questo tipo:
 
//<host>:<port>/<dbname>


dove <host>:<port> rappresenta il socket TCP/IP presso il quale il DBMS è in attesa di richieste di connessioni e <dbname> è il nome dell'istanza del dabatase al quale ci si vuole connettere. Quindi, supponendo che il DBMS IperDb sia in attesa sul socket 10.2.0.1:7654 e ricordando che l'istanza del database si chiama MioDb, il codice 
 
 
 

import java.sql.*;

public MiaClasse{ 
 public static void main(String[] args){

 // Carica in memoria il driver JDBC per l'accesso al DBMS 
 // IperDB (in questo modo il driver si registra al DM). 
 class.forName("com.pincopallo.IperDBDriver"); 
 // Richiede una connessione al database MioDb 
 Connection conn = 
   DriverManager.getConnection("jdbc:iperdb://10.2.0.1:7654/MioDb");

 // Da questo punto in poi si possono compiere tutte le 
 // operazioni dul database. 
 Statement stmt = conn.createStatement(); 
 // Esecuzione di comandi di selezione. 
 ResultSet rst = stmt.executeQuery("SELECT * FROM ..."); 
 // Elaborazione del ResultSet. 
 while (rst.next()){ 
  ... 
 } 
 // Esecuzione di comandi di modifica. 
 int updCount = stmt.executeUpdate("UPDATE ..."); 
 updCount = stmt.executeUpdate("INSERT ..."); 
 updCount = stmt.executeUpdate("DELETE ..."); 
 ... 
 // E' buona norma chiudere le connessioni quando non servono più. 
 conn.close(); 
 } 
}


Ora dovrebbe essere chiaro cosa succede quando viene creata una connessione JDBC.
E' importante sottolineare ancora una volta che, mentre java.sql.Connection è un'interfaccia che fornisce un insieme di servizi predefiniti da JDBC, l'implementazione effettiva di tali servizi è diversa per ciascun DBMS, e quindi rimane a carico dei driver dei DBMS stessi.


  
 

MokaByte rivista web su Java

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