Skip to content

6. Databasebeheer met de API JDBC

6.1. Algemeen

Er zijn talrijke databases op de markt. Om de toegang tot databases onder MS Windows te standaardiseren, heeft Microsoft een interface ontwikkeld met de naam ODBC (Open DataBase Connectivity). Deze laag verbergt de specifieke kenmerken van elke database achter een standaardinterface. Onder Windows zijn er talrijke stuurprogramma’s die de toegang tot databases vergemakkelijken. Hier volgt bijvoorbeeld een lijst met stuurprogramma’s die op een Win95-computer zijn geïnstalleerd:

Image

Een toepassing die gebruikmaakt van deze stuurprogramma’s kan alle bovengenoemde databases gebruiken zonder dat er aanpassingen nodig zijn.

Image

Om ervoor te zorgen dat ook Java-toepassingen gebruik kunnen maken van de interface ODBC, heeft Sun de interface JDBC (Java DataBase Connectivity) ontwikkeld, die tussen de Java-toepassing en de interface ODBC wordt geplaatst:

Image

6.2. Belangrijke stappen bij het gebruik van databases

6.2.1. Inleiding

In een JAVA-applicatie die gebruikmaakt van een database met de interface JDBC, zijn doorgaans de volgende stappen te vinden:

  1. Verbinding maken met de database
  2. Verzenden van SQL-query's naar de database
  3. Ontvangst en verwerking van de resultaten van deze query's
  4. Het verbreken van de verbinding

Stappen 2 en 3 worden herhaaldelijk uitgevoerd; de verbinding wordt pas verbroken aan het einde van het gebruik van de database. Dit is een relatief klassiek patroon waarmee u wellicht vertrouwd bent als u al eens interactief met een database hebt gewerkt. We zullen elk van deze stappen aan de hand van een voorbeeld nader toelichten. We nemen een database ACCESS met de naam ARTICLES en met de volgende structuur:

naam
type
code
artikelcode van 4 tekens
nom
de naam (tekenreeks)
prix
de prijs (werkelijke)
stock_actu
de huidige voorraad (geheel getal)
stock_mini
de minimumvoorraad (geheel getal) waaronder het artikel moet worden bijgevuld

Deze database ACCESS is gedefinieerd als „gebruikers”-gegevensbron in de databasemanager ODBC:

Image

Image

De kenmerken ervan worden via de knop Configurer als volgt gespecificeerd:

Image

Deze configuratie bestaat in wezen uit het koppelen van de database ARTICLES aan het bijbehorende Access-bestand articles.mdb. Zodra dit is gebeurd, is de database ARTICLES toegankelijk voor applicaties die gebruikmaken van de interface ODBC.

6.2.2. De verbindingsfase

Om een database te kunnen gebruiken, moet een Java-toepassing eerst een verbindingsfase doorlopen. Dit gebeurt met de volgende klassemethode:

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

met

DriverManager: Java-klasse die de lijst met beschikbare stuurprogramma’s voor de applicatie bevat

Connection: Java-klasse die een verbinding tot stand brengt tussen de applicatie en de database, waardoor de applicatie SQL-verzoeken naar de database kan verzenden en resultaten kan ontvangen

URL: naam die de database identificeert. Deze naam is vergelijkbaar met de URL van het internet. Daarom maakt hij deel uit van de klasse URL. Het internet speelt hier echter geen enkele rol. De URL heeft de volgende vorm:

jdbc:nom_du_pilote:nom_de_la_source;param=val1;param2=val2

In onze voorbeelden, waarin uitsluitend ODBC-stuurprogramma’s worden gebruikt, heet het stuurprogramma odbc. Het derde deel van de URL bestaat uit de naam van de bron met eventuele parameters. In onze voorbeelden gaat het om ODBC-bronnen die bij het systeem bekend zijn. De URL van de eerder gedefinieerde gegevensbron Articles zal dus

jdbc:odbc:Artikelen

id: gebruikersnaam (login)

mdp: wachtwoord van de gebruiker (password)

Kortom, het programma maakt verbinding met een database:

  • die wordt aangeduid met een naam (URL)
  • onder de identiteit van een gebruiker (id, wachtwoord)

Als deze drie parameters correct zijn en als de driver bestaat die de verbinding van de Java-applicatie met de opgegeven database kan tot stand brengen, dan wordt er een verbinding tot stand gebracht tussen de Java-applicatie en de database. Deze verbinding wordt voor het programma gerealiseerd door het object van het type Connection dat wordt geretourneerd door de klasse DriverManager. Aangezien deze verbinding om verschillende redenen kan mislukken, kan er een uitzondering worden gegenereerd. We schrijven daarom:


    Connection connexion=null;
    URL base=...;
    String id=...;
    String mdp=...;
try{
        connexion=DriverManager.getConnection(base,id,mdp);
} catch (Exception e){
        // de uitzondering afhandelen
}

Om verbinding te kunnen maken met een database, moet de juiste driver beschikbaar zijn. In onze voorbeelden is dat de driver ODBC, die de gevraagde database kan beheren. Deze driver moet niet alleen beschikbaar zijn in de lijst met ODBC-drivers op de machine, maar er moet ook de klasse JAVA aanwezig zijn, die als interface met de driver fungeert. Hiervoor vraagt de applicatie de benodigde klasse op de volgende manier op:

    Class.forName(String nomClasse)

De klasse Class heeft op geen enkele manier te maken met de interface JDBC. Het is een algemene klasse voor het beheer van klassen. Met de statische methode forName kan een klasse dynamisch worden geladen, waardoor gebruik kan worden gemaakt van de statische attributen en methoden ervan. De klasse die als interface fungeert voor de stuurprogramma’s ODBC van MS Windows heet „sun.jdbc.odbc.JdbcOdbcDriver”. We schrijven dus (de methode kan een uitzondering genereren):

try{
    Class.forName(« sun.jdbc.odbc.JdbcOdbcDriver ») ;
} catch (Exception e){
    // de uitzondering afhandelen (klasse bestaat niet)
}

De klassen die nodig zijn voor de interface JDBC bevinden zich in het pakket java.sql. Aan het begin van het programma schrijft u dus:

    import java.sql.*;

Hier volgt een programma waarmee verbinding kan worden gemaakt met een database:

import java.sql.*;
import java.io.*;

// aanroep: pg PILOTE URL UID MDP
// maakt verbinding met de database URL via de klasse JDBC PILOTE
// de gebruiker UID wordt geïdentificeerd met een wachtwoord MDP

