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:

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

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:

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:
- Verbinding maken met de database
- Verzenden van SQL-query's naar de database
- Ontvangst en verwerking van de resultaten van deze query's
- 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:


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

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:
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:
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:
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:
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:
Alleen de trefwoorden op de eerste regel zijn verplicht, de overige zijn optioneel. Er zijn nog andere trefwoorden die hier niet worden weergegeven.
- Er wordt een join uitgevoerd met alle tabellen die achter het sleutelwoord `from` staan
- Alleen de kolommen die achter het sleutelwoord `select` staan, worden behouden
- Alleen de rijen die voldoen aan de voorwaarde van het sleutelwoord `where` worden behouden
- 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<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:
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:
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:
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
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:
Er zijn twee nuttige methoden in de klasse ResultSetMetaData:
- int getColumnCount(), die het aantal kolommen van de ResultSet retourneert
- 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:
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:
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:
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 été mises à jour
Nouvelle Requête : (requete 3 du fichier des requetes : select nom,stock_mini from articles)
Résultats obtenus
-----------------
vélo,8
arc,8
canoé,7
fusil,8
skis nautiques,8
essai3,9
cachalot,6
léopard,7
panthère,7
Nouvelle Requête : (requete 4 du fichier des requetes : insert into articles (code,nom,prix,stock_actu,stock_mini) values ('x400','nouveau',100,20,10))
1 ligne(s) ont été mises à jour
Nouvelle Requête : (requete 5 du fichier des requetes : select * from articles)
Résultats obtenus
-----------------
a300,vélo,1202,31,8
d600,arc,5000,10,8
d800,canoé,1502,13,7
x123,fusil,3000,10,8
s345,skis nautiques,1800,3,8
f450,essai3,3,3,9
f807,cachalot,200000,0,6
z400,léopard,500000,1,7
g457,panthère,800000,1,7
x400,nouveau,100,20,10
Nouvelle Requête : (requete 6 du fichier des requêtes : delete from articles where code='x400')
1 ligne(s) ont été mises à jour
Nouvelle Requête : (requete 7 du fichier des requêtes : select * from articles)
Résultats obtenus
-----------------
a300,vélo,1202,31,8
d600,arc,5000,10,8
d800,canoé,1502,13,7
x123,fusil,3000,10,8
s345,skis nautiques,1800,3,8
f450,essai3,3,3,9
f807,cachalot,200000,0,6
z400,léopard,500000,1,7
g457,panthère,800000,1,7
6.3. IMPOTS 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:

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
de naam DSN van de gegevensbron ODBC | |
| de identiteit van een gebruiker met leesrechten voor de database |
| 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

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

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

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

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
- de client stuurt vervolgens de benodigde parameters voor de verbinding met een database en de query die hij wil uitvoeren in de vorm:
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:
...
als het gaat om het resultaat van een Select-query of
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
- 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:
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:

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' |
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 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 |
mnu toevoegen | om een nieuw record toe te voegen aan de artikeltabel | |
mnusupprimer | om het momenteel weergegeven record uit de artikeltabel te verwijderen | |
mnumodifier | om het momenteel weergegeven record in de artikeltabel te wijzigen | |
mnuprecedent | om naar het vorige record te gaan | |
mnusuivant | om naar het volgende record te gaan | |
vorige | om naar het eerste record te gaan | |
mnudernier | om naar het laatste record te gaan | |
mnuquitter | om de applicatie te verlaten |
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).
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).
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
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
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:
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.
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:

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:

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:

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.

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

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

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.

