Skip to content

6. Database management with API JDBC

6.1. Overview

There are many databases on the market. To standardize database access under Windows, Microsoft has developed an interface called ODC (Open Database Connectivity). This layer hides the specific features of each database behind a standard interface. There are many MS drivers available for Windows that facilitate access to databases. Here, for example, is a list of ODBC drivers installed on a Win95 machine:

Image

An application relying on these drivers can use any of the above databases without rewriting.

Image

To enable Java applications to also take advantage of the ODBC interface, Sun has created the JDBC interface (Java DataBase Connectivity), which will act as an intermediary between the Java application and the ODBC interface:

Image

6.2. Key steps in database operations

6.2.1. Introduction

In a JAVA application using a database with the JDBC interface, the following steps are generally involved:

  • Connecting to the database
  • Sending SQL queries to the database
  • Receiving and processing the results of these queries
  • Closing the connection

Steps 2 and 3 are performed repeatedly; the connection is closed only at the end of database operations. This is a relatively standard pattern that you may be familiar with if you have worked with a database interactively. We will detail each of these steps using an example. Consider a database named ACCESS, referred to as ARTICLES, with the following structure:

name
type
code
4-character item code
name
its name (string)
price
its price (actual)
stock_actu
current stock (integer)
stock_mini
the minimum stock (integer) below which the item must be restocked

This database ACCESS is defined as a "user" data source in the ODBC database manager:

Image

Image

Its characteristics are specified using the Configure button as follows:

Image

This configuration essentially consists of associating the ARTICLES database with the Access file articles.mdb corresponding to this database. Once this is done, the ARTICLES database is accessible to applications using the ODBC interface.

6.2.2. The connection step

To use a database, a Java application must first go through a connection phase. This is done using the following class method:

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

where

DriverManager: Java class containing the list of drivers available to the application

Connection: Java class establishing a link between the application and the database, through which the application will send SQL queries to the database and receive results

URL: name identifying the database. This name is analogous to URL on the Internet. This is why it is part of the URL class. However, the Internet plays no role here. The URL has the following form:

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

In our examples, where we will use only ODBC drivers, the driver is called odbc. The third part of the URL consists of the source name with any parameters. In our examples, these will be ODBC sources known to the system. Thus, the URL for the Articles data source defined previously will be

jdbc:odbc:Articles

id: User ID (login)

mdp: user password

In summary, the program connects to a database:

  • identified by a name (URL)
  • under a user ID (id, mdp)

If these three parameters are correct, and if the driver capable of establishing the connection between the Java application and the specified database exists, then a connection is established between the Java application and the database. This connection is represented for the program by the Connection object returned by the DriverManager class. Since this connection may fail for various reasons, it may throw an exception. We will therefore write:


    Connection connexion=null;
    URL base=...;
    String id=...;
    String mdp=...;
try{
        connexion=DriverManager.getConnection(base,id,mdp);
} catch (Exception e){
        // handle the exception
}

To connect to a database, you must have the appropriate driver. In our examples, this will be the ODBC driver, which is capable of managing the requested database. While this driver must be available in the list of ODBC drivers on the machine, you must also have the JAVA class, which will act as the interface to it. To do this, the application will request the necessary class as follows:

    Class.forName(String nomClasse)

The Class class is in no way related to the JDBC interface. It is a general class management class. Its static method forName allows a class to be loaded dynamically, thereby making its attributes and static methods available. The class that interfaces with the ODBC drivers for MS Windows is called “sun.jdbc.odbc.JdbcOdbcDriver.” We will therefore write (the method may throw an exception):

try{
    Class.forName(« sun.jdbc.odbc.JdbcOdbcDriver ») ;
} catch (Exception e){
    // handle exception (non-existent class)
}

The classes required for the JDBC interface are located in the java.sql package. Therefore, at the beginning of the program, we will write:

    import java.sql.*;

Here is a program that allows you to connect to a database:

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