public class connexion1{
    static String syntaxe="pg PILOTE URL UID MDP";

    public static void main(String arg[]){
        // controle van het aantal argumenten
        if(arg.length<2 || arg.length>4)
            erreur(syntaxe,1);
        // verbinding met de 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);
        }
        // de database afsluiten
        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);
    }
}// klasse    

Hier volgt een voorbeeld van de uitvoering:

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. Verzoeken naar de database verzenden

De interface JDBC maakt het mogelijk om SQL-query's te verzenden naar de database die is gekoppeld aan de Java-applicatie, en de resultaten van deze query's te verwerken. De taal SQL (Structured Query Language) is een gestandaardiseerde querytaal voor relationele databases. Er worden verschillende soorten query's onderscheiden:

  • query’s voor het opvragen van gegevens uit de database (SELECT)
  • query's voor het bijwerken van de database (INSERT, DELETE, UPDATE)
  • query's voor het aanmaken/verwijderen van tabellen (CREATE, DELETE)

Hierbij wordt ervan uitgegaan dat de lezer bekend is met de basisbeginselen van de taal SQL.

6.2.3.1. De klasse Statement

Om een willekeurige SQL-query naar een database te verzenden, moet de Java-toepassing beschikken over een object van het type Statement. Dit object slaat onder andere de tekst van de query op. Dit object is noodzakelijkerwijs gekoppeld aan de lopende verbinding. Het is dus een methode van de tot stand gebrachte verbinding waarmee de Statement-objecten kunnen worden aangemaakt die nodig zijn voor het verzenden van SQL-query’s. Als connexion het object is dat de verbinding met de database vertegenwoordigt, wordt een Statement-object als volgt verkregen:

    Statement requete=connexion.CreateStatement();

Zodra een object Statement is verkregen, kunnen SQL-query's worden verzonden. Dit gebeurt op verschillende manieren, afhankelijk van of de query een opvraag- of een bijwerkingsquery is.

6.2.3.2. Een opvraagverzoek naar de database verzenden

Een opvraagverzoek is doorgaans een verzoek van het type:

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

Alleen de trefwoorden op de eerste regel zijn verplicht, de overige zijn optioneel. Er zijn nog andere trefwoorden die hier niet worden weergegeven.

  1. Er wordt een join uitgevoerd met alle tabellen die achter het sleutelwoord `from` staan
  2. Alleen de kolommen die achter het sleutelwoord `select` staan, worden behouden
  3. Alleen de rijen die voldoen aan de voorwaarde van het sleutelwoord `where` worden behouden
  4. De resulterende rijen, gesorteerd volgens de uitdrukking in het sleutelwoord `order by`, vormen het resultaat van de query.

Het resultaat van een select is een tabel. Als we de vorige tabel ARTICLES als uitgangspunt nemen en we willen de namen van de artikelen waarvan de huidige voorraad onder de minimumdrempel ligt, dan schrijven we: *select naam from artikelen where stock\_actu&lt;stock\_mini*. Als we ze in alfabetische volgorde van de namen willen, schrijven we: select naam from artikelen where stock_actu<stock_mini order by naam

Om dit soort query's uit te voeren, biedt de klasse Statement de methode executeQuery:

    ResultSet executeQuery(String requête)

waarbij requête de tekst is van de query SELECT die moet worden verzonden.

Dus als

  • connexion het object is dat de verbinding met de database symboliseert
  • dan creëert de instructie s=connexion.createStatement() het object Statement dat nodig is voor het verzenden van de query’s SQL
  • ResultSet rs=s.executeQuery(« select naam from artikelen where stock_actu<stock_mini”) voert een query select uit en wijst de resultatenrijen van de query toe aan een object van het type ResultSet.

6.2.3.3. De klasse ResultSet: resultaat van een SELECT-query

Een object van het type ResultSet vertegenwoordigt een tabel, dat wil zeggen een verzameling rijen en kolommen. Op een bepaald moment heeft men slechts toegang tot één rij van de tabel, de zogenaamde huidige rij. Bij de eerste aanmaak van het ResultSet-object is de huidige rij rij nr. 1, mits het ResultSet-object niet leeg is. Om naar de volgende rij te gaan, beschikt de klasse ResultSet over de methode next:

    boolean next()

Deze methode probeert naar de volgende regel van ResultSet te gaan en retourneert true als dit lukt, false anders. Als dit lukt, wordt de volgende regel de nieuwe huidige regel. De vorige regel gaat verloren en kan niet meer worden teruggehaald. De tabel van ResultSet bevat de kolommen col1, col2,.... Om de verschillende velden van de huidige regel te benutten, zijn de volgende methoden beschikbaar:

Type getType("coli") 

om het veld „coli” van de huidige regel op te halen. Type geeft het type van het veld coli aan. De methode getString wordt vrij vaak op alle velden toegepast, waardoor de inhoud van het veld als tekenreeks wordt verkregen. Vervolgens wordt deze indien nodig geconverteerd. Als de kolomnaam onbekend is, kunnen de methoden

Type getType(i) 

waarbij i de index van de gewenste kolom is (i>=1).

6.2.3.4. Een eerste voorbeeld

Hier volgt een programma dat de inhoud weergeeft van de eerder aangemaakte database ARTICLES:

import java.sql.*;
import java.io.*;

// de inhoud van een systeemdatabase weergeven ARTICLES

public class articles1{

    static final String DB="ARTICLES";        // te gebruiken database

    public static void main(String arg[]){

        Connection connect=null;        // verbinding met de database
        Statement S=null;                // object voor het verzenden van query's
        ResultSet RS=null;            // resultatentabel van een query
        try{
            // verbinding met de database
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
            System.out.println("Connexion avec la base " + DB + " établie");
            // aanmaken van een Statement-object
            S=connect.createStatement();
            // uitvoering van een SELECT-query
            RS=S.executeQuery("select * from " + DB);
            // verwerking van de resultatentabel
            while(RS.next()){                // zolang er nog een rij is om te verwerken
                // wordt deze op het scherm weergegeven
                System.out.println(RS.getString("code")+","+
                    RS.getString("nom")+","+
                    RS.getString("prix")+","+
                    RS.getString("stock_actu")+","+
                    RS.getString("stock_mini"));
            }// volgende rij
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // de database sluiten
        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);
    }
}// klasse    

