6. Gestione dei database con API JDBC
6.1. Informazioni generali
Sul mercato sono disponibili numerose banche dati. Al fine di uniformare l'accesso ai database in ambiente Windows, Microsoft ha sviluppato un'interfaccia denominata ODBC (Open DataBase Connectivity). Questo livello nasconde le specificità di ciascun database dietro un'interfaccia standard. In ambiente Windows sono disponibili numerosi driver che facilitano l’accesso ai database. Ecco, ad esempio, un elenco di driver installati su un computer con Win95:

Un’applicazione che si avvale di questi driver può utilizzare qualsiasi database tra quelli sopra elencati senza necessità di riscrittura.

Affinché anche le applicazioni Java possano sfruttare l’interfaccia ODBC, Sun ha creato l’interfaccia JDBC (Java DataBase Connectivity) che si interporrà tra l’applicazione Java e l’interfaccia ODBC:

6.2. Le fasi fondamentali nella gestione dei database
6.2.1. Introduzione
In un’applicazione JAVA che utilizza un database con l’interfaccia JDBC, si riscontrano generalmente le seguenti fasi:
- Connessione al database
- Invio di query SQL al database
- Ricezione ed elaborazione dei risultati di tali query
- Chiusura della connessione
Le fasi 2 e 3 vengono eseguite ripetutamente, mentre la chiusura della connessione avviene solo al termine dell’utilizzo del database. Si tratta di uno schema relativamente classico, con cui potreste avere familiarità se avete già utilizzato un database in modo interattivo. Illustreremo in dettaglio ciascuna di queste fasi con un esempio. Consideriamo un database ACCESS denominato ARTICLES e con la seguente struttura:
nome | tipo |
code | codice articolo di 4 caratteri |
nom | nome (stringa di caratteri) |
prix | il suo prezzo (effettivo) |
stock_actu | la sua disponibilità attuale (numero intero) |
stock_mini | la quantità minima (numero intero) al di sotto della quale è necessario rifornire l’articolo |
Questo database ACCESS è definito come fonte dati «utente» nel gestore dei database ODBC:


Le sue caratteristiche sono specificate tramite il pulsante Configurer come segue:

Questa configurazione consiste essenzialmente nell’associare al database ARTICLES il file Access articles.mdb corrispondente a tale database. Una volta fatto ciò, il database ARTICLES è accessibile alle applicazioni che utilizzano l’interfaccia ODBC.
6.2.2. La fase di connessione
Per utilizzare un database, un’applicazione Java deve prima eseguire una fase di connessione. Ciò avviene tramite il seguente metodo di classe:
con
DriverManager: classe Java contenente l’elenco dei driver disponibili per l’applicazione
Connection: classe Java che stabilisce un collegamento tra l’applicazione e il database, grazie al quale l’applicazione trasmetterà le query SQL al database e riceverà i risultati
URL: nome che identifica il database. Questo nome è analogo ai URL di Internet. Per questo motivo fa parte della classe URL. Tuttavia, Internet non interviene in alcun modo in questo contesto. Il nome URL ha la seguente forma:
jdbc:nom_du_pilote:nom_de_la_source;param=val1;param2=val2
Nei nostri esempi, in cui utilizzeremo esclusivamente driver ODBC, il driver si chiama odbc. La terza parte del URL è costituita dal nome della fonte con eventuali parametri. Nei nostri esempi, si tratterà di fonti ODBC già note al sistema. Pertanto, l’URL della fonte di dati Articles definita in precedenza sarà
jdbc:odbc:Articoli
id: nome utente (login)
mdp: password dell'utente (password)
In sintesi, il programma si connette a un database:
- identificato da un nome (URL)
- con le credenziali di un utente (ID, password)
Se questi tre parametri sono corretti e se esiste il driver in grado di garantire la connessione dell’applicazione Java al database specificato, allora viene stabilita una connessione tra l’applicazione Java e il database. Questa connessione si concretizza per il programma nell’oggetto di tipo Connection restituito dalla classe DriverManager. Poiché questa connessione può fallire per vari motivi, è possibile che generi un’eccezione. Si scriverà quindi:
Connection connexion=null;
URL base=...;
String id=...;
String mdp=...;
try{
connexion=DriverManager.getConnection(base,id,mdp);
} catch (Exception e){
// gestire l'eccezione
}
Per poter effettuare la connessione a un database, è necessario disporre del driver adeguato. Nei nostri esempi, si tratterà del driver ODBC in grado di gestire il database richiesto. Se questo driver deve essere presente nell’elenco dei driver ODBC disponibili sul computer, è necessario disporre anche della classe JAVA che fungerà da interfaccia con esso. A tal fine, l’applicazione richiederà la classe necessaria nel modo seguente:
La classe Class non è in alcun modo collegata all’interfaccia JDBC. Si tratta di una classe generica per la gestione delle classi. Il suo metodo statico forName consente di caricare dinamicamente una classe e quindi di usufruire dei suoi attributi e metodi statici. La classe che funge da interfaccia con i driver ODBC di MS per Windows si chiama «sun.jdbc.odbc.JdbcOdbcDriver». Si scriverà quindi (il metodo può generare un'eccezione):
try{
Class.forName(« sun.jdbc.odbc.JdbcOdbcDriver ») ;
} catch (Exception e){
// gestire l'eccezione (classe inesistente)
}
Le classi necessarie per l'interfaccia JDBC si trovano nel pacchetto java.sql. All'inizio del programma si scriverà quindi:
Ecco un programma che consente di connettersi a un database:
import java.sql.*;
import java.io.*;
// chiamata: pg PILOTE URL UID MDP
// si connette al database URL tramite la classe JDBC PILOTE
// l'utente UID viene identificato tramite la password MDP
public class connexion1{
static String syntaxe="pg PILOTE URL UID MDP";
public static void main(String arg[]){
// verifica del numero di argomenti
if(arg.length<2 || arg.length>4)
erreur(syntaxe,1);
// connessione al database
Connection connect=null;
String uid="";
String mdp="";
if(arg.length>=3) uid=arg[2];
if(arg.length==4) mdp=arg[3];
try{
Class.forName(arg[0]);
connect=DriverManager.getConnection(arg[1],uid,mdp);
System.out.println("Connexion avec la base " + arg[1] + " établie");
} catch (Exception e){
erreur("Erreur " + e,2);
}
// chiusura del database
try{
connect.close();
System.out.println("Base " + arg[1] + " fermée");
} catch (Exception e){}
}// main
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// classe
Ecco un esempio di esecuzione:
E:\data\java\jdbc\0>java connexion1 sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles
Connexion avec la base jdbc:odbc:articles établie
Base jdbc:odbc:articles fermée
6.2.3. Invio di richieste al database
L'interfaccia JDBC consente di inviare query SQL al database collegato all'applicazione Java, nonché di elaborare i risultati di tali query. Il linguaggio SQL (Structured Query Language) è un linguaggio di query standardizzato per i database relazionali. Si distinguono diversi tipi di query:
- le query di interrogazione del database (SELECT)
- le query di aggiornamento del database (INSERT, DELETE, UPDATE)
- le query di creazione/eliminazione di tabelle (CREATE, DELETE)
Si presume che il lettore conosca le nozioni di base del linguaggio SQL.
6.2.3.1. La classe Statement
Per inviare una query SQL, di qualsiasi tipo, a un database, l’applicazione Java deve disporre di un oggetto di tipo Statement. Questo oggetto memorizzerà, tra le altre cose, il testo della query. Tale oggetto è necessariamente associato alla connessione corrente. È quindi un metodo della connessione stabilita che consente di creare gli oggetti Statement necessari per l’invio delle query SQL. Se connexion è l’oggetto che rappresenta la connessione al database, un oggetto Statement si ottiene nel modo seguente:
Una volta ottenuto un oggetto Statement, è possibile inviare richieste SQL. La procedura varia a seconda che la richiesta sia di interrogazione o di aggiornamento del database.
6.2.3.2. Inviare una richiesta di interrogazione del database
Una query di interrogazione è tipicamente una query del tipo:
Solo le parole chiave della prima riga sono obbligatorie, le altre sono facoltative. Esistono altre parole chiave non riportate qui.
- Viene eseguito un join con tutte le tabelle che seguono la parola chiave from
- Vengono mantenute solo le colonne che seguono la parola chiave `select`
- Vengono mantenute solo le righe che soddisfano la condizione specificata dalla parola chiave `where`
- Le righe risultanti, ordinate secondo l’espressione della parola chiave `order by`, costituiscono il risultato della query.
Il risultato di una query select è una tabella. Se si considera la tabella ARTICLES precedente e si desiderano i nomi degli articoli il cui stock attuale è inferiore alla soglia minima, si scriverà: select nome from articoli where stock_actu<stock_mini. Se si desidera ordinarli in ordine alfabetico in base ai nomi, si scriverà: select nome from articoli where stock_actu<stock_mini order by nome
Per eseguire questo tipo di query, la classe Statement offre il metodo executeQuery:
dove requête è il testo della query SELECT da inviare.
Pertanto, se
- connexion è l’oggetto che rappresenta la connessione al database
- Statement s=connexion.createStatement() crea l’oggetto Statement necessario per l’invio delle query SQL
- ResultSet rs=s.executeQuery(" select nome da articoli dove stock_actu<stock_mini”) esegue una query select e assegna le righe risultanti dalla query a un oggetto di tipo ResultSet.
6.2.3.3. La classe ResultSet: risultato di una query SELECT
Un oggetto di tipo ResultSet rappresenta una tabella, ovvero un insieme di righe e colonne. In un dato momento, si ha accesso solo a una riga della tabella, denominata riga corrente. Al momento della creazione iniziale del ResultSet, la riga corrente è la riga n. 1 se il ResultSet non è vuoto. Per passare alla riga successiva, la classe ResultSet dispone del metodo next:
Questo metodo tenta di passare alla riga successiva di ResultSet e restituisce true in caso di successo, false in caso contrario. In caso di successo, la riga successiva diventa la nuova riga corrente. La riga precedente viene persa e non sarà possibile tornare indietro per recuperarla. La tabella di ResultSet presenta le colonne col1, col2,.... Per utilizzare i diversi campi della riga corrente, sono disponibili i seguenti metodi:
per ottenere il campo «coli» della riga corrente. Type indica il tipo del campo coli. Si utilizza abbastanza spesso il metodo getString su tutti i campi, il che consente di ottenere il contenuto del campo come stringa di caratteri. Successivamente, se necessario, si procede alla conversione. Se non si conosce il nome della colonna, è possibile utilizzare i metodi
dove i è l’indice della colonna desiderata (i>=1).
6.2.3.4. Un primo esempio
Ecco un programma che visualizza il contenuto del database ARTICLES creato in precedenza:
import java.sql.*;
import java.io.*;
// visualizza il contenuto di un database di sistema ARTICLES
public class articles1{
static final String DB="ARTICLES"; // database da utilizzare
public static void main(String arg[]){
Connection connect=null; // connessione al database
Statement S=null; // oggetto di invio delle query
ResultSet RS=null; // tabella dei risultati di una query
try{
// connessione al database
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
System.out.println("Connexion avec la base " + DB + " établie");
// creazione di un oggetto Statement
S=connect.createStatement();
// esecuzione di una query SELECT
RS=S.executeQuery("select * from " + DB);
// elaborazione della tabella dei risultati
while(RS.next()){ // finché c'è una riga da elaborare
// la si visualizza sullo schermo
System.out.println(RS.getString("code")+","+
RS.getString("nom")+","+
RS.getString("prix")+","+
RS.getString("stock_actu")+","+
RS.getString("stock_mini"));
}// riga successiva
} catch (Exception e){
erreur("Erreur " + e,2);
}
// chiusura del database
try{
connect.close();
System.out.println("Base " + DB + " fermée");
} catch (Exception e){}
}// mano
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// classe
I risultati ottenuti sono i seguenti:
Connexion avec la base ARTICLES établie
a300,vélo,1202,30,2
d600,arc,5000,10,2
d800,canoé,1502,12,6
x123,fusil,3000,10,2
s345,skis nautiques,1800,3,2
f450,essai3,3,3,3
f807,cachalot,200000,0,0
z400,léopard,500000,1,1
g457,panthère,800000,1,1
Base ARTICLES fermée
6.2.3.5. La classe ResultSetMetadata
Nell'esempio precedente, conosciamo i nomi delle colonne del ResultSet. Se non li conosciamo, non possiamo utilizzare il metodo getType(nom_colonne). Si utilizzerà invece getType(n. colonna). Tuttavia, per ottenere tutte le colonne, dovremmo sapere quante colonne ha il ResultSet ottenuto. La classe ResultSet non ci fornisce questa informazione. È la classe ResultSetMetaData a fornircela. Più in generale, questa classe ci fornisce informazioni sulla struttura della tabella, ovvero sulla natura delle sue colonne.
È possibile accedere alle informazioni sulla struttura di un ResultSet istanziando innanzitutto un oggetto ResultSetMetaData. Se RS è un ResultSet, il ResultSetMetaData associato si ottiene tramite:
Si notino due metodi utili nella classe ResultSetMetaData:
- int getColumnCount(), che restituisce il numero di colonne del ResultSet
- String getColumnLabel(int i) che restituisce il nome della colonna i di ResultSet (i>=1)
6.2.3.6. Un secondo esempio
Il programma precedente visualizzava il contenuto del database ARTICLES. Qui scriviamo un programma che esegue sul database ARTICLES qualsiasi query SQL Select digitata dall’utente sulla tastiera.
import java.sql.*;
import java.io.*;
// visualizza il contenuto di una base di sistema ARTICLES
public class sql1{
static final String DB="ARTICLES"; // database da analizzare
public static void main(String arg[]){
Connection connect=null; // connessione al database
Statement S=null; // oggetto di invio delle query
ResultSet RS=null; // tabella dei risultati di una query
String select; // testo della query SQL select
int nbColonnes; // numero di colonne del ResultSet
// creazione di un flusso di input da tastiera
BufferedReader in=null;
try{
in=new BufferedReader(new InputStreamReader(System.in));
} catch(Exception e){
erreur("erreur lors de l'ouverture du flux clavier ("+e+")",3);
}
try{
// connessione al database
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
System.out.println("Connexion avec la base " + DB + " établie");
// creazione di un oggetto Statement
S=connect.createStatement();
// ciclo di esecuzione delle query SQL digitate dalla tastiera
System.out.print("Requête : ");
select=in.readLine();
while(!select.equals("fin")){
// esecuzione della query
RS=S.executeQuery(select);
// numero di colonne
nbColonnes=RS.getMetaData().getColumnCount();
// elaborazione della tabella dei risultati
System.out.println("Résultats obtenus\n\n");
while(RS.next()){ // finché c'è una riga da elaborare
// la si visualizza sullo schermo
for(int i=1;i<nbColonnes;i++)
System.out.print(RS.getString(i)+",");
System.out.println(RS.getString(nbColonnes));
}// riga successiva
// richiesta successiva
System.out.print("Requête : ");
select=in.readLine();
}// while
} catch (Exception e){
erreur("Erreur " + e,2);
}
// chiusura del database e del flusso di input
try{
connect.close();
System.out.println("Base " + DB + " fermée");
in.close();
} catch (Exception e){}
}// main
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// classe
Ecco alcuni risultati ottenuti:
Connexion avec la base ARTICLES établie
Requête : select * from articles order by prix desc
Résultats obtenus
g457,panthère,800000,1,1
z400,léopard,500000,1,1
f807,cachalot,200000,0,0
d600,arc,5000,10,2
x123,fusil,3000,10,2
s345,skis nautiques,1800,3,2
d800,canoé,1502,12,6
a300,vélo,1202,30,2
f450,essai3,3,3,3
Requête : select nom, prix from articles where prix >10000 order by prix desc
Résultats obtenus
panthère,800000
léopard,500000
cachalot,200000
6.2.3.7. Inviare una richiesta di aggiornamento del database
Un oggetto di tipo Statement consente di memorizzare le richieste SQL. Il metodo utilizzato da questo oggetto per inviare richieste di aggiornamento SQL (INSERT, UPDATE, DELETE) non è più il metodo executeQuery esaminato in precedenza, bensì il metodo executeUpdate:
La differenza sta nel risultato: mentre executeQuery restituiva la tabella dei risultati (ResultSet), executeUpdate restituisce il numero di righe interessate dall’operazione di aggiornamento.
6.2.3.8. Un terzo esempio
Riprendiamo il programma precedente e lo modifichiamo leggermente: le query digitate dalla tastiera sono ora query di aggiornamento del database ARTICLES.
import java.sql.*;
import java.io.*;
// visualizza il contenuto di un database di sistema ARTICLES
public class sql2{
static final String DB="ARTICLES"; // database da utilizzare
public static void main(String arg[]){
Connection connect=null; // connessione al database
Statement S=null; // oggetto di invio delle query
ResultSet RS=null; // tabella dei risultati di una query
String sqlUpdate; // testo della query SQL di aggiornamento
int nbLignes; // numero di righe interessate da un aggiornamento
// creazione di un flusso di input da tastiera
BufferedReader in=null;
try{
in=new BufferedReader(new InputStreamReader(System.in));
} catch(Exception e){
erreur("erreur lors de l'ouverture du flux clavier ("+e+")",3);
}
try{
// connessione al database
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
System.out.println("Connexion avec la base " + DB + " établie");
// creazione di un oggetto Statement
S=connect.createStatement();
// ciclo di esecuzione delle query SQL digitate da tastiera
System.out.print("Requête : ");
sqlUpdate=in.readLine();
while(!sqlUpdate.equals("fin")){
// esecuzione della query
nbLignes=S.executeUpdate(sqlUpdate);
// monitoraggio
System.out.println(nbLignes + " ligne(s) ont été mises à jour");
// richiesta successiva
System.out.print("Requête : ");
sqlUpdate=in.readLine();
}// while
} catch (Exception e){
erreur("Erreur " + e,2);
}
// chiusura del database e del flusso di input
try{
// si liberano le risorse associate al database
RS.close();
S.close();
connect.close();
System.out.println("Base " + DB + " fermée");
// chiusura flusso tastiera
in.close();
} catch (Exception e){}
}// mano
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// classe
Ecco il risultato di diverse esecuzioni dei programmi sql1 e sql2:
Elenco delle righe del database ARTICLES:
E:\data\java\jdbc\0>java sql1
Connexion avec la base ARTICLES établie
Requête : select nom,stock_actu from articles
Résultats obtenus
vélo,30
arc,10
canoé,12
fusil,10
skis nautiques,3
essai3,3
cachalot,0
léopard,1
panthère,1
Si modificano alcune righe:
E:\data\java\jdbc\0>java sql2
Connexion avec la base ARTICLES établie
Requête : update articles set stock_actu=stock_actu+1 where stock_actu>10
2 ligne(s) ont été mises à jour
Verifica:
E:\data\java\jdbc\0>java sql1
Connexion avec la base ARTICLES établie
Requête : select nom,stock_actu from articles
Résultats obtenus
vélo,31
arc,10
canoé,13
fusil,10
skis nautiques,3
essai3,3
cachalot,0
léopard,1
panthère,1
Si aggiunge una riga:
E:\data\java\jdbc\0>java sql2
Connexion avec la base ARTICLES établie
Requête : insert into articles (code,nom,prix,stock_actu,stock_mini) values ('x400','nouveau',200,20,10)
1 ligne(s) ont été mises à jour
Verifica:
E:\data\java\jdbc\0>java sql1
Connexion avec la base ARTICLES établie
Requ_te : select nom,stock_actu from articles
Résultats obtenus
vélo,31
arc,10
canoé,13
fusil,10
skis nautiques,3
essai3,3
cachalot,0
léopard,1
panthère,1
nouveau,20
Si elimina una riga:
E:\data\java\jdbc\0>java sql2
Connexion avec la base ARTICLES établie
Requête : delete from articles where code='x400'
1 ligne(s) ont été mises à jour
Requête : fin
Verifica:
E:\data\java\jdbc\0>java sql1
Connexion avec la base ARTICLES établie
Requête : select nom,stock_actu from articles
Résultats obtenus
vélo,31
arc,10
cano_,13
fusil,10
skis nautiques,3
essai3,3
cachalot,0
léopard,1
panthère,1
6.2.3.9. Invia una richiesta SQL qualsiasi
L'oggetto Statement, necessario per l'invio delle richieste SQL, dispone di un metodo execute in grado di eseguire qualsiasi tipo di richiesta SQL:
Il risultato restituito è il valore booleano true se la richiesta ha restituito un ResultSet (executeQuery), false se ha restituito un numero (executeUpdate). Il valore ResultSet ottenuto può essere recuperato con il metodo getResultSet e il numero di righe aggiornate tramite il metodo getUpdateCount. Pertanto si scriverà:
Statement S=...;
ResultSet RS=...;
int nbLignes;
String requête=...;
// esecuzione di una query SQL
if (S.execute(requête)){
// si ottiene un resultset
RS=S.getResultSet();
// elaborazione del ResultSet
...
} else {
// si trattava di una query di aggiornamento
nbLignes=S.getUpdateCount();
...
}
6.2.3.10. Quarto esempio
Riprendiamo lo spirito dei programmi sql1 e sql2 in un programma sql3, ora in grado di eseguire qualsiasi query SQL digitata dalla tastiera. Per rendere il programma più generico, le caratteristiche del database da utilizzare vengono passate come parametri al programma.
import java.sql.*;
import java.io.*;
// chiamata: pg PILOTE URL UID MDP
// si connette al database URL tramite la classe JDBC PILOTE
// l'utente UID viene identificato tramite la password MDP
public class sql3{
static String syntaxe="pg PILOTE URL UID MDP";
public static void main(String arg[]){
// verifica del numero di argomenti
if(arg.length<2 || arg.length>4)
erreur(syntaxe,1);
// inizializzazione dei parametri di connessione
Connection connect=null;
String uid="";
String mdp="";
if(arg.length>=3) uid=arg[2];
if(arg.length==4) mdp=arg[3];
// altri dati
Statement S=null; // oggetto di invio delle richieste
ResultSet RS=null; // tabella dei risultati di una query di interrogazione
String sqlText; // testo della query SQL da eseguire
int nbLignes; // numero di righe interessate da un aggiornamento
int nbColonnes; // numero di colonne di un ResultSet
// creazione di un flusso di input da tastiera
BufferedReader in=null;
try{
in=new BufferedReader(new InputStreamReader(System.in));
} catch(Exception e){
erreur("erreur lors de l'ouverture du flux clavier ("+e+")",3);
}
try{
// connessione al database
Class.forName(arg[0]);
connect=DriverManager.getConnection(arg[1],uid,mdp);
System.out.println("Connexion avec la base " + arg[1] + " établie");
// creazione di un oggetto Statement
S=connect.createStatement();
// ciclo di esecuzione delle query SQL digitate dalla tastiera
System.out.print("Requête : ");
sqlText=in.readLine();
while(!sqlText.equals("fin")){
// esecuzione della query
try{
if(S.execute(sqlText)){
// si è ottenuto un ResultSet - lo si analizza
RS=S.getResultSet();
// numero di colonne
nbColonnes=RS.getMetaData().getColumnCount();
// elaborazione della tabella dei risultati
System.out.println("\nRésultats obtenus\n-----------------\n");
while(RS.next()){ // finché c'è una riga da elaborare
// viene visualizzata sullo schermo
for(int i=1;i<nbColonnes;i++)
System.out.print(RS.getString(i)+",");
System.out.println(RS.getString(nbColonnes));
}// riga successiva di ResultSet
} else {
// si trattava di una richiesta di aggiornamento
nbLignes=S.getUpdateCount();
// seguito
System.out.println(nbLignes + " ligne(s) ont été mises à jour");
}//if
} catch (Exception e){
System.out.println("Erreur " +e);
}
// richiesta successiva
System.out.print("\nNouvelle Requête : ");
sqlText=in.readLine();
}// while
} catch (Exception e){
erreur("Erreur " + e,2);
}
// chiusura del database e del flusso di input
try{
// si liberano le risorse associate al database
RS.close();
S.close();
connect.close();
System.out.println("Base " + arg[1] + " fermée");
// chiusura del flusso da tastiera
in.close();
} catch (Exception e){}
}// main
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// classe
Si crea il seguente file di query:
select * from articles
update articles set stock_mini=stock_mini+5 where stock_mini<5
select nom,stock_mini from articles
insert into articles (code,nom,prix,stock_actu,stock_mini) values ('x400','nouveau',100,20,10)
select * from articles
delete from articles where code='x400'
select * from articles
fin
Il programma viene avviato nel modo seguente:
Il programma legge quindi i dati di input dal file requetes e scrive i dati di output nel file results. I risultati ottenuti sono i seguenti:
Connexion avec la base jdbc:odbc:articles établie
Requête : (requete 1 du fichier des requetes : select * from articles)
Résultats obtenus
-----------------
a300,vélo,1202,31,3
d600,arc,5000,10,3
d800,canoé,1502,13,7
x123,fusil,3000,10,3
s345,skis nautiques,1800,3,3
f450,essai3,3,3,4
f807,cachalot,200000,0,1
z400,léopard,500000,1,2
g457,panthère,800000,1,2
Nouvelle Requête : (requete 2 du fichier des requetes : update articles set stock_mini=stock_mini+5 where stock_mini<5)
8 ligne(s) ont été mises à jour
Nouvelle Requête : (requete 3 du fichier des requetes : select nom,stock_mini from articles)
Résultats obtenus
-----------------
vélo,8
arc,8
canoé,7
fusil,8
skis nautiques,8
essai3,9
cachalot,6
léopard,7
panthère,7
Nouvelle Requête : (requete 4 du fichier des requetes : insert into articles (code,nom,prix,stock_actu,stock_mini) values ('x400','nouveau',100,20,10))
1 ligne(s) ont été mises à jour
Nouvelle Requête : (requete 5 du fichier des requetes : select * from articles)
Résultats obtenus
-----------------
a300,vélo,1202,31,8
d600,arc,5000,10,8
d800,canoé,1502,13,7
x123,fusil,3000,10,8
s345,skis nautiques,1800,3,8
f450,essai3,3,3,9
f807,cachalot,200000,0,6
z400,léopard,500000,1,7
g457,panthère,800000,1,7
x400,nouveau,100,20,10
Nouvelle Requête : (requete 6 du fichier des requêtes : delete from articles where code='x400')
1 ligne(s) ont été mises à jour
Nouvelle Requête : (requete 7 du fichier des requêtes : select * from articles)
Résultats obtenus
-----------------
a300,vélo,1202,31,8
d600,arc,5000,10,8
d800,canoé,1502,13,7
x123,fusil,3000,10,8
s345,skis nautiques,1800,3,8
f450,essai3,3,3,9
f807,cachalot,200000,0,6
z400,léopard,500000,1,7
g457,panthère,800000,1,7
6.3. IMPOTS con un database
L'ultima volta che abbiamo affrontato il problema del calcolo delle imposte, abbiamo utilizzato un'interfaccia grafica e i dati erano memorizzati in un file. Riprendiamo questa versione partendo dal presupposto che ora i dati si trovino in un database ODBC-MySQL. MySQL è un SGBD di dominio pubblico utilizzabile su diverse piattaforme, tra cui Windows e Linux. Con questo SGBD, è stato creato un database denominato dbimpots contenente un'unica tabella chiamata impots. L'accesso al database è controllato da un nome utente e una password, in questo caso admimpots/mdpimpots. La schermata mostra come utilizzare il database dbimpots con MySQL:
C:\Program Files\EasyPHP\mysql\bin>mysql -u admimpots -p
Enter password: *********
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 3.23.49-max-nt
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> use dbimpots;
Database changed
mysql> show tables;
+--------------------+
| Tables_in_dbimpots |
+--------------------+
| impots |
+--------------------+
1 row in set (0.00 sec)
mysql> describe impots;
+---------+--------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limites | double | YES | | NULL | |
| coeffR | double | YES | | NULL | |
| coeffN | double | YES | | NULL | |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)
mysql> select * from impots;
+---------+--------+---------+
| limites | coeffR | coeffN |
+---------+--------+---------+
| 12620 | 0 | 0 |
| 13190 | 0.05 | 631 |
| 15640 | 0.1 | 1290.5 |
| 24740 | 0.15 | 2072.5 |
| 31810 | 0.2 | 3309.5 |
| 39970 | 0.25 | 4900 |
| 48360 | 0.3 | 6898 |
| 55790 | 0.35 | 9316.5 |
| 92970 | 0.4 | 12106 |
| 127860 | 0.45 | 16754 |
| 151250 | 0.5 | 23147.5 |
| 172040 | 0.55 | 30710 |
| 195000 | 0.6 | 39312 |
| 0 | 0.65 | 49062 |
+---------+--------+---------+
14 rows in set (0.00 sec)
mysql>quit
L'interfaccia grafica dell'applicazione è la seguente:

L'interfaccia grafica ha subito alcune modifiche:
n. | tipo | nome | ruolo |
1 | JTextField | txtConnexion | stringa di connessione al database ODBC |
2 | JScrollPane | JScrollPane1 | contenitore per l'area di testo 3 |
3 | JTextArea | txtStatus | visualizza i messaggi di stato, in particolare i messaggi di errore |
La stringa di connessione inserita al punto (1) ha la seguente forma: DSN;login;password con
il nome DSN della fonte dati ODBC | |
| l’identità di un utente con diritto di lettura sul database |
| la sua password |
Il database dbimpots è stato creato manualmente con MySQL. Lo si trasforma in una fonte dati ODBC nel modo seguente:
- si avvia il gestore delle fonti dati ODBC a 32 bit

- si utilizza il pulsante [Add] per aggiungere una nuova fonte dati ODBC

- si seleziona il driver MySQL e si esegue [Terminer]

- il driver MySQL richiede alcune informazioni:
1 | il nome DSN da assegnare alla fonte di dati ODBC - può essere qualsiasi nome |
2 | il computer su cui viene eseguito il SGBD MySQL - in questo caso localhost. È interessante notare che il database potrebbe essere un database remoto. Le applicazioni locali che utilizzano la fonte dati ODBC non se ne accorgerebbero. Questo varrebbe in particolare per la nostra applicazione Java. |
3 | il database MySQL da utilizzare. MySQL è un SGBD che gestisce database relazionali, ovvero insiemi di tabelle collegate tra loro da relazioni. In questo caso, si specifica il nome del database gestito. |
4 | il nome di un utente con diritti di accesso a questo database |
5 | la sua password |
Una volta definita la fonte dati ODBC, è possibile testare il nostro programma:
![]() | ![]() |
Esaminiamo il codice che, rispetto alla versione grafica senza database, è stato modificato. Ricordiamo il codice della classe impots utilizzata finora:
// creazione di una classe «imposte»
public class impots{
// i dati necessari per il calcolo dell'imposta
// provengono da una fonte esterna
private double[] limites, coeffR, coeffN;
// costruttore
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
// si verifica che i 3 array abbiano le stesse dimensioni
boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la même taille("+
LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
// va bene
this.limites=LIMITES;
this.coeffR=COEFFR;
this.coeffN=COEFFN;
}//produttore
// calcolo dell'imposta
public long calculer(boolean marié, int nbEnfants, int salaire){
// calcolo del numero di quote
double nbParts;
if (marié) nbParts=(double)nbEnfants/2+2;
else nbParts=(double)nbEnfants/2+1;
if (nbEnfants>=3) nbParts+=0.5;
// calcolo del reddito imponibile e del quoziente familiare
double revenu=0.72*salaire;
double QF=revenu/nbParts;
// calcolo dell'imposta
limites[limites.length-1]=QF+1;
int i=0;
while(QF>limites[i]) i++;
// restituzione del risultato
return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
}//calcolare
}//classe
Questa classe crea le tre tabelle limites, coeffR, coeffN a partire da tre tabelle passate come parametri al suo costruttore. Si decide di aggiungere un nuovo costruttore che consenta di creare le stesse tre tabelle a partire da un database:
public impots(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: nome DSN del database
// userIMPOTS, mdpIMPOTS: login/password di accesso al database
A titolo di esempio, decidiamo di non implementare questo nuovo costruttore nella classe impots, ma in una classe derivata, impotsJDBC:
// pacchetti importati
import java.sql.*;
import java.util.*;
public class impotsJDBC extends impots{
// aggiunta di un costruttore che consente di creare
// le tabelle «limites», «coeffr» e «coeffn» a partire dalla tabella
// importazione da un database
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: nome DSN del database
// userIMPOTS, mdpIMPOTS: login/password di accesso al database
// le tabelle di dati
ArrayList aLimites=new ArrayList();
ArrayList aCoeffR=new ArrayList();
ArrayList aCoeffN=new ArrayList();
// connessione al database
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
// creazione di un oggetto Statement
Statement S=connect.createStatement();
// query SELECT
String select="select limites, coeffr, coeffn from impots";
// esecuzione della query
ResultSet RS=S.executeQuery(select);
while(RS.next()){
// elaborazione della riga corrente
aLimites.add(RS.getString("limites"));
aCoeffR.add(RS.getString("coeffr"));
aCoeffN.add(RS.getString("coeffn"));
}// riga successiva
// chiusura delle risorse
RS.close();
S.close();
connect.close();
// trasferimento dei dati in tabelle delimitate
int n=aLimites.size();
limites=new double[n];
coeffR=new double[n];
coeffN=new double[n];
for(int i=0;i<n;i++){
limites[i]=Double.parseDouble((String)aLimites.get(i));
coeffR[i]=Double.parseDouble((String)aCoeffR.get(i));
coeffN[i]=Double.parseDouble((String)aCoeffN.get(i));
}//for
}//costruttore
}//classe
Il costruttore legge il contenuto della tabella impots dal database che gli è stato passato come parametro e popola i tre array limites, coeffR, coeffN. Possono verificarsi diversi errori. Il costruttore non li gestisce, ma li «trasmette» al programma chiamante:
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
Se si osserva attentamente il codice precedente, si noterà che la classe impotsJDBC utilizza direttamente i campi limites, coeffR, coeffN della sua classe base impots. Questi sono dichiarati privati:
la classe impotsJDBC non ha accesso diretto a questi campi. Apportiamo quindi una prima modifica alla classe base scrivendo:
L'attributo protected consente alle classi derivate dalla classe impots di avere accesso diretto ai campi dichiarati con questo attributo. Dobbiamo apportare una seconda modifica. Il costruttore della classe figlia impotsJDBC è dichiarato come segue:
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
Sappiamo che prima di creare un oggetto di una classe figlia, è necessario creare prima un oggetto della classe padre. A tal fine, il costruttore della classe figlia deve chiamare esplicitamente il costruttore della classe padre con un'istruzione super(....). In questo caso ciò non avviene perché non è chiaro quale costruttore della classe padre si possa chiamare. Per il momento ce n’è solo uno e non è adatto. Il compilatore cercherà quindi nella classe padre un costruttore senza parametri che possa chiamare. Non ne trova nessuno e questo genera un errore in fase di compilazione. Aggiungiamo quindi un costruttore senza argomenti alla nostra classe impots:
Lo dichiariamo "protected" in modo che possa essere utilizzato solo dalle classi figlie. Lo scheletro della classe impots è ora il seguente:
public class impots{
// i dati necessari per il calcolo dell'imposta
// provengono da una fonte esterna
protected double[] limites=null;
protected double[] coeffR=null;
protected double[] coeffN=null;
// costruttore vuoto
protected impots(){}
// produttore
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
...........
}//costruttore
// calcolo dell'imposta
public long calculer(boolean marié, int nbEnfants, int salaire){
.............
}//calcolare
}//classe
L'azione del menu Initialiser della nostra applicazione diventa la seguente:
void mnuInitialiser_actionPerformed(ActionEvent e) {
// si recupera la stringa di connessione
Pattern séparateur=Pattern.compile("\\s*;\\s*");
String[] champs=séparateur.split(txtConnexion.getText().trim());
// sono necessari tre campi
if(champs.length!=3){
// errore
txtStatus.setText("Chaîne de connexion (DSN;uid;mdp) incorrecte");
// ritorno all'interfaccia grafica
txtConnexion.requestFocus();
return;
}//if
// si caricano i dati
try{
// creazione dell'oggetto impotsJDBC
objImpots=new impotsJDBC(champs[0],champs[1],champs[2]);
// conferma
txtStatus.setText("Données chargées");
// lo stipendio può essere modificato
txtSalaire.setEditable(true);
// non sono più possibili modifiche
mnuInitialiser.setEnabled(false);
txtConnexion.setEditable(false);
}catch(Exception ex){
// problema
txtStatus.setText("Erreur : " + ex.getMessage());
// fine
return;
}//intercettazione
}
Una volta creato l'oggetto objImpots, l'applicazione risulta identica a quella grafica già realizzata. Si invita il lettore a fare riferimento a quest'ultima.
6.4. Esercizi
6.4.1. Esercizio 1
Fornire un'interfaccia grafica al programma sql3 precedente.
6.4.2. Esercizio 2
Un’applet Java può accedere a un database solo tramite il server da cui è stata caricata. Infatti, poiché un’applet non ha accesso al disco del computer su cui viene eseguita, il database non può trovarsi sul computer del cliente che utilizza l’applet. Ci troviamo quindi nella seguente situazione:

Il computer su cui viene eseguita l’applet, il server e il computer che ospita il database possono essere tre macchine diverse. In questo caso si suppone che il database si trovi sul server.
Problema 1
Scrivere in Java la seguente applicazione server:
- l’applicazione server opera su una porta che le viene passata come parametro
- quando un client si connette, l’applicazione server invia il messaggio
- il client invia quindi i parametri necessari per la connessione a un database e la query che desidera eseguire nel formato:
I parametri sono separati dalla barra. I primi quattro parametri sono quelli del programma sql3 descritto in questo capitolo.
- L’applicazione server crea quindi una connessione con il database specificato, che deve trovarsi sullo stesso computer, ed esegue su di esso la query SQL. I risultati vengono restituiti al client nel formato:
...
se si tratta del risultato di una query Select oppure
per restituire il numero di righe interessate da una query di aggiornamento. Se si verifica un errore di connessione al database o di esecuzione della query, l’applicazione restituisce
- una volta eseguita la query, l’applicazione server chiude la connessione.
Problema 2
Creare un’applet Java che consenta di interrogare il server precedente. È possibile ispirarsi all’interfaccia grafica dell’esercizio 1 precedente. Poiché la porta del server può variare, questa dovrà essere inserita nell’interfaccia dell’applet. Lo stesso vale per tutti i parametri necessari all’invio della riga:
che il client deve inviare al server.
6.4.3. Esercizio 3
Il testo seguente illustra un problema inizialmente destinato ad essere risolto in Visual Basic. Adattatelo per l’elaborazione in Java all’interno di un’applet. Quest’ultima si baserà sul server dell’esercizio 2. L’interfaccia grafica potrà essere modificata per tenere conto del nuovo contesto di esecuzione.
Si propone di creare un'applicazione che metta in evidenza le diverse operazioni di aggiornamento possibili di una tabella di un database ACCESS. Il database ACCESS si chiama articles.mdb. Contiene un'unica tabella denominata «articoli» che elenca gli articoli venduti da un'azienda. La sua struttura è la seguente:
nome | tipo |
code | codice articolo di 4 caratteri |
nom | il suo nome (stringa di caratteri) |
prix | il suo prezzo (effettivo) |
stock_actu | la sua disponibilità attuale (numero intero) |
stock_mini | la quantità minima (numero intero) al di sotto della quale è necessario rifornire l'articolo |
Si propone di visualizzare e aggiornare questa tabella tramite il seguente modulo:

I controlli di questo modulo sono i seguenti:
n. | tipo | nome | Funzione |
1 | casella di testo | scheda | numero della scheda visualizzata enabled è impostato su false |
2 | casella di testo | codice | codice dell'articolo |
3 | casella di testo | nome | nome dell'articolo |
4 | casella di testo | prezzo | prezzo dell'articolo |
5 | casella di testo | attuale | stock attuale dell'articolo |
6 | casella di testo | minimo | scorta minima dell'articolo |
7 | data | data1 | Controllo dati associato al database nome_database=percorso del file articoli.mdb recordsource=articoli connect=access |
8 | HScrollBar | posizione | consente di navigare nella tabella |
9 | pulsante | OK | consente di confermare un aggiornamento - appare solo durante l'aggiornamento |
10 | pulsante | Annulla | consente di annullare un aggiornamento - compare solo durante l'aggiornamento |
11 | frame | frame1 | per abbellire |
12 | casella di testo | basename | nome del database aperto enabled è impostato su false |
13 | casella di testo | sourcename | nome della tabella aperta enabled è impostato su false |
controllo | Particolarità |
data1 | I campi databasename e recordsource sono compilati. databasename deve indicare il database Access articles.mdb presente nella vostra directory e recordsource la tabella articoli. |
code | Si tratta di una casella di testo che si desidera collegare al campo «code» del record corrente di «data1». A tal fine, si compilano due campi: datasource: si inserisce «data1» per indicare che la casella di testo è collegata alla tabella associata a «data1» datafield: si seleziona il campo "codice" della tabella "articoli" Dopo queste operazioni, la casella di testo «codice» conterrà sempre il campo «codice» del record corrente di data1. Viceversa, modificando il contenuto di questa casella di testo si modificherà il campo «codice» della scheda corrente. Si procede allo stesso modo per le altre caselle di testo |
nom | fonte dati: data1 campo dati: nome |
prix | fonte dati: data1 campo dati: prezzo |
actuel | fonte dati: data1 campo dati: stock_actu |
minimum | fonte dati: data1 campo dati: stock_mini |
La struttura dei menu è la seguente
Modifica | Sfoglia | Esci |
Aggiungi | Indietro | |
Modifica | Avanti | |
Elimina | Primo | |
Ultimo |
Le diverse opzioni hanno le seguenti funzioni:
menu | nome | funzione |
mnuaggiungere | per aggiungere un nuovo record alla tabella articoli | |
mnusupprimer | per eliminare dalla tabella articoli il record attualmente visualizzato | |
mnumodifica | per modificare, nella tabella articoli, il record attualmente visualizzato | |
mnuprecedent | per passare al record precedente | |
mnusuivant | per passare alla registrazione successiva | |
precedente | per passare al primo record | |
mnudernier | per passare all'ultimo record | |
mnuquitter | per uscire dall'applicazione |
Durante l'evento form_load, viene aperta la tabella associata a data1 (data1.refresh). Se l’apertura non va a buon fine, viene visualizzato un messaggio di errore e il programma termina (end). In caso contrario, viene visualizzato il modulo con il primo record della tabella articles. Non è possibile effettuare alcun inserimento nei campi (proprietà enabled impostata su false). L'inserimento è possibile solo tramite le opzioni Aggiungi e Modifica. I pulsanti OK e Annulla sono nascosti (visible=false).
Ci proponiamo di creare le procedure relative alle diverse opzioni del menu e ai pulsanti OK e Annulla. In una prima fase, tralasceremo i seguenti punti:
- l’abilitazione/disabilitazione di alcune opzioni del menu: ad esempio, l’opzione Avanti deve essere disabilitata se ci si trova sull’ultimo record della tabella
- la gestione dello scroller orizzontale
menu Sfoglia/Avanti
- passa al record successivo (data1.RecordSet.MoveNext) se non ci si trova alla fine del file (data1.RecordSet.EOF). Aggiorna la casella di testo della scheda (data1.recordset.absoluteposition/ data1.recordset.recordcount).
Menu Sfoglia/Precedente
- passa al record precedente (data1.RecordSet.MovePrevious) se non ci si trova all’inizio del file (data1.RecordSet.BOF). Aggiorna la casella di testo della scheda.
Menu Sfoglia/Primo
- passa al primo record (data1.RecordSet.MoveFirst) se il file non è vuoto (data1.recordset.recordcount=0). Aggiorna la casella di testo della scheda.
Menu Sfoglia/Ultimo
- passa all'ultimo record (data1.RecordSet.MoveLast) se il file non è vuoto (data1.recordset.recordcount=0). Aggiorna la casella di testo della scheda.
Menu Modifica/Aggiungi
- consente di aggiungere un record alla tabella
- attiva la modalità Aggiunta record (data1.recordset.addnew)
- consente l’inserimento di dati nei 5 campi codice, nome, prezzo, ecc. (enabled=true)
- disabilita i menu Modifica, Sfoglia, Esci (enabled=false)
- visualizza i pulsanti OK e Annulla (visible=true)
pulsante OK
- conferma una modifica al record (data1.recordset.Update)
- nasconde i pulsanti OK e Annulla (visible=false)
- abilita le opzioni Modifica, Sfoglia, Esci (enabled=true)
- aggiorna la casella di testo della scheda
pulsante Annulla
- annulla una modifica al record (data1.recordset.CancelUpdate)
- nasconde i pulsanti OK e Annulla (visible=false)
- abilita le opzioni Modifica, Sfoglia, Esci (enabled=true)
- aggiorna la casella di testo della scheda
menu Modifica/Modifica
- consente di modificare il record visualizzato nel modulo
- attiva la modalità Modifica record (data1.recordset.edit)
- consente l'inserimento di dati nei 4 campi nome, prezzo, ... (enabled=true) ma non nel campo codice (enabled=false)
- disabilita i menu Modifica, Sfoglia, Esci (enabled=false)
- visualizza i pulsanti OK e Annulla (visible=true)
menu Modifica/Elimina
- consente di eliminare (data1.recordset.delete) il record visualizzato dalla tabella
- passa alla scheda successiva (data1.recordset.movenext)
menu Esci
- scarica il foglio (unload me)
evento form_unload (cancel as integer)
- attivato dall’operazione unload me o dalla chiusura del modulo tramite Alt-F4 o un doppio clic sulla casella di sistema, quindi non necessariamente tramite l’opzione Esci.
- pone la domanda «Vuoi davvero uscire dall’applicazione?» con due pulsanti Sì/No (msgbox con style=vbyes+vbno)
- se la risposta è No (=vbno), si imposta cancel a -1 e si esce dalla procedura form_unload. Il valore di cancel pari a -1 indica che la chiusura della finestra è stata rifiutata.
- Se la risposta è «Sì» (=vbyes), il database viene chiuso (data1.recordset.close, data1.database.close).
In questa sezione ci si concentra sull’abilitazione/disabilitazione dei menu. Dopo ogni operazione che modifica la scheda corrente, verrà richiamata una procedura che chiameremo Oueston. Questa verificherà le seguenti condizioni:
. se il file è vuoto,
- disabiliteremo i menu Sfoglia, Modifica/Elimina,
- autorizzeremo gli altri
. Se la scheda corrente è la prima,
- disabiliterà «Sfoglia/Precedente»
- e si consentiranno le altre
. Se la scheda corrente è l'ultima, si
- disabiliterà «Sfoglia/Successivo»
- si consentiranno le altre operazioni
Un variatore orizzontale presenta tre campi importanti:
- min: il suo valore minimo
- max: il suo valore massimo
- value: il suo valore attuale
Inizializzazione del variatore
Al caricamento (form_load), il variatore verrà inizializzato come segue:
- min=0
- max=data1.recordset.recordcount-1
- value=1
Si noti che subito dopo l’apertura del database (data1.refresh), il numero di record della tabella rappresentata da data1.recordset.recordcount è errato. È necessario andare alla fine della tabella (MoveLast), quindi tornare all'inizio della tabella (MoveFirst) affinché il valore sia corretto.
Intervento diretto sul variatore
La posizione del cursore del variatore rappresenta la posizione nella tabella.
Quando l’utente modifica il variatore (qui denominato “posizione”), viene attivato l’evento position_change. In questo evento, si modificherà il record corrente della tabella in modo che rifletta lo spostamento effettuato sul variatore. A tal fine, si utilizzerà il campo absoluteposition di data1.recordset. Quando si assegna il valore i a questo campo, la scheda n. i della tabella diventa la scheda corrente. I record sono numerati a partire da 0 e hanno quindi un numero compreso nell’intervallo [0,data1.recordset.recordcount-1]. Nella procedura position_change, è sufficiente scrivere
affinché la scheda corrente visualizzata nel modulo rifletta la regolazione effettuata sul variatore.
Fatto ciò, si richiamerà quindi la procedura Oueston per aggiornare i menu.
Aggiornamento del variatore
Poiché la posizione del cursore del variatore deve rispecchiare la posizione nella tabella, è necessario aggiornare il valore del variatore ogni volta che si verifica un cambiamento della scheda corrente nella tabella, generato da uno dei due menu. Poiché ciascuno di essi richiama la procedura Oueston, è preferibile inserire anche questo aggiornamento all’interno di tale procedura. È sufficiente scrivere qui:
Si aggiunge l’opzione Sfoglia/Cerca, che ha lo scopo di consentire all’utente di visualizzare un articolo di cui fornisce il codice.
Quando questa opzione è attivata, si verificano le seguenti sequenze:
- si passa alla modalità “Aggiungi scheda” (Addnew), al solo scopo di non modificare la scheda corrente su cui ci si trovava al momento dell’attivazione dell’opzione,
- si abilita l’inserimento nel campo “Codice” e si cancella il contenuto del campo “Scheda”,
- si disabilitano i menu e si visualizzano i pulsanti OK e Annulla
- quando l’utente preme OK, occorre cercare la scheda corrispondente al codice digitato dall’utente. Tuttavia, la procedura OK_click è già utilizzata per le opzioni «Sfoglia/Aggiungi» e «Sfoglia/Modifica». Per distinguere tra questi casi, dobbiamo gestire una variabile globale che chiameremo qui «stato», che avrà tre possibili valori: «aggiungi», «modifica» e «cerca». Le procedure associate ai pulsanti OK e Annulla utilizzeranno questa variabile per capire in quale contesto vengono chiamate.
- Se «stato» è pari a «cerca», nella procedura associata a OK, si
- annulla l’operazione addnew (data1.recordset.cancelupdate) poiché non si intendeva aggiungere una scheda. Va notato che, in tal caso, la scheda corrente torna ad essere quella che era presente sullo schermo prima dell’operazione Sfoglia/Cerca.
- Si costruisce il criterio di ricerca relativo al codice e si avvia la ricerca (data1.recordset.findfirst criterio),
- se la ricerca non va a buon fine (data1.recordset.nomatch=true), lo si segnala all’utente, quindi si torna alla modalità di aggiunta (addnew) e si esce dalla procedura OK. L’utente dovrà digitare nuovamente un nuovo codice o selezionare l’opzione Annulla.
- Se la ricerca ha esito positivo, la scheda trovata diventa la nuova scheda corrente. Vengono ripristinati i menu, vengono nascosti i pulsanti OK/Annulla, viene disabilitato l’inserimento nel campo codice e si esce dalla procedura.
- . Se lo stato è «Cerca», nella procedura collegata a Annulla, si
- annulla l’operazione addnew (data1.recordset.cancelupdate). Si tornerà quindi automaticamente alla scheda corrente precedente all’operazione Sfoglia/Cerca.
- ripristina i menu, nasconde i pulsanti OK/Annulla, disabilita l'inserimento nel campo codice e si esce dalla procedura.
Un articolo deve essere identificato in modo univoco tramite il proprio codice. Assicurarsi che nell’opzione Sfoglia/Aggiungi l’aggiunta venga rifiutata se il record da aggiungere ha un codice articolo già presente nella tabella.
6.4.4. Esercizio 4
Qui viene presentata un’applicazione web basata sul server dell’esercizio 2. Si tratta di un prototipo di applicazione di e-commerce.
Il cliente ordina gli articoli tramite la seguente interfaccia web:

Può eseguire le seguenti operazioni:
- selezionare un articolo dall’elenco a discesa
- specifica la quantità desiderata
- conferma l’acquisto con il pulsante Acheter
- il suo acquisto viene visualizzato nell'elenco degli articoli acquistati
- può rimuovere articoli da questo elenco, selezionando un articolo e utilizzando il pulsante Retirer
- quando preme il pulsante Bilan, ottiene il seguente riepilogo:

Il riepilogo consente all'utente di visualizzare i dettagli della propria fattura. L'utente può accedere ai dettagli relativi a un articolo selezionato dall'elenco a discesa tramite il pulsante Informations:

Una volta richiesta la sintesi della fattura, l’utente può confermarla nella pagina successiva. A tal fine, deve inserire il proprio indirizzo e-mail e confermare l’ordine tramite l’apposito pulsante.

Prima di salvare l’ordine, l’applicazione richiede una conferma:

Una volta confermato l’ordine, l’applicazione lo elabora e invia una pagina di conferma:

In realtà, l’applicazione non elabora l’ordine. Si limita a inviare un’e-mail all’utente chiedendogli di pagare l’importo degli acquisti:
Cher client,
Vous trouverez ci-dessous le détail de votre commande au magasin SuperPrix. Elle vous sera livrée après réception de votre chèque établi à l'ordre de SuperPrix et à envoyer à l'adresse suivante :
SuperPrix
ISTIA
62 av Notre-Dame du Lac
49000 Angers
France
Nous vous remercions vivement de votre commande
----------------------------------------
Votre commande
----------------------------------------
article, quantité, prix unitaire, total
========================================
vélo, 2, 1202.00 F, 2404.00 F
skis nautiques, 3, 1800.00 F, 5400.00 F
Total à payer : 7804 F
Domanda: Creare l'equivalente di questa applicazione web con un'applet Java.