// call: pg PILOTE URL UID MDP
// connects to the URL database using class JDBC PILOTE
// user UID is identified by password MDP

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

    public static void main(String arg[]){
        // check number of arguments
        if(arg.length<2 || arg.length>4)
            erreur(syntaxe,1);
        // base connection
        Connection connect=null;
        String uid="";
        String mdp="";
        if(arg.length>=3) uid=arg[2];
        if(arg.length==4) mdp=arg[3];        
        try{
            Class.forName(arg[0]);
            connect=DriverManager.getConnection(arg[1],uid,mdp);
            System.out.println("Connexion avec la base " + arg[1] + " établie");
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // closing the base
        try{
            connect.close();
            System.out.println("Base " + arg[1] + " fermée");
        } catch (Exception e){}
    }// hand

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

Here is an example of execution:

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. Sending queries to the database

The JDBC interface allows you to send SQL queries to the database connected to the Java application and process the results of these queries. The SQL language (Structured Query Language) is a standardized query language for relational databases. It supports several types of queries:

  1. database query requests (SELECT)
  2. database update queries (INSERT, DELETE, UPDATE)
  3. table creation/deletion queries (CREATE, DELETE)

It is assumed here that the reader is familiar with the basics of the SQL language.

6.2.3.1. The Statement Class

To issue any SQL query to a database, the Java application must have an object of type Statement. This object will store, among other things, the text of the query. This object is necessarily linked to the current connection. Therefore, a method of the established connection is used to create the Statement objects required to execute SQL queries. If *connection is the object representing the connection to the database, a Statement* object is obtained as follows:

    Statement requete=connexion.CreateStatement();

Once a Statement object has been obtained, SQL queries can be executed. This is done differently depending on whether the query is a query to retrieve data or to update the database.

6.2.3.2. Executing a query to retrieve data from the database

A query is typically of the following type:

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

Only the keywords in the first line are required; the others are optional. There are other keywords not shown here.

  1. A join is performed on all tables listed after the `FROM` keyword
  2. Only the columns following the `select` keyword are retained
  3. Only the rows that satisfy the condition of the `where` keyword are retained
  4. The resulting rows, sorted according to the expression in the `ORDER BY` keyword, form the result of the query.

The result of a SELECT statement is a table. If we consider the previous table ARTICLES and want the names of the items whose current stock is below the minimum threshold, we would write: SELECT name FROM items WHERE stock_actu < stock_mini. If you want them sorted alphabetically by name, you would write: select name from items where stock_actu<stock_mini order by name

To execute this type of query, the Statement class provides the executeQuery method:

    ResultSet executeQuery(String requête)

where query is the text of the query to be executed.

Thus, if

  1. connection is the object representing the connection to the database
  2. Statement s=connexion.createStatement() creates the Statement object required to execute the queries SQL
  3. ResultSet rs=s.executeQuery(" select name from articles where stock_actu<stock_mini”) executes a SELECT query and assigns the query’s result rows to an object of type ResultSet.

6.2.3.3. The ResultSet class: result of a SELECT query

An object of type ResultSet represents a table, i.e., a set of rows and columns. At any given time, only one row of the table is accessible; this is called the current row. When ResultSet is initially created, the current row is row #1 if ResultSet is not empty. To move to the next row, the ResultSet class provides the next method:

    boolean next()

This method attempts to move to the next line in ResultSet and returns true if successful, false otherwise. If successful, the next line becomes the new current line. The previous line is lost and cannot be retrieved. The ResultSet table has columns col1, col2,.... To access the various fields of the current row, the following methods are available:

Type getType("coli") 

to retrieve the "coli" field from the current row. Type refers to the type of the coli field. The getString method is often used on all fields, which returns the field’s content as a string. It is then converted if necessary. If the column name is unknown, you can use the methods

Type getType(i) 

where i is the index of the desired column (i>=1).

6.2.3.4. A first example

Here is a program that displays the contents of the previously created ARTICLES database:

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

// displays the contents of a ARTICLES system database

public class articles1{

    static final String DB="ARTICLES";        // database to exploit

    public static void main(String arg[]){

        Connection connect=null;        // connection to base
        Statement S=null;                // purpose of queries
        ResultSet RS=null;            // query result table
        try{
            // connection to base
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
            System.out.println("Connexion avec la base " + DB + " établie");
            // creation of a Statement object
            S=connect.createStatement();
            // execute a select query
            RS=S.executeQuery("select * from " + DB);
            // using the results table
            while(RS.next()){                // as long as there's a line to operate
                // it is displayed on screen
                System.out.println(RS.getString("code")+","+
                    RS.getString("nom")+","+
                    RS.getString("prix")+","+
                    RS.getString("stock_actu")+","+
                    RS.getString("stock_mini"));
            }// next line
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // closing the base
        try{
            connect.close();
            System.out.println("Base " + DB + " fermée");
        } catch (Exception e){}
    }// hand

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

The results obtained are as follows:

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. The ResultSetMetadata class

In the previous example, we know the column names of ResultSet. If we do not know them, we cannot use the method getType(nom_colonne). Instead, we will use getType(column number). However, to obtain all the columns, we would need to know how many columns the obtained ResultSet has. The ResultSet class does not provide this information. It is the ResultSetMetaData class that provides it. More generally, this class provides information about the table’s structure, i.e., the nature of its columns.

We can access information about the structure of a ResultSet by first instantiating a ResultSetMetaData object. If RS is a ResultSet, the associated ResultSetMetaData is obtained by:

    RS.getMetaData()

Note two useful methods in the ResultSetMetaData class:

  1. int getColumnCount(), which returns the number of columns in ResultSet
  2. String getColumnLabel(int i), which returns the name of column i in ResultSet (i >= 1)

6.2.3.6. A second example

The previous program displayed the contents of the ARTICLES database. Here, we write a program that executes on the ARTICLES database any SQL Select query that the user types at the keyboard.

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

// displays the contents of a ARTICLES system database

public class sql1{

    static final String DB="ARTICLES";        // database to exploit

    public static void main(String arg[]){

        Connection connect=null;        // connection to base
        Statement S=null;                // purpose of queries
        ResultSet RS=null;            // query result table
        String select;                    // query text SQL select
        int nbColonnes;                // no. of columns in ResultSet

        // creation of a keyboard input stream
        BufferedReader in=null;
        try{
            in=new BufferedReader(new InputStreamReader(System.in));
        } catch(Exception e){
            erreur("erreur lors de l'ouverture du flux clavier ("+e+")",3);
        }
        try{
            // connection to base
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
            System.out.println("Connexion avec la base " + DB + " établie");
            // creation of a Statement object
            S=connect.createStatement();
            // execution loop for SQL requests typed on keyboard
            System.out.print("Requête : ");
            select=in.readLine();
            while(!select.equals("fin")){
                // query execution
                RS=S.executeQuery(select);
                // number of columns
                nbColonnes=RS.getMetaData().getColumnCount();
                // using the results table
                System.out.println("Résultats obtenus\n\n");
                while(RS.next()){                // as long as there's a line to operate
                    // it is displayed on screen
                    for(int i=1;i<nbColonnes;i++)
                        System.out.print(RS.getString(i)+",");
                    System.out.println(RS.getString(nbColonnes));
                }// next line
                // following request
                System.out.print("Requête : ");
                select=in.readLine();                
            }// while
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // closing the base and input stream
        try{
            connect.close();
            System.out.println("Base " + DB + " fermée");
            in.close();
        } catch (Exception e){}
    }// hand

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

Here are some of the results obtained:

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. Execute a database update query

A Statement object is used to store SQL queries. The method this object uses to issue SQL update queries (INSERT, UPDATE, DELETE) is no longer the executeQuery method discussed previously, but the executeUpdate method:

    int executeUpdate(String requête)

The difference lies in the result: while executeQuery returned the result table (ResultSet), executeUpdate returns the number of rows affected by the update operation.

6.2.3.8. A third example

We’ll revisit the previous program and modify it slightly: queries entered via the keyboard are now update queries for the ARTICLES database.

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

// displays the contents of a ARTICLES system database

public class sql2{

    static final String DB="ARTICLES";    // database to exploit

    public static void main(String arg[]){

        Connection connect=null;        // connection to base
        Statement S=null;                // purpose of queries
        ResultSet RS=null;            // query result table
        String sqlUpdate;                // text of SQL update request
        int nbLignes;                    // no. of lines affected by an update

        // creation of a keyboard input stream
        BufferedReader in=null;
        try{
            in=new BufferedReader(new InputStreamReader(System.in));
        } catch(Exception e){
            erreur("erreur lors de l'ouverture du flux clavier ("+e+")",3);
        }
        try{
            // base connection
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
            System.out.println("Connexion avec la base " + DB + " établie");
            // creation of a Statement object
            S=connect.createStatement();
            // execution loop for SQL requests typed on keyboard
            System.out.print("Requête : ");
            sqlUpdate=in.readLine();
            while(!sqlUpdate.equals("fin")){
                // query execution
                nbLignes=S.executeUpdate(sqlUpdate);
                // follow-up
                System.out.println(nbLignes + " ligne(s) ont été mises à jour");
                // following request
                System.out.print("Requête : ");
                sqlUpdate=in.readLine();                
            }// while
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // closing the base and input stream
        try{
            // free up resources linked to the base
            RS.close();
            S.close();
            connect.close();
            System.out.println("Base " + DB + " fermée");
            // keyboard flow closure
            in.close();
        } catch (Exception e){}
    }// hand

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

Here are the results of various runs of the sql1 and sql2 programs:

List of rows in the ARTICLES database:


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 modify certain lines:


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

Verification:


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

Add a line:

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 é mises à jour

Verification:


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

Deleting a row:

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 é mises à jour
Requête : fin

Verification:


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. Send any SQL query

The Statement object required to issue SQL queries has an execute method capable of executing any type of SQL query:

    boolean execute(String requête)

The result returned is the Boolean value true if the query returned a *ResultSet* (*executeQuery*), and false if it returned a number (executeUpdate*). The resulting ResultSet can be retrieved using the getResultSet method, and the number of updated rows can be retrieved using the getUpdateCount* method. Thus, we write:


Statement S=...;
ResultSet RS=...;
int nbLignes;
String requête=...;
// query execution SQL
if (S.execute(requête)){
    // we have a resultset
    RS=S.getResultSet();
    // operation of ResultSet
    ...
} else {
    // it was an update request
    nbLignes=S.getUpdateCount();
    ...
}

6.2.3.10. Fourth example

We take the approach of programs sql1 and sql2 and apply it to a program sql3 that is now capable of executing any SQL query typed on the keyboard. To make the program more general, the characteristics of the database to be used are passed as parameters to the program.

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

// call: pg PILOTE URL UID MDP
// connects to the URL database using class JDBC PILOTE
// user UID is identified by password MDP

public class sql3{

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

    public static void main(String arg[]){



        // check number of arguments
        if(arg.length<2 || arg.length>4)
            erreur(syntaxe,1);

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

        // other data
        Statement S=null;                        // purpose of queries
        ResultSet RS=null;                    // result table of a query request
        String sqlText;                            // text of query SQL to be executed
        int nbLignes;                                // no. of lines affected by an update
        int nbColonnes;                            // no. of columns in a ResultSet

        // creation of a keyboard input stream
        BufferedReader in=null;
        try{
            in=new BufferedReader(new InputStreamReader(System.in));
        } catch(Exception e){
            erreur("erreur lors de l'ouverture du flux clavier ("+e+")",3);
        }
        try{
            // connection to base
            Class.forName(arg[0]);
            connect=DriverManager.getConnection(arg[1],uid,mdp);
            System.out.println("Connexion avec la base " + arg[1] + " établie");
            // creation of a Statement object
            S=connect.createStatement();
            // execution loop for SQL requests typed on keyboard
            System.out.print("Requête : ");
            sqlText=in.readLine();
            while(!sqlText.equals("fin")){
                // query execution
                try{
                    if(S.execute(sqlText)){
                        // we have obtained a ResultSet - we exploit it
                        RS=S.getResultSet();
                        // number of columns
                        nbColonnes=RS.getMetaData().getColumnCount();
                        // using the results table
                        System.out.println("\nRésultats obtenus\n-----------------\n");
                        while(RS.next()){                // as long as there's a line to operate
                            // it is displayed on screen
                            for(int i=1;i<nbColonnes;i++)
                                System.out.print(RS.getString(i)+",");
                            System.out.println(RS.getString(nbColonnes));
                        }// next line of ResultSet
                    } else {
                        // it was an update request
                        nbLignes=S.getUpdateCount();
                        // follow-up
                        System.out.println(nbLignes + " ligne(s) ont été mises à jour");
                    }//if
                } catch (Exception e){
                    System.out.println("Erreur " +e);
                }
                // following request
                System.out.print("\nNouvelle Requête : ");
                sqlText=in.readLine();                
            }// while
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // closing the base and input stream
        try{
            // free up resources linked to the base
            RS.close();
            S.close();
            connect.close();
            System.out.println("Base " + arg[1] + " fermée");
            // keyboard flow closure
            in.close();
        } catch (Exception e){}
    }// hand

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

We create the following query file:

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

The program is run as follows:

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

The program reads its input from the queries file and writes its output to the results file. The results obtained are as follows:

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 with a database

The last time we addressed the issue of tax calculation, we used a graphical interface and the data was stored in a file. We are revisiting this version, now assuming that the data is in an ODBC database—MySQL. MySQL is a public-domain SGBD that can be used on various platforms, including Windows and Linux. With this SGBD, a database named dbimpots was created, containing a single table called impots. Access to the database is controlled by a username/password, in this case admimpots/mdpimpots. The screenshot shows how to use the dbimpots database with MySQL:


C:\Program Files\EasyPHP\mysql\bin>mysql -u admimpots -p
Enter password: *********
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 3.23.49-max-nt
 
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
 
mysql> use dbimpots;
Database changed
 
mysql> show tables;
+--------------------+
| Tables_in_dbimpots |
+--------------------+
| impots             |
+--------------------+
1 row in set (0.00 sec)
 
mysql> describe impots;
+---------+--------+------+-----+---------+-------+
| Field   | Type   | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limites | double | YES  |     | NULL    |       |
| coeffR  | double | YES  |     | NULL    |       |
| coeffN  | double | YES  |     | NULL    |       |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)
 
mysql> select * from impots;
+---------+--------+---------+
| limites | coeffR | coeffN  |
+---------+--------+---------+
|   12620 |      0 |       0 |
|   13190 |   0.05 |     631 |
|   15640 |    0.1 |  1290.5 |
|   24740 |   0.15 |  2072.5 |
|   31810 |    0.2 |  3309.5 |
|   39970 |   0.25 |    4900 |
|   48360 |    0.3 |    6898 |
|   55790 |   0.35 |  9316.5 |
|   92970 |    0.4 |   12106 |
|  127860 |   0.45 |   16754 |
|  151250 |    0.5 | 23147.5 |
|  172040 |   0.55 |   30710 |
|  195000 |    0.6 |   39312 |
|       0 |   0.65 |   49062 |
+---------+--------+---------+
14 rows in set (0.00 sec)
 
mysql>quit

The application's graphical interface is as follows:

Image

The graphical interface has undergone some changes:

No.
type
name
role
1
JTextField
txtConnexion
Database connection string ODBC
2
JScrollPane
JScrollPane1
container for the Textarea 3
3
JTextArea
txtStatus
Displays status messages, including error messages

The connection string entered in (1) has the following format: DSN;login;password with

DSN
the name DSN of the data source ODBC

login
the identity of a user with read access to the database

motdepasse
their password

The dbimpots database was created manually using MySQL. It is converted into the ODBC data source as follows:

  • Launch the 32-bit ODBC data source manager

Image

  • use the [Add] button to add a new ODBC data source

Image

  • Select the driver MySQL and click [Terminer]

Image

  • The MySQL driver requests certain information:
1
the name DSN to be given to the data source ODBC - can be anything
2
the machine on which SGBD MySQL runs—here, localhost. It is worth noting that the database could be a remote database. Local applications using the ODBC data source would not be aware of this. This would be the case, in particular, for our Java application.
3
the database MySQL to be used. MySQL is a SGBD that manages relational databases, which are sets of tables linked together by relationships. Here, we specify the name of the managed database.
4
the name of a user with access rights to this database
5
their password

Once the ODBC data source is defined, we can test our program:

 

Let’s examine the code that has been modified compared to the version graphical version without a database. Recall the code for the impots class used so far:

// creation of an impots class

public class impots{

    // data required for tax calculation
     // come from an external source

    private double[] limites, coeffR, coeffN;

     // manufacturer
    public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
         // check that the 3 arrays have the same size
        boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
        if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la même taille("+
                                LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
        // it's good
        this.limites=LIMITES;
        this.coeffR=COEFFR;
        this.coeffN=COEFFN;
    }//manufacturer

     // tAX CALCULATION
    public long calculer(boolean marié, int nbEnfants, int salaire){
         // calculating the number of shares
        double nbParts;
        if (marié) nbParts=(double)nbEnfants/2+2;
        else nbParts=(double)nbEnfants/2+1;
        if (nbEnfants>=3) nbParts+=0.5;
         // calculation of taxable income & family quota
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // tAX CALCULATION
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // return result
        return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
    }//calculate
}//class

This class constructs the three limit arrays, coeffR, coeffN, from three arrays passed as parameters to its constructor. We decide to add a new constructor that allows us to construct the same three arrays from a database:

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

    // dsnIMPOTS: DSN database name
     // userIMPOTS, mdpIMPOTS: database login/password

For this example, we decide not to implement this new constructor in the impots class but in a derived class impotsJDBC:

// imported packages
import java.sql.*;
import java.util.*;

public class impotsJDBC extends impots{
  // addition of a constructor for building
   // limit tables, coeffr, coeffn from table
   // database taxes
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: DSN database name
     // userIMPOTS, mdpIMPOTS: database login/password

     // data tables
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();

    // connection to base
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
    // creation of a Statement object
    Statement S=connect.createStatement();
    // select request
    String select="select limites, coeffr, coeffn from impots";
    // query execution
    ResultSet RS=S.executeQuery(select);
    while(RS.next()){
      // running line operation
      aLimites.add(RS.getString("limites"));
      aCoeffR.add(RS.getString("coeffr"));
      aCoeffN.add(RS.getString("coeffn"));
    }// next line
     // closing resources
    RS.close();
    S.close();
    connect.close();
     // data transfer to bounded arrays
    int n=aLimites.size();
    limites=new double[n];
    coeffR=new double[n];
    coeffN=new double[n];
    for(int i=0;i<n;i++){
      limites[i]=Double.parseDouble((String)aLimites.get(i));
      coeffR[i]=Double.parseDouble((String)aCoeffR.get(i));
      coeffN[i]=Double.parseDouble((String)aCoeffN.get(i));
    }//for
  }//manufacturer
}//class

The constructor reads the contents of the impots table from the database passed to it as parameters and populates the three limit arrays, coeffR, coeffN. A number of errors may occur. The constructor does not handle them but "passes them up" to the calling program:

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

If we look closely at the previous code, we can see that the class impotsJDBC directly uses the fields limites, coeffR, and coeffN from its base class impots. Since these are declared private:

    private double[] limites, coeffR, coeffN;

the class impotsJDBC does not have direct access to these fields. We therefore make an initial modification to the base class by writing:

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

The protected attribute allows classes derived from the impots class to have direct access to the fields declared with this attribute. We need to make a second modification. The constructor of the child class impotsJDBC is declared as follows:

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

We know that before constructing an object of a child class, we must first construct an object of the parent class. To do this, the child class’s constructor must explicitly call the parent class’s constructor using a super(....) statement. This is not done here because we cannot see which constructor of the parent class we could call. There is currently only one, and it is not suitable. The compiler will then search the parent class for a parameterless constructor that it could call. It does not find one, and this generates a compilation error. We therefore add a parameterless constructor to our impots class:

   // empty builder
  protected impots(){}

We declare it as "protected" so that it can only be used by child classes. The skeleton of the *impots* class is now as follows:

public class impots{

  // data required for tax calculation
   // come from an external source

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

   // empty builder
  protected impots(){}

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

   // tAX CALCULATION
  public long calculer(boolean marié, int nbEnfants, int salaire){
.............
    }//calculate
}//class

The action of the Initialize menu in our application becomes the following:

  void mnuInitialiser_actionPerformed(ActionEvent e) {
    // retrieve the connection string
    Pattern séparateur=Pattern.compile("\\s*;\\s*");
    String[] champs=séparateur.split(txtConnexion.getText().trim());
     // three fields are required
    if(champs.length!=3){
      // error
      txtStatus.setText("Chaîne de connexion (DSN;uid;mdp) incorrecte");
       // back to visual interface
      txtConnexion.requestFocus();
      return;
    }//if
     // load data
    try{
       // creation of impotsJDBC object
      objImpots=new impotsJDBC(champs[0],champs[1],champs[2]);
       // confirmation
      txtStatus.setText("Données chargées");
       // salary can be modified
      txtSalaire.setEditable(true);
       // no more chgt possible
      mnuInitialiser.setEnabled(false);
      txtConnexion.setEditable(false);
    }catch(Exception ex){
       // problem
      txtStatus.setText("Erreur : " + ex.getMessage());
      // end
      return;
    }//catch
  }

Once the objImpots object has been created, the application is identical to the graphical application already written. The reader is invited to refer to it.

6.4. Exercises

6.4.1. Exercise 1

Provide a graphical interface for the previous sql3 program.

6.4.2. Exercise 2

A Java applet can only access a database through the server from which it was loaded. Since an applet does not have access to the disk of the machine on which it is running, the database cannot be on the client machine using the applet. We are therefore in the following situation:

Image

The machine running the applet, the server, and the machine hosting the database may be three different machines. Here, we assume that the database is located on the server.

Problem 1

Write the following server application in Java:

  • The server application runs on a port passed to it as a parameter
  • When a client connects, the server application sends the message
200 - Bienvenue - Envoyez votre requête
  • The client then sends the parameters needed to connect to a database and the query it wants to execute in the following format:
Pilote Java/URL base/UID/MDP/requête SQL

The parameters are separated by a slash. The first four parameters are those of the sql3 program described in this chapter.

  • The server application then establishes a connection with the specified database, which must be located on the same machine as the server, and executes the query SQL on it. The results are returned to the client in the following format:
100 - ligne1
100 - ligne2

...

if it is the result of a Select query, or

101 - nbLignes

to return the number of rows affected by an update query. If a database connection or query execution error occurs, the application returns

500 - Message d’erreur
  • Once the query has been executed, the server application closes the connection.

Problem 2

Create a Java applet to query the previous server. You can draw inspiration from the graphical interface in the previous Exercise 1. Since the server port may vary, it will be entered via the applet’s interface. The same applies to all parameters required to send the row:

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

that the client must send to the server.

6.4.3. Exercise 3

The following text describes a problem originally intended to be solved in Visual Basic. Adapt it for processing in Java within an applet. The applet will rely on the server from Exercise 2. The graphical interface may be modified to account for the new execution context.

We propose to create an application that highlights the various possible update operations on a table in the ACCESS database. The database ACCESS is named articles.mdb. It has a single table named articles that lists the items sold by a company. Its structure is as follows:

name
type
code
4-character item code
name
its name (string)
price
its price (actual)
stock_actu
current stock (integer)
stock_mini
the minimum stock (integer) below which the item must be restocked

We propose to view and update this table using the following form:

Image

The controls on this form are as follows:

No.
Type
Name
Function
1
textbox
record
number of the displayed record
enabled is false
2
textbox
code
item code
3
textbox
name
item name
4
textbox
price
item price
5
textbox
current
current stock of the item
6
textbox
minimum
minimum stock of the item
7
data
data1
data control associated with the database
databasename=path to the articles.mdb file
recordsource=articles
connect=access
8
HScrollBar
position
allows you to navigate the table
9
button
OK
allows you to confirm an update - appears only during the update
10
button
Cancel
allows you to cancel an update - appears only during the update
11
frame
frame1
for aesthetic purposes
12
textbox
basename
name of the open database
enabled is set to false
13
textbox
sourcename
name of the open table
enabled is set to false

Creating the sheet under VB

Control
Special features
data1
The databasename and recordsource fields are filled in. databasename must refer to the Access database articles.mdb in your directory, and recordsource to the articles table.
code
This is a text box that we want to link to the code field of the current record in data1. To do this, we fill in two fields:
datasource: enter data1 to indicate that the text box is linked to the table associated with data1
datafield: select the code field from the articles table
After these steps, the code text box will always contain the code field of the current record in data1. Conversely, changing the content of this text box will update the code field of the current record.
Do the same for the other textboxes
name
datasource: data1 datafield: name
price
datasource: data1 datafield: price
current
datasource: data1 datafield: stock_actu
minimum
datasource: data1 datafield: stock_mini

The menus

The menu structure is as follows

Edit
Browse
Exit
Add
Back
 
Edit
Next
 
Delete
First
 
 
Last
 

The roles of the various options are as follows:

menu
name
function
Ajouter
mnuadd
to add a new record to the items table
Supprimer
mnu-delete
to delete the currently displayed record from the items table
Modifier
mnumodify
to edit the currently displayed record in the item table
Précédent
mnuprecedent
to go to the previous record
Suivant
mnusuivant
to skip to the next recording
Premier
previous
to skip to the first track
Dernier
mnudernier
to skip to the last recording
Quitter
mnuquit
to exit the application

Loading the sheet

During the form_load event, the table associated with data1 is opened (data1.refresh). If the table fails to open, an error message is displayed and the program terminates (end). Otherwise, the form is displayed with the first record of the articles table visible. No entries can be made in the fields (enabled property set to false). Entry is only possible using the Add and Edit options. The OK and Cancel buttons are hidden (visible=false).


Part 1

We propose to build the procedures related to the various menu options as well as the OK and Cancel buttons. Initially, we will ignore the following points:

  • enabling/disabling certain menu options: for example, the option Next button must be disabled if the cursor is positioned on the last record in the table
  • managing the horizontal scrollbar

Browse/Next menu

  • moves to the next record (data1.RecordSet.MoveNext) if you are not at the end of the file (data1.RecordSet.EOF). Updates the record textbox (data1.recordset.absoluteposition/ data1.recordset.recordcount).

Browse menu/Previous

  • Moves to the previous record (data1.RecordSet.MovePrevious) if not at the beginning of the file (data1.RecordSet.BOF). Updates the record text box.

Browse/First menu

  • moves to the first record (data1.RecordSet.MoveFirst) if the file is not empty (data1.recordset.recordcount=0). Updates the record textbox.

Browse menu/Last

  • moves to the last record (data1.RecordSet.MoveLast) if the file is not empty (data1.recordset.recordcount=0). Updates the record textbox.

Edit/Add menu

  • allows you to add a record to the table
  • enters Record Add mode (data1.recordset.addnew)
  • allows entries in the 5 fields: code, name, price, etc. (enabled=true)
  • disables the Edit, Browse, and Exit menus (enabled=false)
  • displays the OK and Cancel buttons (visible=true)

OK button

  • saves a record change (data1.recordset.Update)
  • hides the OK and Cancel buttons (visible=false)
  • enables the Edit, Browse, and Exit options (enabled=true)
  • updates the record textbox

Cancel button

  • cancels a record update (data1.recordset.CancelUpdate)
  • hides the OK and Cancel buttons (visible=false)
  • enables the Edit, Browse, and Exit options (enabled=true)
  • updates the record textbox

Edit/Modify menu

  • allows you to edit the record displayed on the form
  • enters record edit mode (data1.recordset.edit)
  • Enables data entry in the 4 fields: name, price, etc. (enabled=true) but not in the code field (enabled=false)
  • disables the Edit, Browse, and Exit menus (enabled=false)
  • displays the OK and Cancel buttons (visible=true)

Edit/Delete menu

  • allows you to delete (data1.recordset.delete) the displayed record from the table
  • moves to the next record (data1.recordset.movenext)

Exit menu

  • unloads the sheet (unload me)

event form_unload (cancel as integer)

  • triggered by the "unload me" operation or closing the form via Alt-F4 or a double-click on the system tray icon, so not necessarily by the option exit.
  • displays the question “Do you really want to exit the application?” with two Yes/No buttons (msgbox with style=vbyes+vbno)
  • if the answer is No (=vbno), set cancel to -1 and exit the form_unload procedure. A cancel value of -1 indicates that closing the window is denied.
  • If the answer is Yes (=vbyes), the database is closed (data1.recordset.close, data1.database.close).

Part 2 - Menu Management

Here, we focus on enabling/disabling menus. After each operation that changes the current record, we will call a procedure that we can call Oueston. This procedure will check the following conditions:

. if the file is empty, we

  • disable the Browse, Edit/Delete menus,
  • enable the others

. If the current record is the first record, we

  • disable Browse/Previous
  • and allow the rest

. If the current record is the last one,

  • disable Browse/Next
  • allow the rest

Part 3 - Managing the horizontal slider

A horizontal slider has three important fields:

  • min: its minimum value
  • max: its maximum value
  • value: its current value

Initializing the drive

Upon loading (form_load), the drive will be initialized as follows:

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

Note that immediately after opening the database (data1.refresh), the number of records in the table represented by data1.recordset.recordcount is incorrect. You must go to the end of the table (MoveLast), then return to the beginning of the table (MoveFirst) for it to be correct.

Direct action on the drive

The position of the drive’s cursor represents the position in the table.

When the user adjusts the drive (referred to as "position" here), the event position_change is triggered. In this event, we will change the current record in the table so that it reflects the movement made on the slider. To do this, we will use the absoluteposition field of data1.recordset. When we assign the value i to this field, record #i in the table becomes the current record. The records are numbered starting from 0 and therefore have a number within the range [0,data1.recordset.recordcount-1]. In the procedure position_change, we simply need to write

    data1.recordset.absoluteposition=position.value

so that the current record displayed on the form reflects the adjustment made on the drive.

Once this is done, we will then call the Oueston procedure to update the menus.

Updating the drive

Since the position of the drive’s cursor must reflect the position in the table, the drive’s value must be updated every time there is a change in the current record in the table, triggered by one of the menus. Since each of these calls the Oueston procedure, it is best to place this update within that procedure as well. Simply write the following here:

    position.value=data1.recordset.absoluteposition

Part 4 - Option Search

We add the option Browse/Search, which allows the user to view an item by entering its code.

When this option is activated, the following steps occur:

  • the system switches to Add New mode, solely to avoid modifying the current record that was open when the option was activated,
  • We enable data entry in the code field and clear the contents of the record field,
  • We disable the menus and display the OK and Cancel buttons
  • When the user clicks OK, we need to search for the record corresponding to the code entered by the user. However, the OK_click procedure is already used for the Browse/Add and Browse/Modify options. To distinguish between these cases, we need to manage a global variable, which we will call `state` here, that will have three possible values: “add,” “edit,” and “search.” The procedures associated with the OK and Cancel buttons will use this variable to determine the context in which they are called.
  • If state is “search,” in the procedure linked to OK, we
    • cancel the addnew operation (data1.recordset.cancelupdate) because we did not intend to add a record. Note that the current record then reverts to the one that was on the screen before the Browse/Search operation.
    • Construct the search criteria based on the code and launch the search (data1.recordset.findfirst criteria),
    • if the search fails (data1.recordset.nomatch=true), the user is notified, then the system returns to add mode (addnew) and exits the OK procedure. The user must re-enter a new code or select option Cancel.
    • If the search is successful, the found record becomes the new current record. The menus are restored, the OK/Cancel buttons are hidden, input in the code field is disabled, and the procedure is exited.
  • . If the status is "search," in the procedure linked to Cancel, we
    • cancel the addnew operation (data1.recordset.cancelupdate). The system will then automatically return to the current record from before the Browse/Search operation.
    • Restores the menus, hides the OK/Cancel buttons, disables input in the code field, and exits the procedure.

Part 5 - Code Management

An item must be uniquely identified by its code. Ensure that in the option Browse/Add procedure, the addition is rejected if the record to be added has an item code that already exists in the table.

6.4.4. Exercise 4

Here we present a web application based on the server from Exercise 2. It is a basic e-commerce application.

The customer orders items using the following web interface:

Image

They can perform the following operations:

  • select an item from the drop-down list
  • specify the desired quantity
  • confirm their purchase by clicking the Buy button
  • Their purchase is displayed in the list of purchased items
  • he can remove items from this list by selecting an item and clicking the Remove button
  • When they click the Summary button, they see the following summary:

Image

The summary allows the user to view the details of their invoice. The user can access details about an item selected from the drop-down list by clicking the "Information" button:

Image

Once the user has requested the invoice summary, they can confirm it on the next page. To do so, they must enter their email address and confirm their order using the appropriate button.

Image

Before saving their order, the application asks for confirmation:

Image

Once the order is confirmed, the application processes it and displays a confirmation page:

Image

In reality, the application does not process the order. It simply sends an email to the user asking them to pay for the purchases:

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'payable to SuperPrix and sent to the following address:

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

Question: Create the equivalent of this web application using a Java applet.