De verkregen resultaten zijn als volgt:

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. De klasse ResultSetMetadata

In het vorige voorbeeld zijn de kolomnamen van ResultSet bekend. Als deze niet bekend zijn, kan de methode getType(nom_colonne) niet worden gebruikt. In plaats daarvan gebruiken we getType(kolomnummer). Om echter alle kolommen te verkrijgen, zouden we moeten weten hoeveel kolommen de verkregen ResultSet heeft. De klasse ResultSet geeft ons deze informatie niet. Het is de klasse ResultSetMetaData die ons deze informatie geeft. Meer in het algemeen geeft deze klasse ons informatie over de structuur van de tabel, dat wil zeggen over de aard van de kolommen.

We krijgen toegang tot de informatie over de structuur van een ResultSet door eerst een ResultSetMetaData-object te instantiëren. Als RS een ResultSet is, wordt de bijbehorende ResultSetMetaData verkregen via:

    RS.getMetaData()

Er zijn twee nuttige methoden in de klasse ResultSetMetaData:

  1. int getColumnCount(), die het aantal kolommen van de ResultSet retourneert
  2. String getColumnLabel(int i), die de naam van kolom i van ResultSet retourneert (i >= 1)

6.2.3.6. Een tweede voorbeeld

Het vorige programma gaf de inhoud van de database ARTICLES weer. Hier schrijven we een programma dat op de database ARTICLES elke query SQL Select uitvoert die de gebruiker via het toetsenbord invoert.

import java.sql.*;
import java.io.*;

// toont de inhoud van een systeemdatabase ARTICLES

public class sql1{

    static final String DB="ARTICLES";        // te gebruiken database

    public static void main(String arg[]){

        Connection connect=null;        // verbinding met de database
        Statement S=null;                // object voor het verzenden van query's
        ResultSet RS=null;            // resultatentabel van een query
        String select;                    // tekst van de query SQL select
        int nbColonnes;                // aantal kolommen van ResultSet

        // aanmaken van een toetsenbordinvoer
        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{
            // verbinding met de database
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
            System.out.println("Connexion avec la base " + DB + " établie");
            // aanmaken van een Statement-object
            S=connect.createStatement();
            // lus voor het uitvoeren van de via het toetsenbord ingevoerde SQL-query's
            System.out.print("Requête : ");
            select=in.readLine();
            while(!select.equals("fin")){
                // uitvoering van de query
                RS=S.executeQuery(select);
                // aantal kolommen
                nbColonnes=RS.getMetaData().getColumnCount();
                // verwerking van de resultatentabel
                System.out.println("Résultats obtenus\n\n");
                while(RS.next()){                // zolang er nog een rij te verwerken is
                    // wordt deze op het scherm weergegeven
                    for(int i=1;i<nbColonnes;i++)
                        System.out.print(RS.getString(i)+",");
                    System.out.println(RS.getString(nbColonnes));
                }// volgende rij
                // volgende query
                System.out.print("Requête : ");
                select=in.readLine();                
            }// while
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // de database en de invoerstroom sluiten
        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);
    }
}// klasse    

Hier zijn enkele verkregen resultaten:

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. Een verzoek indienen om de database bij te werken

Een object van het type Statement maakt het mogelijk om SQL-verzoeken op te slaan. De methode die dit object gebruikt om SQL-updateverzoeken te verzenden (INSERT, UPDATE, DELETE) is niet langer de eerder besproken methode executeQuery, maar de methode executeUpdate:

    int executeUpdate(String requête)

Het verschil zit in het resultaat: terwijl executeQuery de resultatentabel (ResultSet) retourneerde, retourneert executeUpdate het aantal rijen dat door de updatebewerking is beïnvloed.

6.2.3.8. Een derde voorbeeld

We nemen het vorige programma weer op en passen het enigszins aan: de via het toetsenbord ingevoerde query’s zijn nu updatequery’s voor de database ARTICLES.

import java.sql.*;
import java.io.*;

// toont de inhoud van een systeemdatabase ARTICLES

public class sql2{

    static final String DB="ARTICLES";    // te gebruiken database

    public static void main(String arg[]){

        Connection connect=null;        // verbinding met de database
        Statement S=null;                // object voor het verzenden van query's
        ResultSet RS=null;            // resultatentabel van een query
        String sqlUpdate;                // tekst van de updatequery SQL
        int nbLignes;                    // aantal rijen dat door een update is beïnvloed

        // aanmaken van een toetsenbordinvoer
        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{
            // verbinding met de database
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
            System.out.println("Connexion avec la base " + DB + " établie");
            // aanmaken van een Statement-object
            S=connect.createStatement();
            // lus voor het uitvoeren van via het toetsenbord ingevoerde SQL-query's
            System.out.print("Requête : ");
            sqlUpdate=in.readLine();
            while(!sqlUpdate.equals("fin")){
                // uitvoering van de query
                nbLignes=S.executeUpdate(sqlUpdate);
                // opvolging
                System.out.println(nbLignes + " ligne(s) ont été mises à jour");
                // volgende zoekopdracht
                System.out.print("Requête : ");
                sqlUpdate=in.readLine();                
            }// while
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // de database en de invoerstroom sluiten
        try{
            // de aan de database gekoppelde bronnen worden vrijgegeven
            RS.close();
            S.close();
            connect.close();
            System.out.println("Base " + DB + " fermée");
            // toetsenbordstroom uitschakelen
            in.close();
        } catch (Exception e){}
    }// hand

    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }
}// klasse    

Hieronder ziet u het resultaat van verschillende uitvoeringen van de programma’s sql1 en sql2:

Lijst met regels uit de 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

We wijzigen enkele regels:


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

Controle:


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

We voegen een regel toe:


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

Controle:


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

Een regel wordt verwijderd:


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

Controle:


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. Een willekeurig SQL-verzoek verzenden

Het object Statement dat nodig is voor het verzenden van verzoeken SQL beschikt over een methode execute waarmee elk type verzoek SQL kan worden uitgevoerd:

    boolean execute(String requête)

Het geretourneerde resultaat is de booleaanse waarde true als de query een ResultSet (executeQuery) heeft geretourneerd, en false als deze een getal heeft geretourneerd (executeUpdate). De verkregen waarde ResultSet kan worden opgehaald met de methode getResultSet en het aantal bijgewerkte rijen met de methode getUpdateCount. We schrijven dus:


