Skip to content

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:

Image

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

Image

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:

Image

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:

  1. Connessione al database
  2. Invio di query SQL al database
  3. Ricezione ed elaborazione dei risultati di tali query
  4. 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:

Image

Image

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

Image

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:

Connection DriverManager.getConnection(String URL, String id, String mdp)

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:

    Class.forName(String nomClasse)

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:

    import java.sql.*;

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:

    Statement requete=connexion.CreateStatement();

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:

    select col1, col2,... from table1, table2,...
    where condition
    order by expression
    ...

Solo le parole chiave della prima riga sono obbligatorie, le altre sono facoltative. Esistono altre parole chiave non riportate qui.

  1. Viene eseguito un join con tutte le tabelle che seguono la parola chiave from
  2. Vengono mantenute solo le colonne che seguono la parola chiave `select`
  3. Vengono mantenute solo le righe che soddisfano la condizione specificata dalla parola chiave `where`
  4. 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:

    ResultSet executeQuery(String requête)

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:

    boolean 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:

Type getType("coli") 

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

Type getType(i) 

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:

    RS.getMetaData()

Si notino due metodi utili nella classe ResultSetMetaData:

  1. int getColumnCount(), che restituisce il numero di colonne del ResultSet
  2. 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:

    int executeUpdate(String requête)

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:

    boolean execute(String requête)

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:

E:\data\java\jdbc\0>java sql3 sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles <requetes >results

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 é 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 é 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 é 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:

Image

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

DSN
il nome DSN della fonte dati ODBC

login
l’identità di un utente con diritto di lettura sul database

motdepasse
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

Image

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

Image

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

Image

  • 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:

    private double[] limites, coeffR, coeffN;

la classe impotsJDBC non ha accesso diretto a questi campi. Apportiamo quindi una prima modifica alla classe base scrivendo:

  protected double[] limites=null;
  protected double[] coeffR=null;
  protected double[] coeffN=null;

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:

   // costruttore vuoto
  protected 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:

Image

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
200 - Bienvenue - Envoyez votre requête
  • il client invia quindi i parametri necessari per la connessione a un database e la query che desidera eseguire nel formato:
Pilote Java/URL base/UID/MDP/requête SQL

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:
100 - ligne1
100 - ligne2

...

se si tratta del risultato di una query Select oppure

101 - nbLignes

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

500 - Message d’erreur
  • 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:

Pilote Java/URL base/UID/MDP/requête SQL

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:

Image

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

Creazione del foglio in VB

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

I menu

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
Ajouter
mnuaggiungere
per aggiungere un nuovo record alla tabella articoli
Supprimer
mnusupprimer
per eliminare dalla tabella articoli il record attualmente visualizzato
Modifier
mnumodifica
per modificare, nella tabella articoli, il record attualmente visualizzato
Précédent
mnuprecedent
per passare al record precedente
Suivant
mnusuivant
per passare alla registrazione successiva
Premier
precedente
per passare al primo record
Dernier
mnudernier
per passare all'ultimo record
Quitter
mnuquitter
per uscire dall'applicazione

Caricamento del foglio

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).


Parte 1

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).

Parte 2 - Gestione dei menu

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

Parte 3 - Gestione del variatore orizzontale

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

    data1.recordset.absoluteposition=position.value

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:

    position.value=data1.recordset.absoluteposition

Parte 4 - Opzione Cerca

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.

Parte 5 - Gestione del codice

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:

Image

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:

Image

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:

Image

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.

Image

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

Image

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

Image

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.