Skip to content

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:

Image

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

Image

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:

Image

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:

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

Image

Image

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

Image

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:

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

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:

    Class.forName(String nomClasse)

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:

    import java.sql.*;

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:

    Statement requete=connexion.CreateStatement();

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:

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

Sono obbligatorie solo le parole chiave nella prima riga; le altre sono facoltative. Esistono altre parole chiave non riportate qui.

  1. Viene eseguito un join con tutte le tabelle elencate dopo 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 della parola chiave `where`
  4. 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 &lt; 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:

    ResultSet executeQuery(String requête)

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:

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

Type getType("coli") 

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

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.*;

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

    RS.getMetaData()

Nella classe ResultSetMetaData sono presenti due metodi utili:

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

    int executeUpdate(String requête)

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:

    boolean execute(String requête)

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:

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

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

Image

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

DSN
che è il nome DSN dell'origine dati ODBC
login
l'identità di un utente con accesso in lettura al database
password
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

Image

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

Image

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

Image

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

    private double[] limites, coeffR, coeffN;

la classe impotsJDBC non ha accesso diretto a questi campi. Apportiamo quindi una modifica iniziale 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 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:

  // constructeur vide
  protected 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:

Image

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

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:
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 un errore di esecuzione della query, l'applicazione restituisce

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

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

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:

Image

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

Creazione del foglio in VB

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

I menu

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
Aggiungi
mnuadd
per aggiungere un nuovo record alla tabella degli articoli
Elimina
delete
per eliminare il record attualmente visualizzato dalla tabella degli elementi
Modifica
modifica
per modificare il record attualmente visualizzato nella tabella degli articoli
Precedente
mnuprecedent
per passare al record precedente
Successivo
giù-successivo
per passare alla registrazione successiva
Prima
precedente
per passare alla prima traccia
Ultima
mnunlast
per passare all'ultima registrazione
Esci
mnuquit
per uscire dall'applicazione

Caricamento del foglio

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


Parte 1

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

Parte 2 - Gestione dei menu

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

Parte 3 - Gestione del cursore orizzontale

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

    data1.recordset.absoluteposition=position.value

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:

    position.value=data1.recordset.absoluteposition

Parte 4 - Opzione di ricerca

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.

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

Image

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:

Image

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

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

Image

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

Image

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

Image

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.