Statement S=...;
ResultSet RS=...;
int nbLignes;
String requête=...;
// uitvoering van een query SQL
if (S.execute(requête)){
    // er is een resultatenreeks
    RS=S.getResultSet();
    // verwerking van de ResultSet
    ...
} else {
    // dit was een update-query
    nbLignes=S.getUpdateCount();
    ...
}

6.2.3.10. Vierde voorbeeld

We passen de opzet van de programma's sql1 en sql2 toe in een programma sql3 dat nu elke via het toetsenbord ingevoerde SQL-opdracht kan uitvoeren. Om het programma algemener te maken, worden de kenmerken van de te doorzoeken database als parameters aan het programma doorgegeven.

import java.sql.*;
import java.io.*;

// aanroep: pg PILOTE URL UID MDP
// maakt verbinding met de database URL via de klasse JDBC PILOTE
// de gebruiker UID wordt geïdentificeerd met een wachtwoord MDP

public class sql3{

    static String syntaxe="pg PILOTE URL UID MDP";

    public static void main(String arg[]){



        // controle van het aantal argumenten
        if(arg.length<2 || arg.length>4)
            erreur(syntaxe,1);

        // initieer verbindingsparameters
        Connection connect=null;
        String uid="";
        String mdp="";
        if(arg.length>=3) uid=arg[2];
        if(arg.length==4) mdp=arg[3];        

        // overige gegevens
        Statement S=null;                        // object voor het verzenden van verzoeken
        ResultSet RS=null;                    // resultatentabel van een query
        String sqlText;                            // tekst van de uit te voeren query SQL
        int nbLignes;                                // aantal rijen waarop een update van invloed is
        int nbColonnes;                            // aantal kolommen van een ResultSet

        // aanmaken van een toetsenbordinvoer
        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{
            // verbinding met de database
            Class.forName(arg[0]);
            connect=DriverManager.getConnection(arg[1],uid,mdp);
            System.out.println("Connexion avec la base " + arg[1] + " établie");
            // een Statement-object aanmaken
            S=connect.createStatement();
            // lus voor het uitvoeren van de via het toetsenbord ingevoerde SQL-query's
            System.out.print("Requête : ");
            sqlText=in.readLine();
            while(!sqlText.equals("fin")){
                // uitvoering van de query
                try{
                    if(S.execute(sqlText)){
                        // we hebben een ResultSet verkregen – we verwerken deze
                        RS=S.getResultSet();
                        // aantal kolommen
                        nbColonnes=RS.getMetaData().getColumnCount();
                        // verwerking van de resultatentabel
                        System.out.println("\nRésultats obtenus\n-----------------\n");
                        while(RS.next()){                // zolang er nog een rij is om te verwerken
                            // wordt deze op het scherm weergegeven
                            for(int i=1;i<nbColonnes;i++)
                                System.out.print(RS.getString(i)+",");
                            System.out.println(RS.getString(nbColonnes));
                        }// volgende rij van ResultSet
                    } else {
                        // dit was een updateverzoek
                        nbLignes=S.getUpdateCount();
                        // vervolg
                        System.out.println(nbLignes + " ligne(s) ont été mises à jour");
                    }//if
                } catch (Exception e){
                    System.out.println("Erreur " +e);
                }
                // volgend verzoek
                System.out.print("\nNouvelle Requête : ");
                sqlText=in.readLine();                
            }// while
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // de database en de invoerstroom sluiten
        try{
            // de aan de database gekoppelde bronnen worden vrijgegeven
            RS.close();
            S.close();
            connect.close();
            System.out.println("Base " + arg[1] + " fermée");
            // toetsenbordstroom afsluiten
            in.close();
        } catch (Exception e){}
    }// main

    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }
}// klasse    

We stellen het volgende querybestand op:

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

Het programma wordt als volgt gestart:

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

Het programma leest dus de invoer uit het bestand requetes en schrijft de uitvoer naar het bestand results. De verkregen resultaten zijn als volgt:

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 met een database

De laatste keer dat we het probleem van de belastingberekening behandelden, was dat met een grafische interface en werden de gegevens in een bestand opgeslagen. We pakken deze versie opnieuw op, maar gaan er nu vanuit dat de gegevens in een ODBC-MySQL-database staan. MySQL is een SGBD uit het publieke domein die op verschillende platforms kan worden gebruikt, waaronder Windows en Linux. Met dit SGBD is een database dbimpots aangemaakt met daarin één enkele tabel genaamd impots. De toegang tot de database wordt beheerd via een gebruikersnaam en wachtwoord, in dit geval admimpots/mdpimpots. De schermafbeelding laat zien hoe de database dbimpots met MySQL kan worden gebruikt:


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

De grafische interface van de applicatie ziet er als volgt uit:

Image

De grafische interface heeft enkele wijzigingen ondergaan:

nr.
type
naam
rol
1
JTextField
txtConnexion
databaseverbindingsstring ODBC
2
JScrollPane
JScrollPane1
container voor het tekstveld 3
3
JTextArea
txtStatus
geeft statusberichten weer, met name foutmeldingen

De in (1) ingevoerde aanmeldingsreeks ziet er als volgt uit: DSN;login;wachtwoord met

DSN
de naam DSN van de gegevensbron ODBC

login
de identiteit van een gebruiker met leesrechten voor de database

motdepasse
zijn wachtwoord

De database dbimpots is handmatig aangemaakt met MySQL. Deze wordt op de volgende manier omgezet naar de gegevensbron ODBC:

  • we starten de 32-bits gegevensbronbeheerder ODBC

Image

  • gebruik de knop [Add] om een nieuwe gegevensbron ODBC toe te voegen

Image

  • we selecteren de driver MySQL en voeren [Terminer] uit

Image

  • Het stuurprogramma MySQL vraagt om een aantal gegevens:
1
de naam DSN die aan de gegevensbron ODBC moet worden gegeven – kan willekeurig zijn
2
de machine waarop SGBD MySQL draait – in dit geval localhost. Het is interessant om op te merken dat de database een externe database zou kunnen zijn. Lokale applicaties die de gegevensbron ODBC gebruiken, zouden hier niets van merken. Dit zou met name het geval zijn voor onze Java-applicatie.
3
de te gebruiken database MySQL. MySQL is een SGBD die relationele databases beheert; dit zijn verzamelingen van tabellen die onderling via relaties met elkaar zijn verbonden. Hier wordt de naam van de beheerde database opgegeven.
4
de naam van een gebruiker die toegangsrechten heeft tot deze database
5
zijn wachtwoord

