6. Gestione di database con l'API JDBC
6.1. Panoramica
Sul mercato sono disponibili numerosi database. Per standardizzare l'accesso ai database in ambiente MS Windows, Microsoft ha sviluppato un'interfaccia denominata ODBC (Open DataBase Connectivity). Questo livello nasconde le caratteristiche specifiche di ciascun database dietro un'interfaccia standard. In ambiente MS Windows sono disponibili numerosi driver ODBC che facilitano l'accesso ai database. Di seguito, ad esempio, è riportato un elenco dei driver ODBC 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 modifiche.

Per consentire alle applicazioni Java di sfruttare a loro volta l'interfaccia ODBC, Sun ha creato l'interfaccia JDBC (Java Database Connectivity), che funge da intermediario tra l'applicazione Java e l'interfaccia ODBC:

6.2. Passaggi chiave nelle operazioni sui database
6.2.1. Introduzione
In un'applicazione Java che utilizza un database con l'interfaccia JDBC, sono generalmente coinvolti i seguenti passaggi:
- Connessione al database
- Invio di query SQL al database
- Ricezione ed elaborazione dei risultati di tali query
- Chiusura della connessione
I passaggi 2 e 3 vengono eseguiti ripetutamente, con la connessione che viene chiusa solo al termine delle operazioni sul database. Si tratta di un modello relativamente standard che potresti conoscere se hai utilizzato un database in modo interattivo. Descriveremo in dettaglio ciascuno di questi passaggi utilizzando un esempio. Prenderemo in considerazione un database di Access denominato ARTICLES con la seguente struttura:
nome | tipo |
codice | codice articolo di 4 caratteri |
nome | il suo nome (stringa) |
prezzo | il suo prezzo (effettivo) |
stock_attuale | scorte attuali (numero intero) |
min_stock | la scorta minima (numero intero) al di sotto della quale l'articolo deve essere rifornito |
Questo database ACCESS è definito come origine dati "utente" nel Database Manager ODBC:


Le sue proprietà vengono specificate utilizzando il pulsante Configura come segue:

Questa configurazione consiste essenzialmente nell'associare il database ARTICLES al file di 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 stabilire una connessione. Ciò avviene utilizzando il seguente metodo di classe:
dove
DriverManager: una classe Java contenente l'elenco dei driver disponibili per l'applicazione
Connection: una classe Java che stabilisce un collegamento tra l'applicazione e il database, attraverso il quale l'applicazione invierà query SQL al database e riceverà i risultati
URL: nome che identifica il database. Questo nome è analogo agli URL di Internet. Ecco perché fa parte della classe URL. Tuttavia, Internet non c'entra affatto in questo caso. L'URL ha la seguente forma:
**jdbc:nome\_driver:nome\_sorgente;param=val1;param2=val2**
Nei nostri esempi, in cui useremo solo driver ODBC, il driver si chiama **odbc**. La terza parte dell'URL è costituita dal nome della sorgente e da eventuali parametri. Nei nostri esempi, si tratterà di sorgenti ODBC note al sistema. Pertanto, l'URL per la sorgente dati *Articles* definita in precedenza sarà
**jdbc:odbc:Articles**
id: ID utente (login)
mdp: password utente
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 stabilire la connessione tra l'applicazione Java e il database specificato, allora viene stabilita una connessione tra l'applicazione Java e il database. Questa connessione è rappresentata nel programma dall'oggetto Connection restituito dalla classe DriverManager. Poiché questa connessione può fallire per vari motivi, potrebbe generare un'eccezione. Scriveremo quindi:
Connection connexion=null;
URL base=...;
String id=...;
String mdp=...;
try{
connexion=DriverManager.getConnection(base,id,mdp);
} catch (Exception e){
// traiter l’exception
}
Per connettersi a un database, è necessario disporre del driver appropriato. Nei nostri esempi, si tratterà del driver ODBC in grado di gestire il database richiesto. Sebbene questo driver debba essere disponibile nell'elenco dei driver ODBC presenti sul computer, è necessario disporre anche della classe Java che fungerà da interfaccia con esso. A tal fine, l'applicazione richiederà la classe necessaria come segue:
La classe Class non è in alcun modo correlata all'interfaccia JDBC. Si tratta di una classe di gestione generale delle classi. Il suo metodo statico forName consente di caricare dinamicamente una classe, rendendo così disponibili i suoi attributi e metodi statici. La classe che si interfaccia con i driver ODBC di MS Windows si chiama “sun.jdbc.odbc.JdbcOdbcDriver”. Scriveremmo quindi (il metodo potrebbe generare un'eccezione):
try{
Class.forName(« sun.jdbc.odbc.JdbcOdbcDriver ») ;
} catch (Exception e){
// handle exception (non-existent class)
}
Le classi necessarie per l'interfaccia JDBC si trovano nel pacchetto java.sql. Pertanto, all'inizio del programma scriviamo:
Ecco un programma che consente di connettersi a un database:
import java.sql.*;
import java.io.*;
// call: pg PILOTE URL UID MDP
// connects to the URL database using class JDBC PILOTE
// user UID is identified by password MDP
public class connexion1{
static String syntaxe="pg PILOTE URL UID MDP";
public static void main(String arg[]){
// check number of arguments
if(arg.length<2 || arg.length>4)
erreur(syntaxe,1);
// connection to base
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);
}
// closing the base
try{
connect.close();
System.out.println("Base " + arg[1] + " fermée");
} catch (Exception e){}
}// hand
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// class
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 query al database
L'interfaccia JDBC consente di inviare query SQL al database collegato all'applicazione Java, nonché di elaborare i risultati di tali query. SQL (Structured Query Language) è un linguaggio di query standardizzato per i database relazionali. Esistono diversi tipi di query:
- istruzioni di interrogazione del database (SELECT)
- query di aggiornamento del database (INSERT, DELETE, UPDATE)
- query per la creazione/eliminazione di tabelle (CREATE, DELETE)
Si presume che il lettore abbia familiarità con le nozioni di base del linguaggio SQL.
6.2.3.1. La classe Statement
Per inviare una query SQL 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. È necessariamente associato alla connessione corrente. È quindi un metodo della connessione stabilita che consente di creare gli oggetti Statement necessari per eseguire le query SQL. Se connection è l'oggetto che rappresenta la connessione al database, un oggetto Statement si ottiene come segue:
Una volta ottenuto un oggetto Statement, è possibile eseguire le query SQL. Ciò avviene in modo diverso a seconda che la query sia una query per recuperare dati o per aggiornare il database.
6.2.3.2. Esecuzione di una query per recuperare dati dal database
Una query è tipicamente del seguente tipo:
Sono obbligatorie solo le parole chiave nella prima riga; le altre sono facoltative. Esistono altre parole chiave non riportate qui.
- Viene eseguito un join con tutte le tabelle elencate dopo la parola chiave `FROM`
- Vengono mantenute solo le colonne che seguono la parola chiave `select`
- Vengono mantenute solo le righe che soddisfano la condizione della parola chiave `where`
- Le righe risultanti, ordinate in base all'espressione nella parola chiave `ORDER BY`, costituiscono il risultato della query.
Il risultato di un SELECT è una tabella. Se consideriamo la precedente tabella ARTICLES e vogliamo i nomi degli articoli il cui stock attuale è inferiore alla soglia minima, scriveremmo: SELECT name FROM articles WHERE current\_stock < min\_stock*. Se vogliamo che siano ordinati alfabeticamente per nome, scriveremmo: select name from articles where current_stock < minimum_stock order by name*
Per eseguire questo tipo di query, la classe Statement fornisce il metodo executeQuery:
dove query è il testo della query SELECT da eseguire.
Pertanto, se
- connection è l'oggetto che rappresenta la connessione al database
- Statement s = connection.createStatement() crea l'oggetto Statement necessario per eseguire le query SQL
- ResultSet rs = s.executeQuery("select name from articles where current_stock < minimum_stock") esegue una query SELECT e assegna le righe risultanti della query a un oggetto di tipo ResultSet.
6.2.3.3. La classe ResultSet: risultato di una query SELECT
Un oggetto ResultSet rappresenta una tabella, ovvero un insieme di righe e colonne. In un dato momento, è accessibile solo una riga della tabella, nota come 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 fornisce il metodo next:
Questo metodo tenta di passare alla riga successiva nel ResultSet e restituisce true in caso di esito positivo, false in caso contrario. In caso di esito positivo, la riga successiva diventa la nuova riga corrente. La riga precedente viene persa e non può essere recuperata. La tabella ResultSet ha le colonne col1, col2, ... Per accedere ai vari campi della riga corrente, sono disponibili i seguenti metodi:
per recuperare il campo "coli" dalla riga corrente. Type si riferisce al tipo del campo coli. Il metodo getString viene spesso utilizzato su tutti i campi e consente di recuperare il contenuto del campo come stringa. È quindi possibile convertirlo se necessario. 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.*;
// displays the contents of a ARTICLES system database
public class articles1{
static final String DB="ARTICLES"; // database to exploit
public static void main(String arg[]){
Connection connect=null; // connection to base
Statement S=null; // purpose of queries
ResultSet RS=null; // query result table
try{
// base connection
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
System.out.println("Connexion avec la base " + DB + " établie");
// creation of a Statement object
S=connect.createStatement();
// execute a select query
RS=S.executeQuery("select * from " + DB);
// using the results table
while(RS.next()){ // as long as there's a line to operate
// it is displayed on screen
System.out.println(RS.getString("code")+","+
RS.getString("nom")+","+
RS.getString("prix")+","+
RS.getString("stock_actu")+","+
RS.getString("stock_mini"));
}// next line
} catch (Exception e){
erreur("Erreur " + e,2);
}
// closing the base
try{
connect.close();
System.out.println("Base " + DB + " fermée");
} catch (Exception e){}
}// hand
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// class
I risultati 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(column_name). Utilizziamo invece getType(column_number). Tuttavia, per ottenere tutte le colonne, dovremmo sapere quante colonne ha il ResultSet. La classe ResultSet non fornisce questa informazione. È la classe ResultSetMetaData a fornirla. Più in generale, questa classe fornisce informazioni sulla struttura della tabella, ovvero sulla natura delle sue colonne.
Per accedere alle informazioni sulla struttura di un ResultSet, occorre innanzitutto istanziare un oggetto ResultSetMetaData. Se RS è un ResultSet, il ResultSetMetaData associato si ottiene tramite:
Nella classe ResultSetMetaData sono presenti due metodi utili:
- int getColumnCount(), che restituisce il numero di colonne nel ResultSet
- String getColumnLabel(int i), che restituisce il nome della colonna i nel 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 qualsiasi query SQL SELECT digitata dall'utente sulla tastiera sul database ARTICLES.
import java.sql.*;
import java.io.*;
// displays the contents of a ARTICLES system database
public class sql1{
static final String DB="ARTICLES"; // database to exploit
public static void main(String arg[]){
Connection connect=null; // connection to base
Statement S=null; // purpose of queries
ResultSet RS=null; // query result table
String select; // query text SQL select
int nbColonnes; // no. of columns in ResultSet
// creation of a keyboard input stream
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{
// base connection
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
System.out.println("Connexion avec la base " + DB + " établie");
// creation of a Statement object
S=connect.createStatement();
// execution loop for SQL requests typed on keyboard
System.out.print("Requête : ");
select=in.readLine();
while(!select.equals("fin")){
// query execution
RS=S.executeQuery(select);
// number of columns
nbColonnes=RS.getMetaData().getColumnCount();
// using the results table
System.out.println("Résultats obtenus\n\n");
while(RS.next()){ // as long as there's a line to operate
// it is displayed on screen
for(int i=1;i<nbColonnes;i++)
System.out.print(RS.getString(i)+",");
System.out.println(RS.getString(nbColonnes));
}// next line
// following request
System.out.print("Requête : ");
select=in.readLine();
}// while
} catch (Exception e){
erreur("Erreur " + e,2);
}
// closing the base and input stream
try{
connect.close();
System.out.println("Base " + DB + " fermée");
in.close();
} catch (Exception e){}
}// hand
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// class
Ecco alcuni dei 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. Esegui una query di aggiornamento del database
Un oggetto Statement viene utilizzato per memorizzare le query SQL. Il metodo utilizzato da questo oggetto per eseguire le query di aggiornamento SQL (INSERT, UPDATE, DELETE) non è più il metodo executeQuery descritto in precedenza, ma il metodo executeUpdate:
La differenza sta nel risultato: mentre executeQuery restituiva il set di 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 sulla tastiera sono ora query di aggiornamento per il database ARTICLES.
import java.sql.*;
import java.io.*;
// displays the contents of a ARTICLES system database
public class sql2{
static final String DB="ARTICLES"; // database to exploit
public static void main(String arg[]){
Connection connect=null; // connection to base
Statement S=null; // purpose of queries
ResultSet RS=null; // query result table
String sqlUpdate; // text of SQL update request
int nbLignes; // no. of lines affected by an update
// creation of a keyboard input stream
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{
// base connection
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
System.out.println("Connexion avec la base " + DB + " établie");
// creation of a Statement object
S=connect.createStatement();
// execution loop for SQL requests typed on keyboard
System.out.print("Requête : ");
sqlUpdate=in.readLine();
while(!sqlUpdate.equals("fin")){
// query execution
nbLignes=S.executeUpdate(sqlUpdate);
// follow-up
System.out.println(nbLignes + " ligne(s) ont été mises à jour");
// following request
System.out.print("Requête : ");
sqlUpdate=in.readLine();
}// while
} catch (Exception e){
erreur("Erreur " + e,2);
}
// closing the base and input stream
try{
// free up resources linked to the base
RS.close();
S.close();
connect.close();
System.out.println("Base " + DB + " fermée");
// keyboard flow closure
in.close();
} catch (Exception e){}
}// hand
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// class
Ecco i risultati di diverse esecuzioni dei programmi sql1 e sql2:
Elenco delle righe nel 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
Modifichiamo 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
Aggiungi 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
Eliminazione di 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. Esegui qualsiasi query SQL
L'oggetto Statement necessario per l'esecuzione delle query SQL dispone di un metodo execute in grado di eseguire qualsiasi tipo di query SQL:
Il valore restituito è il valore booleano true se la query ha restituito un ResultSet* (executeQuery*), e false se ha restituito un numero (executeUpdate*). Il ResultSet risultante può essere recuperato utilizzando il metodo getResultSet, e il numero di righe aggiornate utilizzando il metodo getUpdateCount*. Pertanto, scriveremmo:
Statement S=...;
ResultSet RS=...;
int nbLignes;
String requête=...;
// exécution d’une requête SQL
if (S.execute(requête)){
// on a un resultset
RS=S.getResultSet();
// exploitation du ResultSet
...
} else {
// c’était une requête de mise à jour
nbLignes=S.getUpdateCount();
...
}
6.2.3.10. Quarto esempio
Prendiamo il concetto dai programmi sql1 e sql2 e lo applichiamo a un programma sql3 che ora è in grado di eseguire qualsiasi query SQL digitata sulla 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.*;
// call: pg PILOTE URL UID MDP
// connects to the URL database using class JDBC PILOTE
// user UID is identified by password MDP
public class sql3{
static String syntaxe="pg PILOTE URL UID MDP";
public static void main(String arg[]){
// check number of arguments
if(arg.length<2 || arg.length>4)
erreur(syntaxe,1);
// init connection parameters
Connection connect=null;
String uid="";
String mdp="";
if(arg.length>=3) uid=arg[2];
if(arg.length==4) mdp=arg[3];
// other data
Statement S=null; // purpose of queries
ResultSet RS=null; // result table of a query request
String sqlText; // text of query SQL to be executed
int nbLignes; // no. of lines affected by an update
int nbColonnes; // nb of columns in a ResultSet
// creation of a keyboard input stream
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{
// base connection
Class.forName(arg[0]);
connect=DriverManager.getConnection(arg[1],uid,mdp);
System.out.println("Connexion avec la base " + arg[1] + " établie");
// creation of a Statement object
S=connect.createStatement();
// execution loop for SQL requests typed on keyboard
System.out.print("Requête : ");
sqlText=in.readLine();
while(!sqlText.equals("fin")){
// query execution
try{
if(S.execute(sqlText)){
// we have obtained a ResultSet - we exploit it
RS=S.getResultSet();
// number of columns
nbColonnes=RS.getMetaData().getColumnCount();
// using the results table
System.out.println("\nRésultats obtenus\n-----------------\n");
while(RS.next()){ // as long as there's a line to operate
// it is displayed on screen
for(int i=1;i<nbColonnes;i++)
System.out.print(RS.getString(i)+",");
System.out.println(RS.getString(nbColonnes));
}// next line of ResultSet
} else {
// it was an update request
nbLignes=S.getUpdateCount();
// follow-up
System.out.println(nbLignes + " ligne(s) ont été mises à jour");
}//if
} catch (Exception e){
System.out.println("Erreur " +e);
}
// following request
System.out.print("\nNouvelle Requête : ");
sqlText=in.readLine();
}// while
} catch (Exception e){
erreur("Erreur " + e,2);
}
// closing the base and input stream
try{
// free up resources linked to the base
RS.close();
S.close();
connect.close();
System.out.println("Base " + arg[1] + " fermée");
// keyboard flow closure
in.close();
} catch (Exception e){}
}// hand
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// class
Creiamo 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 eseguito come segue:
Il programma legge i dati in ingresso dal file queries e scrive i risultati 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. e per il calcolo delle imposte 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. Riprenderemo questa versione, supponendo ora che i dati si trovino in un database ODBC-MySQL. MySQL è un DBMS open-source che può essere utilizzato su varie piattaforme, tra cui Windows e Linux. Con questo DBMS è stato creato un database denominato dbimpots, contenente una singola 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:
No. | tipo | nome | ruolo |
1 | JTextField | txtConnection | Stringa di connessione al database ODBC |
2 | JScrollPane | JScrollPane1 | contenitore per l'area di testo 3 |
3 | JTextArea | txtStatus | visualizza i messaggi di stato, inclusi i messaggi di errore |
La stringa di connessione inserita in (1) ha il seguente formato: DSN;login;password con
che è il nome DSN dell'origine dati ODBC | |
l'identità di un utente con accesso in lettura al database | |
la relativa password |
Il database dbimpots è stato creato manualmente utilizzando MySQL. Viene convertito in un'origine dati ODBC come segue:
- Avviare l'Amministratore delle origini dati ODBC a 32 bit

- utilizzare il pulsante [Aggiungi] per aggiungere una nuova origine dati ODBC

- Selezionare il driver MySQL e fare clic su [Fine]

- Il driver MySQL richiede alcune informazioni:
1 | Il nome DSN da assegnare all'origine dati ODBC: può essere qualsiasi nome |
2 | la macchina su cui è in esecuzione il DBMS MySQL, in questo caso localhost. Vale la pena notare che il database potrebbe essere un database remoto. Le applicazioni locali che utilizzano l'origine dati ODBC non se ne accorgerebbero. Questo sarebbe il caso, in particolare, della nostra applicazione Java. |
3 | il database MySQL da utilizzare. MySQL è un DBMS che gestisce database relazionali, ovvero insiemi di tabelle collegate tra loro da relazioni. Qui specifichiamo 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 l'origine dati ODBC, possiamo testare il nostro programma:
![]() | ![]() |
Diamo un'occhiata al codice che è stato modificato rispetto alla versione grafica senza database. Ecco il codice per la classe *impots* utilizzata finora:
// creation of an impots class
public class impots{
// data required for tax calculation
// come from an external source
private double[] limites, coeffR, coeffN;
// manufacturer
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
// check that the 3 arrays have the same size
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+")");
// it's good
this.limites=LIMITES;
this.coeffR=COEFFR;
this.coeffN=COEFFN;
}//manufacturer
// tAX CALCULATION
public long calculer(boolean marié, int nbEnfants, int salaire){
// calculating the number of shares
double nbParts;
if (marié) nbParts=(double)nbEnfants/2+2;
else nbParts=(double)nbEnfants/2+1;
if (nbEnfants>=3) nbParts+=0.5;
// calculation of taxable income & family quota
double revenu=0.72*salaire;
double QF=revenu/nbParts;
// tAX CALCULATION
limites[limites.length-1]=QF+1;
int i=0;
while(QF>limites[i]) i++;
// return result
return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
}//calculate
}//class
Questa classe crea i tre array limits, coeffR e coeffN a partire da tre array passati come parametri al suo costruttore. Abbiamo deciso di aggiungere un nuovo costruttore che ci consenta di creare gli stessi tre array attingendo da un database:
public impots(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: DSN database name
// userIMPOTS, mdpIMPOTS: database login/password
Per questo esempio, decidiamo di non implementare questo nuovo costruttore nella classe *impots,* ma in una classe derivata, *impotsJDBC*:
// imported packages
import java.sql.*;
import java.util.*;
public class impotsJDBC extends impots{
// addition of a constructor for building
// limit tables, coeffr, coeffn from table
// database taxes
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: DSN database name
// userIMPOTS, mdpIMPOTS: database login/password
// data tables
ArrayList aLimites=new ArrayList();
ArrayList aCoeffR=new ArrayList();
ArrayList aCoeffN=new ArrayList();
// connection to base
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
// creation of a Statement object
Statement S=connect.createStatement();
// select request
String select="select limites, coeffr, coeffn from impots";
// query execution
ResultSet RS=S.executeQuery(select);
while(RS.next()){
// running line operation
aLimites.add(RS.getString("limites"));
aCoeffR.add(RS.getString("coeffr"));
aCoeffN.add(RS.getString("coeffn"));
}// next line
// closing resources
RS.close();
S.close();
connect.close();
// data transfer to bounded arrays
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
}//manufacturer
}//class
Il costruttore legge il contenuto della tabella impots dal database che gli viene passato come parametro e popola i tre array: limites, coeffR e 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 osserviamo attentamente il codice precedente, possiamo notare che la classe impotsJDBC utilizza direttamente i campi limits, coeffR e coeffN della sua classe base impots. Poiché questi sono dichiarati privati:
la classe impotsJDBC non ha accesso diretto a questi campi. Apportiamo quindi una modifica iniziale 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 costruire un oggetto di una classe figlia, dobbiamo prima costruire un oggetto della classe padre. Per farlo, il costruttore della classe figlia deve chiamare esplicitamente il costruttore della classe padre usando un'istruzione super(....). Qui, questo non viene fatto perché non riusciamo a vedere quale costruttore della classe padre potremmo chiamare. Attualmente ce n'è solo uno, e non è adatto. Il compilatore cercherà quindi nella classe padre un costruttore senza parametri che possa chiamare. Non ne trova uno, e questo genera un errore di compilazione. Aggiungiamo quindi un costruttore senza parametri alla nostra classe impots:
Lo dichiariamo come "protected" in modo che possa essere utilizzato solo dalle classi figlie. Lo scheletro della classe impots ora appare così:
public class impots{
// data required for tax calculation
// come from an external source
protected double[] limites=null;
protected double[] coeffR=null;
protected double[] coeffN=null;
// empty builder
protected impots(){}
// manufacturer
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
...........
}//manufacturer
// tAX CALCULATION
public long calculer(boolean marié, int nbEnfants, int salaire){
.............
}//calculate
}//class
L'azione per il menu Inizializza nella nostra applicazione diventa la seguente:
void mnuInitialiser_actionPerformed(ActionEvent e) {
// retrieve the connection string
Pattern séparateur=Pattern.compile("\\s*;\\s*");
String[] champs=séparateur.split(txtConnexion.getText().trim());
// three fields are required
if(champs.length!=3){
// error
txtStatus.setText("Chaîne de connexion (DSN;uid;mdp) incorrecte");
// back to visual interface
txtConnexion.requestFocus();
return;
}//if
// load data
try{
// creation of impotsJDBC object
objImpots=new impotsJDBC(champs[0],champs[1],champs[2]);
// confirmation
txtStatus.setText("Données chargées");
// salary can be modified
txtSalaire.setEditable(true);
// no more chgt possible
mnuInitialiser.setEnabled(false);
txtConnexion.setEditable(false);
}catch(Exception ex){
// problem
txtStatus.setText("Erreur : " + ex.getMessage());
// end
return;
}//catch
}
Una volta creato l'oggetto objImpots, l'applicazione è identica all'applicazione grafica già scritta. Si invita il lettore a consultarla.
6.4. Esercizi
6.4.1. Esercizio 1
Fornire un'interfaccia grafica per il precedente programma sql3.
6.4.2. Esercizio 2
Un'applet Java può accedere a un database solo tramite il server da cui è stata caricata. Poiché un'applet non ha accesso al disco del computer su cui è in esecuzione, il database non può trovarsi sul computer client che utilizza l'applet. Ci troviamo quindi nella seguente situazione:

Il computer su cui è in esecuzione l'applet, il server e il computer che ospita il database potrebbero essere tre macchine diverse. In questo caso, supponiamo che il database si trovi sul server.
Problema 1
Scrivi la seguente applicazione server in Java:
- L'applicazione server viene eseguita 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 connettersi a un database e la query che desidera eseguire nel seguente formato:
I parametri sono separati da una barra. I primi quattro parametri sono quelli del programma sql3 descritto in questo capitolo.
- L'applicazione server stabilisce quindi una connessione con il database specificato, che deve trovarsi sulla stessa macchina del server, ed esegue la query SQL su di esso. I risultati vengono restituiti al client nel seguente 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 un errore di esecuzione della query, l'applicazione restituisce
- Una volta eseguita la query, l'applicazione server chiude la connessione.
Problema 2
Crea un'applet Java che interroghi il server descritto sopra. Puoi trarre ispirazione dall'interfaccia grafica dell'Esercizio 1. Poiché la porta del server può variare, verrà inserita tramite l'interfaccia dell'applet. Lo stesso vale per tutti i parametri necessari per inviare la riga:
che il client deve inviare al server.
6.4.3. Esercizio 3
Il testo seguente descrive un problema originariamente pensato per essere risolto in Visual Basic. Adattalo per l'elaborazione in Java all'interno di un'applet. Questa applet si baserà sul server dell'Esercizio 2. L'interfaccia grafica può essere modificata per tenere conto del nuovo contesto di esecuzione.
Proponiamo di creare un'applicazione che metta in evidenza le varie operazioni di aggiornamento possibili su una tabella in un database ACCESS. Il database ACCESS si chiama articles.mdb. Ha una singola tabella denominata articles che elenca gli articoli venduti da un'azienda. La sua struttura è la seguente:
nome | tipo |
codice | codice articolo di 4 caratteri |
nome | il suo nome (stringa) |
prezzo | il suo prezzo (effettivo) |
stock_attuale | scorte attuali (numero intero) |
min_stock | la scorta minima (numero intero) al di sotto della quale l'articolo deve essere rifornito |
Proponiamo di visualizzare e aggiornare questa tabella utilizzando il seguente modulo:

I controlli presenti in questo modulo sono i seguenti:
N. | Tipo | Nome | Funzione |
1 | casella di testo | record | Numero del record visualizzato abilitato è falso |
2 | casella di testo | codice | codice articolo |
3 | casella di testo | nome | nome articolo |
4 | casella di testo | prezzo | prezzo articolo |
5 | casella di testo | attuale | stock attuale dell'articolo |
6 | casella di testo | minimo | scorta minima dell'articolo |
7 | dati | dati1 | Controllo dei dati associato al database databasename=percorso del file articles.mdb recordsource=articoli connect=access |
8 | HScrollBar | posizione | consente di navigare nella tabella |
9 | pulsante | OK | permette di confermare un aggiornamento - appare solo durante un aggiornamento |
10 | pulsante | Annulla | permette di annullare un aggiornamento - appare solo durante l'aggiornamento |
11 | frame | frame1 | per motivi estetici |
12 | casella di testo | nomebase | nome del database aperto enabled è impostato su false |
13 | casella di testo | sourcename | nome della tabella aperta "enabled" è impostato su false |
controllo | Funzionalità speciali |
data1 | I campi databasename e recordsource sono già compilati. databasename deve fare riferimento al database di Access articles.mdb presente nella directory, mentre recordsource alla tabella articles. |
code | Questa è una casella di testo che vogliamo collegare al campo "code" del record corrente in data1. Per farlo, compiliamo due campi: datasource: inserisci data1 per indicare che la casella di testo è collegata alla tabella associata a data1 datafield: selezionare il campo code dalla tabella articles Dopo questi passaggi, la casella di testo "code" conterrà sempre il campo "code" del record corrente in data1. Al contrario, modificando il contenuto di questa casella di testo si aggiornerà il campo "code" del record corrente. Fai lo stesso per le altre caselle di testo |
nome | origine dati: data1 campo dati: nome |
prezzo | origine dati: data1 campo dati: price |
attuale | origine dati: data1 campo dati: stock_attuale |
minimo | fonte dati: data1 campo dati: stock_min |
La struttura del menu è la seguente
Modifica | Sfoglia | Esci |
Aggiungi | Indietro | |
Modifica | Avanti | |
Elimina | Primo | |
Ultimo |
I ruoli delle varie opzioni sono i seguenti:
Menu | nome | funzione |
mnuadd | per aggiungere un nuovo record alla tabella degli articoli | |
delete | per eliminare il record attualmente visualizzato dalla tabella degli elementi | |
modifica | per modificare il record attualmente visualizzato nella tabella degli articoli | |
mnuprecedent | per passare al record precedente | |
giù-successivo | per passare alla registrazione successiva | |
precedente | per passare alla prima traccia | |
mnunlast | per passare all'ultima registrazione | |
mnuquit | per uscire dall'applicazione |
Durante l'evento form_load, viene aperta la tabella associata a data1 (data1.refresh). Se la tabella non si apre, viene visualizzato un messaggio di errore e il programma termina (end). Altrimenti, viene visualizzato il modulo con il primo record della tabella articles visibile. Non è consentito alcun inserimento nei campi (proprietà enabled impostata su false). L'inserimento è possibile solo utilizzando le opzioni Aggiungi e Modifica. I pulsanti OK e Annulla sono nascosti (visible=false).
Proponiamo di creare le procedure relative alle varie opzioni di menu e ai pulsanti OK e Annulla. Per ora, ignoreremo i seguenti punti:
- abilitazione/disabilitazione di determinate opzioni di menu: ad esempio, l'opzione Avanti deve essere disabilitata se il cursore si trova sull'ultimo record della tabella
- gestione della barra di scorrimento 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 del record (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 del record.
Menu Sfoglia/Primo
- Si sposta al primo record (data1.RecordSet.MoveFirst) se il file non è vuoto (data1.RecordSet.RecordCount = 0). Aggiorna la casella di testo del record.
Menu Sfoglia/Ultimo
- Si sposta sull'ultimo record (data1.RecordSet.MoveLast) se il file non è vuoto (data1.RecordSet.RecordCount = 0). Aggiorna la casella di testo del record.
Menu Modifica/Aggiungi
- consente di aggiungere un record alla tabella
- entra in modalità Aggiungi record (data1.recordset.addnew)
- Abilita l'inserimento dei dati nei 5 campi: codice, nome, prezzo, ecc. (enabled=true)
- Disabilita i menu Modifica, Sfoglia ed Esci (enabled=false)
- visualizza i pulsanti OK e Annulla (visible=true)
Pulsante OK
- salva l'aggiornamento di un record (data1.recordset.Update)
- nasconde i pulsanti OK e Annulla (visible=false)
- abilita le opzioni Modifica, Sfoglia ed Esci (enabled=true)
- aggiorna la casella di testo del modulo
Pulsante Annulla
- annulla l'aggiornamento di un record (data1.recordset.CancelUpdate)
- nasconde i pulsanti OK e Annulla (visible=false)
- abilita le opzioni Modifica, Sfoglia ed Esci (enabled=true)
- aggiorna la casella di testo del modulo
Menu Modifica/Modifica
- consente di modificare il record visualizzato nel modulo
- entra in modalità modifica record (data1.recordset.edit)
- Abilita l'immissione nei 4 campi: nome, prezzo, ecc. (enabled=true) ma non nel campo codice (enabled=false)
- disabilita i menu Modifica, Sfoglia ed 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 al record successivo (data1.recordset.movenext)
Menu Esci
- scarica il modulo (unload me)
evento form_unload (cancel come numero intero)
- innescato dall'operazione unload me o dalla chiusura del modulo tramite Alt-F4 o un doppio clic sull'icona nella barra delle applicazioni, quindi non necessariamente dall'opzione di uscita.
- visualizza la domanda “Vuoi davvero uscire dall’applicazione?” con due pulsanti Sì/No (msgbox con style=vbyes+vbno)
- se la risposta è No (=vbno), imposta cancel a -1 ed esci dalla procedura form_unload. cancel impostato a -1 indica che la chiusura della finestra è negata.
- Se la risposta è Sì (=vbyes), il database viene chiuso (data1.recordset.close, data1.database.close).
Qui ci concentriamo sull'abilitazione/disabilitazione dei menu. Dopo ogni operazione che modifica il record corrente, chiameremo una procedura che chiameremo Oueston. Questa procedura verificherà le seguenti condizioni:
. Se il file è vuoto,
- disattiveremo i menu Sfoglia, Modifica/Elimina,
- abilitiamo gli altri
. Se il record corrente è il primo,
- disabilitiamo Sfoglia/Precedente
- lascerà il resto
. Se il record corrente è l'ultimo,
- disattiva Sfoglia/Avanti
- consentirà il resto
Un cursore orizzontale presenta tre campi importanti:
- min: il suo valore minimo
- max: il suo valore massimo
- value: il suo valore attuale
Inizializzazione del cursore
Al caricamento (form_load), il cursore verrà inizializzato come segue:
- min=0
- max=data1.recordset.recordcount-1
- valore=1
Si noti che immediatamente dopo l'apertura del database (data1.refresh), il numero di record nella tabella rappresentato da data1.recordset.recordcount non è corretto. È necessario spostarsi alla fine della tabella (MoveLast), quindi tornare all'inizio della tabella (MoveFirst) affinché sia corretto.
Azione diretta sull'unità
La posizione del cursore del cursore rappresenta la posizione nella tabella.
Quando l'utente modifica il cursore (qui denominato position), viene attivato l'evento position_change. In questo evento, modificheremo il record corrente nella tabella in modo che rifletta il movimento effettuato sul cursore. Per farlo, useremo il campo absoluteposition di data1.recordset. Quando assegniamo il valore i a questo campo, il record n. i nella tabella diventa il record corrente. I record sono numerati a partire da 0 e quindi hanno un numero compreso nell'intervallo [0,data1.recordset.recordcount-1]. Nella procedura position_change, scriviamo semplicemente
in modo che il record corrente visualizzato nel modulo rifletta il movimento effettuato sul joystick.
Una volta fatto ciò, chiameremo la procedura Oueston per aggiornare i menu.
Aggiornamento del DCS
Poiché la posizione del cursore dell'unità deve riflettere la posizione nella tabella, il valore dell'unità deve essere aggiornato ogni volta che si verifica una modifica nel record corrente della tabella, innescata da uno dei menu. Poiché ciascuno di questi richiama la procedura Oueston, è meglio inserire questo aggiornamento all'interno di quella procedura. Basta scrivere qui:
Aggiungiamo l'opzione Sfoglia/Cerca, che consente all'utente di visualizzare un articolo inserendone il codice.
Quando questa opzione è abilitata, si verificano i seguenti passaggi:
- il sistema passa alla modalità Aggiungi nuovo (Addnew), esclusivamente per evitare di modificare il record corrente che era aperto quando l'opzione è stata abilitata,
- Abilitiamo l'inserimento nel campo del codice e cancelliamo il contenuto del campo del record,
- i menu vengono disabilitati e vengono visualizzati i pulsanti OK e Annulla
- Quando l'utente fa clic su OK, dobbiamo cercare il record corrispondente al codice inserito 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 "state", che avrà tre possibili valori: "add", "modify" e "search". Le procedure collegate ai pulsanti OK e Annulla useranno questa variabile per determinare il contesto in cui vengono chiamate.
- Se state è “search”, nella procedura collegata a OK,
- annulleremo l’operazione di aggiunta (data1.recordset.cancelupdate) poiché non era nostra intenzione aggiungere un record. Si noti che il record corrente tornerà quindi a quello visualizzato sullo schermo prima dell’operazione Sfoglia/Cerca.
- Costruire i criteri di ricerca in base al codice e avviare la ricerca (data1.recordset.findfirst criteria),
- se la ricerca fallisce (data1.recordset.nomatch=true), avvisiamo l'utente, quindi torniamo alla modalità di aggiunta (addnew) e usciamo dalla procedura OK. L'utente dovrà reinserire un nuovo codice o selezionare l'opzione Annulla.
- Se la ricerca ha esito positivo, il record trovato diventa il nuovo record attivo. Ripristiniamo i menu, nascondiamo i pulsanti OK/Annulla, disabilitiamo l'immissione nel campo del codice ed usciamo dalla procedura.
- . Se lo stato è "search", nella procedura collegata a Annulla,
- annulliamo l’operazione addnew (data1.recordset.cancelupdate). Il sistema tornerà quindi automaticamente al record che era attivo prima dell’operazione Sfoglia/Cerca.
- Ripristiniamo i menu, nascondiamo i pulsanti OK/Annulla, disabilitiamo l'immissione nel campo codice ed usciamo 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à esistente nella tabella.
6.4.4. Esercizio 4
Qui presentiamo un'applicazione web basata sul server dell'Esercizio 2. Si tratta di un'applicazione di e-commerce di base.
Il cliente ordina gli articoli utilizzando la seguente interfaccia web:

Può eseguire le seguenti operazioni:
- selezionare un articolo dall'elenco a discesa
- specificare la quantità desiderata
- confermare l'acquisto cliccando sul pulsante Acquista
- Il loro acquisto viene visualizzato nell'elenco degli articoli acquistati
- può rimuovere articoli da questo elenco selezionando un articolo e cliccando sul pulsante Rimuovi
- Quando clicca sul pulsante Riepilogo, vede il seguente riepilogo:

Il riepilogo consente all'utente di visualizzare i dettagli della propria fattura. L'utente può accedere ai dettagli relativi a una voce selezionata nell'elenco a discesa cliccando sul pulsante "Informazioni":

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 utilizzando l'apposito pulsante.

Prima di inviare l'ordine, l'app richiede una conferma:

Una volta confermato l'ordine, l'app lo elabora e visualizza una pagina di conferma:

In realtà, l'app non elabora l'ordine. Si limita a inviare un'e-mail all'utente chiedendogli di pagare i propri 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: Crea l'equivalente di questa applicazione web utilizzando un'applet Java.