Zodra de gegevensbron ODBC is gedefinieerd, kunnen we ons programma testen:

 

Laten we eens kijken naar de code die, in vergelijking met de grafische versie zonder database, is aangepast. Laten we de code van de klasse impots die tot nu toe is gebruikt, nog eens bekijken:

// aanmaken van een klasse 'belastingen'

public class impots{

    // de gegevens die nodig zijn voor de berekening van de belasting
     // zijn afkomstig van een externe bron

    private double[] limites, coeffR, coeffN;

     // constructor
    public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
         // er wordt gecontroleerd of de drie tabellen dezelfde grootte hebben
        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+")");
        // het klopt
        this.limites=LIMITES;
        this.coeffR=COEFFR;
        this.coeffN=COEFFN;
    }//fabrikant

     // berekening van de belasting
    public long calculer(boolean marié, int nbEnfants, int salaire){
         // berekening van het aantal aandelen
        double nbParts;
        if (marié) nbParts=(double)nbEnfants/2+2;
        else nbParts=(double)nbEnfants/2+1;
        if (nbEnfants>=3) nbParts+=0.5;
         // berekening belastbaar inkomen & gezinsquotiënt
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // belastingberekening
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // resultaat weergeven
        return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
    }//berekenen
}//klasse

Deze klasse genereert de drie tabellen limites, coeffR en coeffN op basis van drie tabellen die als parameters aan de constructor worden doorgegeven. We besluiten om een nieuwe constructor toe te voegen waarmee dezelfde drie tabellen vanuit een database kunnen worden gegenereerd:

  public impots(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: naam DSN van de database
     // userIMPOTS, mdpIMPOTS: gebruikersnaam/wachtwoord voor toegang tot de database

In dit voorbeeld besluiten we deze nieuwe constructor niet in de klasse impots te implementeren, maar in een afgeleide klasse impotsJDBC:

// geïmporteerde pakketten
import java.sql.*;
import java.util.*;

public class impotsJDBC extends impots{
  // toevoeging van een constructor waarmee
   // de tabellen limites, coeffr en coeffn op basis van de tabel
   // belastinggegevens uit een database
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: naam DSN van de database
     // userIMPOTS, mdpIMPOTS: gebruikersnaam/wachtwoord voor toegang tot de database

     // de gegevenstabellen
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();

    // verbinding met de database
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
    // aanmaken van een Statement-object
    Statement S=connect.createStatement();
    // SELECT-query
    String select="select limites, coeffr, coeffn from impots";
    // uitvoering van de query
    ResultSet RS=S.executeQuery(select);
    while(RS.next()){
      // de huidige rij verwerken
      aLimites.add(RS.getString("limites"));
      aCoeffR.add(RS.getString("coeffr"));
      aCoeffN.add(RS.getString("coeffn"));
    }// volgende rij
     // bronnen sluiten
    RS.close();
    S.close();
    connect.close();
     // gegevensoverdracht naar begrensde tabellen
    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
  }//constructor
}//klasse

De constructor leest de inhoud van de tabel impots uit de database die als parameters is doorgegeven en vult de drie arrays limites, coeffR en coeffN. Er kunnen een aantal fouten optreden. De constructor verwerkt deze niet, maar geeft ze door aan het aanroepende programma:

  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

Als we de bovenstaande code bekijken, zien we dat de klasse impotsJDBC rechtstreeks gebruikmaakt van de velden limites, coeffR en coeffN van haar basisklasse impots. Deze zijn als privé gedeclareerd:

    private double[] limites, coeffR, coeffN;

heeft de klasse impotsJDBC geen directe toegang tot deze velden. We brengen daarom een eerste wijziging aan in de basisklasse door het volgende te schrijven:

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

Het attribuut protected zorgt ervoor dat klassen die zijn afgeleid van de klasse impots directe toegang hebben tot de velden die met dit attribuut zijn gedeclareerd. We moeten nog een tweede wijziging aanbrengen. De constructor van de dochterklasse impotsJDBC is als volgt gedeclareerd:

  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

We weten dat we, voordat we een object van een dochterklasse aanmaken, eerst een object van de bovenliggende klasse moeten aanmaken. Hiervoor moet de constructor van de dochterklasse expliciet de constructor van de bovenliggende klasse aanroepen met een instructie super(....). Dat is hier niet gebeurd, omdat niet duidelijk is welke constructor van de bovenliggende klasse we zouden kunnen aanroepen. Er is er op dit moment maar één en die is niet geschikt. De compiler gaat dan in de bovenliggende klasse op zoek naar een constructor zonder parameters die hij zou kunnen aanroepen. Hij vindt er geen en dit leidt tot een fout bij het compileren. We voegen daarom een constructor zonder argumenten toe aan onze klasse impots:

   // lege constructor
  protected impots(){}

We declareren deze als „protected”, zodat deze alleen door dochterklassen kan worden gebruikt. Het skelet van de klasse impots ziet er nu als volgt uit:

public class impots{

  // de gegevens die nodig zijn voor de berekening van de belasting
   // zijn afkomstig van een externe bron

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

   // fabrikant leeg
  protected impots(){}

   // fabrikant
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
...........
  }//fabrikant

   // belastingberekening
  public long calculer(boolean marié, int nbEnfants, int salaire){
.............
    }//berekenen
}//klasse

De actie van het menu Initialiser in onze applicatie wordt als volgt:

  void mnuInitialiser_actionPerformed(ActionEvent e) {
    // de verbindingsstring wordt opgehaald
    Pattern séparateur=Pattern.compile("\\s*;\\s*");
    String[] champs=séparateur.split(txtConnexion.getText().trim());
     // er zijn drie velden nodig
    if(champs.length!=3){
      // fout
      txtStatus.setText("Chaîne de connexion (DSN;uid;mdp) incorrecte");
       // terug naar de visuele interface
      txtConnexion.requestFocus();
      return;
    }//if
     // de gegevens worden geladen
    try{
       // het object wordt aangemaakt impotsJDBC
      objImpots=new impotsJDBC(champs[0],champs[1],champs[2]);
       // bevestiging
      txtStatus.setText("Données chargées");
       // het salaris kan worden gewijzigd
      txtSalaire.setEditable(true);
       // geen wijzigingen meer mogelijk
      mnuInitialiser.setEnabled(false);
      txtConnexion.setEditable(false);
    }catch(Exception ex){
       // probleem
      txtStatus.setText("Erreur : " + ex.getMessage());
      // einde
      return;
    }//catch
  }

Zodra het object objImpots is aangemaakt, is de toepassing identiek aan de reeds geschreven grafische toepassing. De lezer wordt verwezen naar die toepassing.

6.4. Oefeningen

6.4.1. Oefening 1

Voorzie het vorige programma sql3 van een grafische interface.

6.4.2. Oefening 2

Een Java-applet heeft alleen toegang tot een database via de server waarvandaan deze is geladen. Aangezien een applet geen toegang heeft tot de harde schijf van de machine waarop deze wordt uitgevoerd, kan de database zich niet bevinden op de machine van de klant die de applet gebruikt. We bevinden ons dus in de volgende situatie:

Image

De computer waarop de applet wordt uitgevoerd, de server en de computer waarop de database staat, kunnen drie verschillende computers zijn. We gaan er hier vanuit dat de database zich op de server bevindt.

Opgave 1

Schrijf in Java de volgende servertoepassing:

  • de serverapplicatie werkt op een poort die als parameter wordt doorgegeven
  • wanneer een client verbinding maakt, verstuurt de serverapplicatie het bericht
200 - Bienvenue - Envoyez votre requête
  • de client stuurt vervolgens de benodigde parameters voor de verbinding met een database en de query die hij wil uitvoeren in de vorm:
Pilote Java/URL base/UID/MDP/requête SQL

De parameters worden gescheiden door een schuine streep. De eerste vier parameters zijn die van het programma sql3 dat in dit hoofdstuk wordt beschreven.

  • De serverapplicatie maakt vervolgens een verbinding met de opgegeven database, die zich op dezelfde machine moet bevinden, en voert daarop de query SQL uit. De resultaten worden naar de client teruggestuurd in de vorm:
100 - ligne1
100 - ligne2

...

als het gaat om het resultaat van een Select-query of

101 - nbLignes

om het aantal rijen weer te geven waarop een updatequery van invloed is. Als er een fout optreedt bij het verbinden met de database of bij het uitvoeren van de query, retourneert de applicatie

500 - Message d’erreur
  • Zodra de query is uitgevoerd, verbreekt de serverapplicatie de verbinding.

Opgave 2

Maak een Java-applet waarmee de vorige server kan worden opgevraagd. Je kunt je laten inspireren door de grafische interface van de vorige oefening 1. Aangezien de poort van de server kan variëren, moet deze in de interface van de applet worden ingevoerd. Hetzelfde geldt voor alle parameters die nodig zijn om de regel te verzenden:

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

die de client naar de server moet verzenden.

6.4.3. Oefening 3

De volgende tekst beschrijft een probleem dat oorspronkelijk bedoeld was om in Visual Basic te worden opgelost. Pas het aan voor verwerking in Java in een applet. Deze applet zal gebruikmaken van de server uit oefening 2. De grafische interface kan worden aangepast om rekening te houden met de nieuwe uitvoeringscontext.

Het is de bedoeling een applicatie te maken die de verschillende mogelijke updatebewerkingen van een tabel in een database ACCESS belicht. De database ACCESS heet articles.mdb. Deze bevat één enkele tabel met de naam ‘artikelen’, waarin de door een bedrijf verkochte artikelen zijn opgenomen. De structuur is als volgt:

naam
type
code
4-cijferige artikelcode
nom
de naam (tekenreeks)
prix
de prijs (werkelijke)
stock_actu
de huidige voorraad (geheel getal)
stock_mini
de minimumvoorraad (geheel getal) waaronder het artikel moet worden bijgevuld

We willen deze tabel weergeven en bijwerken aan de hand van het volgende formulier:

Image

De invoervelden van dit formulier zijn als volgt:

nr.
type
naam
Functie
1
textbox
fiche
nummer van de weergegeven fiche
enabled is ingesteld op false
2
textbox
code
artikelcode
3
tekstvak
naam
artikelnummer
4
tekstvak
prijs
prijs van het artikel
5
textbox
huidige
huidige voorraad van het artikel
6
tekstvak
minimum
minimale voorraad van het artikel
7
data
data1
Controle van de gegevens gekoppeld aan de database
databasename=pad naar het bestand artikelen.mdb
recordsource=artikelen
connect=access
8
HScrollBar
positie
maakt het mogelijk om door de tabel te navigeren
9
knop
OK
hiermee kunt u een update bevestigen – verschijnt alleen tijdens het updaten
10
knop
Annuleren
hiermee kunt u een update annuleren – verschijnt alleen tijdens de update
11
frame
frame1
voor de sier
12
textbox
basename
naam van de geopende database
enabled is ingesteld op false
13
textbox
sourcename
naam van de geopende tabel
'enabled' is ingesteld op 'false'

Aanmaken van het werkblad in VB

controle
Bijzonderheden
data1
De velden databasename en recordsource zijn ingevuld. databasename moet verwijzen naar de Access-database articles.mdb in uw map en recordsource naar de tabel artikelen.
code
Dit is een tekstvak dat we willen koppelen aan het veld ‘code’ van het huidige record van ‘data1’. Hiervoor vullen we twee velden in:
datasource: we vullen ‘data1’ in om aan te geven dat het tekstvak is gekoppeld aan de tabel die bij data1 hoort
datafield: we kiezen het veld ‘code’ uit de tabel ‘artikelen’
Na deze handelingen zal het tekstvak ‘code’ altijd het veld ‘code’ van het huidige record in data1 bevatten. Omgekeerd zal het wijzigen van de inhoud van dit tekstvak het veld ‘code’ van het huidige record wijzigen.
We doen hetzelfde voor de andere tekstvakken
nom
datasource: data1 datafield: naam
prix
gegevensbron: data1 gegevensveld: prijs
actuel
gegevensbron: data1 gegevensveld: stock_actu
minimum
gegevensbron: data1 gegevensveld: stock_mini

De menu's

De structuur van de menu’s is als volgt

Bewerken
Bladeren
Afsluiten
Toevoegen
Terug
 
Wijzigen
Volgende
 
Verwijderen
Eerste
 
 
Laatste
 

De verschillende opties hebben de volgende functies:

menu
naam
functie
Ajouter
mnu toevoegen
om een nieuw record toe te voegen aan de artikeltabel
Supprimer
mnusupprimer
om het momenteel weergegeven record uit de artikeltabel te verwijderen
Modifier
mnumodifier
om het momenteel weergegeven record in de artikeltabel te wijzigen
Précédent
mnuprecedent
om naar het vorige record te gaan
Suivant
mnusuivant
om naar het volgende record te gaan
Premier
vorige
om naar het eerste record te gaan
Dernier
mnudernier
om naar het laatste record te gaan
Quitter
mnuquitter
om de applicatie te verlaten

Het blad wordt geladen

Bij de gebeurtenis form_load wordt de tabel geopend die bij data1 hoort (data1.refresh). Als het openen mislukt, wordt er een foutmelding weergegeven en wordt het programma beëindigd (end). Anders wordt het formulier weergegeven met daarin het eerste record uit de tabel articles. Er kan niets in de velden worden ingevoerd (eigenschap enabled op false). Invoer is alleen mogelijk via de opties Toevoegen en Wijzigen. De knoppen OK en Annuleren zijn verborgen (visible=false).


Deel 1

We gaan de procedures bouwen die verband houden met de verschillende menuopties en met de knoppen OK en Annuleren. In eerste instantie laten we de volgende punten buiten beschouwing:

  • het toestaan/blokkeren van bepaalde menuopties: zo moet de optie Volgende bijvoorbeeld worden geblokkeerd als men zich op het laatste record van de tabel bevindt
  • het beheer van de horizontale schuifbalk

het menu Bladeren/Volgende

  • gaat naar het volgende record (data1.RecordSet.MoveNext) als men zich niet aan het einde van het bestand bevindt (data1.RecordSet.EOF). Werkt het tekstvak van het record bij (data1.recordset.absoluteposition/ data1.recordset.recordcount).

Menu Bladeren/Vorige

  • gaat naar het vorige record (data1.RecordSet.MovePrevious) als men zich niet aan het begin van het bestand bevindt (data1.RecordSet.BOF). Werkt het tekstvak van het record bij.

menu Bladeren/Eerste

  • gaat naar het eerste record (data1.RecordSet.MoveFirst) als het bestand niet leeg is (data1.recordset.recordcount=0). Werkt het tekstvak van het record bij.

menu Bladeren/Laatste

  • gaat naar het laatste record (data1.RecordSet.MoveLast) als het bestand niet leeg is (data1.recordset.recordcount=0). Werkt het tekstvak van het record bij.

menu Bewerken/Toevoegen

  • maakt het mogelijk een record aan de tabel toe te voegen
  • schakelt over naar de modus 'Record toevoegen' (data1.recordset.addnew)
  • maakt invoer mogelijk in de 5 velden code, naam, prijs, ... (enabled=true)
  • schakelt de menu’s Bewerken, Bladeren en Afsluiten uit (enabled=false)
  • geeft de knoppen OK en Annuleren weer (visible=true)

knop OK

  • bevestigt een wijziging in een record (data1.recordset.Update)
  • verbergt de knoppen OK en Annuleren (visible=false)
  • schakelt de opties Bewerken, Bladeren en Afsluiten in (enabled=true)
  • werkt het tekstvak van het record bij

knop Annuleren

  • maakt een wijziging in een record ongeldig (data1.recordset.CancelUpdate)
  • verbergt de knoppen OK en Annuleren (visible=false)
  • schakelt de opties Bewerken, Bladeren en Afsluiten in (enabled=true)
  • werkt het tekstveld van het record bij

menu Bewerken/Wijzigen

  • maakt het mogelijk het record dat op het formulier wordt weergegeven te wijzigen
  • zet het formulier in de bewerkingsmodus (data1.recordset.edit)
  • maakt invoer mogelijk in de 4 velden naam, prijs, ... (enabled=true) maar niet in het veld code (enabled=false)
  • schakelt de menu's Bewerken, Bladeren en Afsluiten uit (enabled=false)
  • geeft de knoppen OK en Annuleren weer (visible=true)

menu Bewerken/Verwijderen

  • maakt het mogelijk om (data1.recordset.delete) het weergegeven record uit de tabel te verwijderen
  • gaat naar het volgende record (data1.recordset.movenext)

menu Afsluiten

  • laadt het werkblad af (unload me)

gebeurtenis form_unload (cancel as integer)

  • geactiveerd door de bewerking unload me of het sluiten van het formulier met Alt-F4 of een dubbelklik op het systeemvak, dus niet noodzakelijkerwijs via de optie Afsluiten.
  • stelt de vraag „Wilt u de toepassing echt afsluiten?“ met twee knoppen Ja/Nee (msgbox met style=vbyes+vbno)
  • als het antwoord ‘Nee’ is (=vbno), wordt ‘cancel’ op -1 gezet en wordt de procedure form_unload verlaten. Een waarde van -1 voor ‘cancel’ geeft aan dat het sluiten van het venster is geweigerd.
  • Als het antwoord Ja is (=vbyes), wordt de database gesloten (data1.recordset.close, data1.database.close).

Deel 2 - Beheer van de menu’s

Hier richten we ons op het toestaan of blokkeren van menu’s. Na elke bewerking die het huidige record wijzigt, roepen we een procedure aan die we Oueston kunnen noemen. Deze controleert de volgende voorwaarden:

. als het bestand leeg is, worden

  • de menu’s Bladeren, Bewerken/Verwijderen,
  • en de overige

. Als het huidige record het eerste record is,

  • worden 'Bladeren' en 'Vorige' uitgeschakeld
  • de rest wordt toegestaan

. Als het huidige record het laatste is,

  • worden de knoppen 'Bladeren' en 'Volgende' uitgeschakeld
  • de rest toestaan

Deel 3 - Beheer van de horizontale variator

Een horizontale variator heeft drie belangrijke velden:

  • min: de minimumwaarde
  • max: de maximale waarde
  • value: de huidige waarde

Initialisatie van de regelaar

Bij het laden (form_load) wordt de regelaar als volgt geïnitialiseerd:

  • min=0
  • max=data1.recordset.recordcount-1
  • value=1

Merk op dat direct na het openen van de database (data1.refresh) het aantal records in de tabel die wordt weergegeven door data1.recordset.recordcount onjuist is. U moet naar het einde van de tabel (MoveLast) gaan en vervolgens terugkeren naar het begin van de tabel (MoveFirst) om dit te corrigeren.

Directe bediening van de regelaar

De positie van de cursor van de regelaar geeft de positie in de tabel weer.

Wanneer de gebruiker de variator (hier ‘positie’ genoemd) wijzigt, wordt de gebeurtenis position_change geactiveerd. In deze gebeurtenis wijzigen we het huidige record in de tabel, zodat dit de verplaatsing weerspiegelt die op de variator is uitgevoerd. Hiervoor gebruiken we het veld absoluteposition van data1.recordset. Wanneer we de waarde i aan dit veld toekennen, wordt record nr. i van de tabel het huidige record. De records worden genummerd vanaf 0 en hebben dus een nummer binnen het bereik [0,data1.recordset.recordcount-1]. In de procedure position_change hoeven we alleen maar het volgende te schrijven

    data1.recordset.absoluteposition=position.value

om ervoor te zorgen dat het huidige record dat op het formulier wordt weergegeven, de wijziging weerspiegelt die op de regelaar is doorgevoerd.

Zodra dit is gebeurd, roepen we vervolgens de Oueston-procedure aan om de menu’s bij te werken.

Bijwerken van de regelaar

Aangezien de positie van de cursor op de variator de positie in de tabel moet weerspiegelen, moet de waarde van de variator worden bijgewerkt telkens wanneer er een wijziging plaatsvindt in het huidige record in de tabel, veroorzaakt door een van de menu’s. Aangezien elk van deze menu’s de Oueston-procedure aanroept, is het het beste om deze bijwerking ook in deze procedure onder te brengen. Hier volstaat het om het volgende te schrijven:

    position.value=data1.recordset.absoluteposition

Deel 4 - Optie Zoeken

We voegen de optie Bladeren/Zoeken toe, waarmee de gebruiker een artikel kan bekijken door de code ervan op te geven.

Wanneer deze optie is geactiveerd, vinden de volgende stappen plaats:

  • we gaan naar 'Record toevoegen' (Addnew), uitsluitend om te voorkomen dat het huidige record waarop we ons bevonden toen de optie werd geactiveerd, wordt gewijzigd,
  • we maken het mogelijk om gegevens in het veld ‘code’ in te voeren en wissen de inhoud van het veld ‘fiche’,
  • de menu’s worden uitgeschakeld en de knoppen OK en Annuleren worden weergegeven
  • wanneer de gebruiker op OK drukt, moeten we het record zoeken dat overeenkomt met de door de gebruiker ingevoerde code. De procedure OK_click wordt echter al gebruikt voor de opties „Bladeren/Toevoegen” en „Bladeren/Wijzigen”. Om onderscheid te maken tussen deze gevallen, moeten we een globale variabele beheren die we hier ‘status’ zullen noemen en die drie mogelijke waarden heeft: ‘toevoegen’, ‘wijzigen’ en ‘zoeken’. De procedures die gekoppeld zijn aan de knoppen OK en Annuleren zullen deze variabele gebruiken om te weten in welke context ze worden aangeroepen.
  • Als ‘status’ de waarde ‘zoeken’ heeft, wordt in de procedure die gekoppeld is aan OK
    • de bewerking addnew (data1.recordset.cancelupdate) geannuleerd, omdat het niet de bedoeling was een record toe te voegen. Merk op dat het huidige record dan weer het record wordt dat vóór de bewerking Bladeren/Zoeken op het scherm stond.
    • Het zoekcriterium op basis van de code wordt opgesteld en de zoekopdracht wordt gestart (data1.recordset.findfirst criterium),
    • als de zoekopdracht mislukt (data1.recordset.nomatch=true), wordt dit aan de gebruiker gemeld, waarna de modus ‘Toevoegen’ (addnew) weer wordt geactiveerd en de procedure OK wordt verlaten. De gebruiker moet dan een nieuwe code invoeren of de optie ‘Annuleren’ kiezen.
    • Als de zoekopdracht slaagt, wordt het gevonden record het nieuwe actieve record. De menu’s worden weergegeven, de knoppen OK/Annuleren worden verborgen, het invoeren in het codeveld wordt geblokkeerd en de procedure wordt beëindigd.
  • . Als de status „zoeken“ is, wordt in de procedure die gekoppeld is aan „Annuleren“
    • de bewerking addnew (data1.recordset.cancelupdate) geannuleerd. Vervolgens keert men automatisch terug naar het huidige record van vóór de bewerking Bladeren/Zoeken.
    • De menu’s worden weergegeven, de knoppen OK/Annuleren worden verborgen, invoer in het codeveld wordt geblokkeerd en de procedure wordt verlaten.

Deel 5 - Codebeheer

Een artikel moet op unieke wijze worden geïdentificeerd aan de hand van zijn code. Zorg ervoor dat in de optie Bladeren/Toevoegen het toevoegen wordt geweigerd als het toe te voegen record een artikelcode heeft die al in de tabel voorkomt.

6.4.4. Oefening 4

Hier wordt een webapplicatie gepresenteerd die gebruikmaakt van de server uit oefening 2. Het is een prille e-commerceapplicatie.

De klant bestelt artikelen via de volgende webinterface:

Image

Hij kan de volgende handelingen uitvoeren:

  • hij selecteert een artikel uit de vervolgkeuzelijst
  • hij geeft het gewenste aantal op
  • hij bevestigt zijn aankoop met de knop Acheter
  • zijn aankoop wordt weergegeven in de lijst met gekochte artikelen
  • hij kan artikelen uit deze lijst verwijderen door een artikel te selecteren en de knop Retirer te gebruiken
  • wanneer hij op de knop Bilan klikt, krijgt hij het volgende overzicht te zien:

Image

Via het overzicht kan de gebruiker de details van zijn factuur bekijken. De gebruiker krijgt toegang tot details over een artikel dat in de vervolgkeuzelijst is geselecteerd met de knop Informations:

Image

Zodra de gebruiker het overzicht van zijn factuur heeft opgevraagd, kan hij deze op de volgende pagina bevestigen. Hiervoor moet hij zijn e-mailadres opgeven en zijn bestelling bevestigen met de daarvoor bestemde knop.

Image

Voordat de bestelling wordt opgeslagen, vraagt de applicatie om bevestiging:

Image

Zodra de bestelling is bevestigd, verwerkt de applicatie deze en stuurt een bevestigingspagina:

Image

In werkelijkheid verwerkt de app de bestelling niet. De app stuurt de gebruiker alleen een e-mail waarin hij wordt gevraagd het aankoopbedrag te betalen:

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

Vraag: Maak het equivalent van deze webapplicatie met een Java-applet.