Skip to content

6. Gestión de bases de datos con API JDBC

6.1. Aspectos generales

Existen numerosas bases de datos en el mercado. Con el fin de unificar el acceso a las bases de datos en Windows, Microsoft ha desarrollado una interfaz denominada ODBC (Open DataBase Connectivity). Esta capa oculta las particularidades de cada base de datos tras una interfaz estándar. En Windows existen numerosos controladores que facilitan el acceso a las bases de datos. A continuación se muestra, por ejemplo, una lista de controladores instalados en un equipo con Win95:

Image

Una aplicación que utilice estos controladores puede acceder a cualquiera de las bases de datos mencionadas anteriormente sin necesidad de reescribirla.

Image

Para que las aplicaciones Java también puedan aprovechar la interfaz ODBC, Sun ha creado la interfaz JDBC (Java DataBase Connectivity), que se intercalará entre la aplicación Java y la interfaz ODBC:

Image

6.2. Pasos importantes en la gestión de bases de datos

6.2.1. Introducción

En una aplicación JAVA que utilice una base de datos con la interfaz JDBC, suelen darse los siguientes pasos:

  1. Conexión a la base de datos
  2. Envío de consultas SQL a la base de datos
  3. Recepción y procesamiento de los resultados de estas consultas
  4. Cierre de la conexión

Los pasos 2 y 3 se repiten, y el cierre de la conexión solo tiene lugar al finalizar la consulta de la base de datos. Se trata de un esquema relativamente clásico con el que quizá esté familiarizado si ha consultado una base de datos de forma interactiva. A continuación, detallaremos cada uno de estos pasos con un ejemplo. Consideremos una base de datos ACCESS denominada ARTICLES y con la siguiente estructura:

nombre
tipo
code
código del artículo de 4 caracteres
nom
su nombre (cadena de caracteres)
prix
su precio (real)
stock_actu
su stock actual (número entero)
stock_mini
el stock mínimo (en número entero) por debajo del cual hay que reponer el artículo

Esta base de datos ACCESS se define como fuente de datos «de usuario» en el gestor de bases de datos ODBC:

Image

Image

Sus características se especifican mediante el botón Configurer de la siguiente manera:

Image

Esta configuración consiste básicamente en asociar a la base ARTICLES el archivo Access articles.mdb correspondiente a dicha base. Una vez hecho esto, la base ARTICLES queda accesible para las aplicaciones que utilizan la interfaz ODBC.

6.2.2. El paso de conexión

Para utilizar una base de datos, una aplicación Java debe realizar primero una fase de conexión. Esto se lleva a cabo mediante el siguiente método de clase:

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

con

DriverManager: clase Java que contiene la lista de controladores disponibles para la aplicación

Connection: clase Java que establece una conexión entre la aplicación y la base de datos, a través de la cual la aplicación enviará consultas SQL a la base de datos y recibirá los resultados

URL: nombre que identifica la base de datos. Este nombre es análogo a los URL de Internet. Por eso forma parte de la clase URL. Sin embargo, Internet no interviene en absoluto aquí. El URL tiene la siguiente forma:

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

En nuestros ejemplos, en los que utilizaremos exclusivamente controladores ODBC, el controlador se denomina odbc. La tercera parte del URL está formada por el nombre de la fuente con los posibles parámetros. En nuestros ejemplos, se tratará de fuentes ODBC conocidas por el sistema. Así, el URL de la fuente de datos Articles definida anteriormente será

jdbc:odbc:Artículos

id: nombre de usuario (login)

mdp: contraseña del usuario (password)

En resumen, el programa se conecta a una base de datos:

  • identificada por un nombre (URL)
  • bajo la identidad de un usuario (id, contraseña)

Si estos tres parámetros son correctos y existe el controlador capaz de garantizar la conexión de la aplicación Java a la base de datos especificada, se establece una conexión entre la aplicación Java y la base de datos. Esta conexión se materializa para el programa mediante el objeto de tipo Connection devuelto por la clase DriverManager. Dado que esta conexión puede fallar por diversas razones, es posible que se produzca una excepción. Por lo tanto, se escribirá:


    Connection connexion=null;
    URL base=...;
    String id=...;
    String mdp=...;
try{
        connexion=DriverManager.getConnection(base,id,mdp);
} catch (Exception e){
        // gestionar la excepción
}

Para que sea posible la conexión a una base de datos, es necesario disponer del controlador adecuado. En nuestros ejemplos, se tratará del controlador ODBC, capaz de gestionar la base de datos solicitada. Si bien es necesario que este controlador esté disponible en la lista de controladores ODBC presentes en el equipo, también es necesario disponer de la clase JAVA, que actuará como interfaz con él. Para ello, la aplicación solicitará la clase que necesita de la siguiente manera:

    Class.forName(String nomClasse)

La clase Class no está relacionada en absoluto con la interfaz JDBC. Se trata de una clase general de gestión de clases. Su método estático forName permite cargar dinámicamente una clase y, por lo tanto, beneficiarse de sus atributos y métodos estáticos. La clase que actúa como interfaz con los controladores ODBC de MS para Windows se llama «sun.jdbc.odbc.JdbcOdbcDriver». Por lo tanto, se escribirá (el método puede generar una excepción):

try{
    Class.forName(« sun.jdbc.odbc.JdbcOdbcDriver ») ;
} catch (Exception e){
    // gestionar la excepción (clase inexistente)
}

Las clases necesarias para la interfaz JDBC se encuentran en el paquete java.sql. Por lo tanto, al inicio del programa se escribirá:

    import java.sql.*;

A continuación se muestra un programa que permite conectarse a una base de datos:

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

// Llamada: pg PILOTE URL UID MDP
// se conecta a la base de datos URL mediante la clase JDBC PILOTE
// el usuario UID se identifica mediante una contraseña MDP

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

    public static void main(String arg[]){
        // comprobación del número de argumentos
        if(arg.length<2 || arg.length>4)
            erreur(syntaxe,1);
        // conexión a la base de datos
        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);
        }
        // cierre de la base de datos
        try{
            connect.close();
            System.out.println("Base " + arg[1] + " fermée");
        } catch (Exception e){}
    }// salida

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

A continuación se muestra un ejemplo de ejecución:

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. Envío de consultas a la base de datos

La interfaz JDBC permite enviar consultas SQL a la base de datos conectada a la aplicación Java, así como procesar los resultados de dichas consultas. El lenguaje SQL (Structured Query Language) es un lenguaje de consultas estandarizado para bases de datos relacionales. En él se distinguen varios tipos de consultas:

  • las consultas de interrogación de la base de datos (SELECT)
  • las consultas de actualización de la base de datos (INSERT, DELETE, UPDATE)
  • las consultas de creación/eliminación de tablas (CREATE, DELETE)

Se da por supuesto que el lector conoce los fundamentos del lenguaje SQL.

6.2.3.1. La clase Statement

Para enviar cualquier consulta SQL a una base de datos, la aplicación Java debe disponer de un objeto de tipo Statement. Este objeto almacenará, entre otras cosas, el texto de la consulta. Este objeto está necesariamente vinculado a la conexión activa. Por lo tanto, es un método de la conexión establecida el que permite crear los objetos Statement necesarios para enviar las consultas SQL. Si connexion es el objeto que representa la conexión con la base de datos, se obtiene un objeto Statement de la siguiente manera:

    Statement requete=connexion.CreateStatement();

Una vez obtenido un objeto Statement, se pueden emitir consultas SQL. Esto se hará de forma diferente según si la consulta es de consulta o de actualización de la base de datos.

6.2.3.2. Enviar una consulta de consulta a la base de datos

Una consulta suele ser una consulta del tipo:

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

Solo son obligatorias las palabras clave de la primera línea; las demás son opcionales. Existen otras palabras clave que no se muestran aquí.

  1. Se realiza una unión con todas las tablas que aparecen después de la palabra clave «from»
  2. Solo se conservan las columnas que aparecen tras la palabra clave «select»
  3. Solo se conservan las filas que cumplen la condición de la palabra clave «where»
  4. Las filas resultantes, ordenadas según la expresión de la palabra clave «order by», conforman el resultado de la consulta.

El resultado de una consulta select es una tabla. Si tomamos como referencia la tabla ARTICLES anterior y queremos obtener los nombres de los artículos cuyo stock actual está por debajo del umbral mínimo, escribiremos: select nombre from artículos where stock_actu<stock_mini. Si queremos ordenarlos alfabéticamente por nombre, escribiremos: select nombre from artículos where stock_actu<stock_mini order by nombre

Para ejecutar este tipo de consulta, la clase Statement ofrece el método executeQuery:

    ResultSet executeQuery(String requête)

donde requête es el texto de la consulta SELECT que se va a enviar.

Así, si

  • connexion es el objeto que representa la conexión con la base de datos
  • Statement s=connexion.createStatement() crea el objeto Statement necesario para el envío de las consultas SQL
  • ResultSet rs=s.executeQuery(« select nombre from artículos where stock_actu<stock_mini») ejecuta una consulta select y asigna las filas resultantes de la consulta a un objeto de tipo ResultSet.

6.2.3.3. La clase ResultSet: resultado de una consulta SELECT

Un objeto de tipo ResultSet representa una tabla, es decir, un conjunto de filas y columnas. En un momento dado, solo se tiene acceso a una fila de la tabla, denominada fila actual. Al crear inicialmente el ResultSet, la línea actual es la línea n.º 1 si el ResultSet no está vacío. Para pasar a la línea siguiente, la clase ResultSet dispone del método next:

    boolean next()

Este método intenta pasar a la línea siguiente de ResultSet y devuelve true si tiene éxito, y false en caso contrario. Si tiene éxito, la línea siguiente se convierte en la nueva línea actual. La línea anterior se pierde y no se podrá volver atrás para recuperarla. La tabla de ResultSet tiene las columnas col1, col2,.... Para utilizar los distintos campos de la línea actual, se dispone de los siguientes métodos:

Type getType("coli") 

para obtener el campo «coli» de la línea actual. Type designa el tipo del campo coli. Es bastante habitual utilizar el método getString en todos los campos, lo que permite obtener el contenido del campo como una cadena de caracteres. A continuación, se realiza la conversión si es necesario. Si no se conoce el nombre de la columna, se pueden utilizar los métodos

Type getType(i) 

, donde i es el índice de la columna deseada (i >= 1).

6.2.3.4. Un primer ejemplo

A continuación se muestra un programa que muestra el contenido de la base de datos ARTICLES creada anteriormente:

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

// muestra el contenido de una base de datos del sistema ARTICLES

public class articles1{

    static final String DB="ARTICLES";        // base de datos a procesar

    public static void main(String arg[]){

        Connection connect=null;        // conexión con la base de datos
        Statement S=null;                // objeto de envío de consultas
        ResultSet RS=null;            // tabla de resultados de una consulta
        try{
            // conexión a la base de datos
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
            System.out.println("Connexion avec la base " + DB + " établie");
            // creación de un objeto Statement
            S=connect.createStatement();
            // ejecución de una consulta SELECT
            RS=S.executeQuery("select * from " + DB);
            // procesamiento de la tabla de resultados
            while(RS.next()){                // mientras haya una fila que procesar
                // se muestra en pantalla
                System.out.println(RS.getString("code")+","+
                    RS.getString("nom")+","+
                    RS.getString("prix")+","+
                    RS.getString("stock_actu")+","+
                    RS.getString("stock_mini"));
            }// fila siguiente
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // cierre de la base
        try{
            connect.close();
            System.out.println("Base " + DB + " fermée");
        } catch (Exception e){}
    }// mano

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

Los resultados obtenidos son los siguientes:

Connexion avec la base ARTICLES établie
a300,vélo,1202,30,2
d600,arc,5000,10,2
d800,canoé,1502,12,6
x123,fusil,3000,10,2
s345,skis nautiques,1800,3,2
f450,essai3,3,3,3
f807,cachalot,200000,0,0
z400,léopard,500000,1,1
g457,panthère,800000,1,1
Base ARTICLES fermée

6.2.3.5. La clase ResultSetMetadata

En el ejemplo anterior, conocemos los nombres de las columnas del ResultSet. Si no los conocemos, no podemos utilizar el método getType(nom_colonne). En su lugar, utilizaremos getType(n.º de columna). Sin embargo, para obtener todas las columnas, necesitaríamos saber cuántas columnas tiene el ResultSet obtenido. La clase ResultSet no nos proporciona esta información. Es la clase ResultSetMetaData la que nos la proporciona. En términos más generales, esta clase nos ofrece información sobre la estructura de la tabla, es decir, sobre la naturaleza de sus columnas.

Se puede acceder a la información sobre la estructura de un ResultSet instanciando primero un objeto ResultSetMetaData. Si RS es un ResultSet, el ResultSetMetaData asociado se obtiene mediante:

    RS.getMetaData()

Cabe destacar dos métodos útiles en la clase ResultSetMetaData:

  1. int getColumnCount(), que devuelve el número de columnas del ResultSet
  2. String getColumnLabel(int i), que devuelve el nombre de la columna i de ResultSet (i >= 1)

6.2.3.6. Un segundo ejemplo

El programa anterior mostraba el contenido de la base de datos ARTICLES. Aquí escribimos un programa que ejecuta en la base de datos ARTICLES cualquier consulta SQL o Select que el usuario introduzca mediante el teclado.

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

// muestra el contenido de una base de datos del sistema ARTICLES

public class sql1{

    static final String DB="ARTICLES";        // base de datos a procesar

    public static void main(String arg[]){

        Connection connect=null;        // conexión con la base de datos
        Statement S=null;                // objeto de envío de consultas
        ResultSet RS=null;            // tabla de resultados de una consulta
        String select;                    // texto de la consulta SQL select
        int nbColonnes;                // número de columnas de ResultSet

        // creación de un flujo de entrada del teclado
        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{
            // conexión a la base de datos
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
            System.out.println("Connexion avec la base " + DB + " établie");
            // creación de un objeto Statement
            S=connect.createStatement();
            // bucle de ejecución de las consultas SQL introducidas mediante el teclado
            System.out.print("Requête : ");
            select=in.readLine();
            while(!select.equals("fin")){
                // ejecución de la consulta
                RS=S.executeQuery(select);
                // número de columnas
                nbColonnes=RS.getMetaData().getColumnCount();
                // procesamiento de la tabla de resultados
                System.out.println("Résultats obtenus\n\n");
                while(RS.next()){                // mientras quede una fila por procesar
                    // se muestra en pantalla
                    for(int i=1;i<nbColonnes;i++)
                        System.out.print(RS.getString(i)+",");
                    System.out.println(RS.getString(nbColonnes));
                }// línea siguiente
                // siguiente consulta
                System.out.print("Requête : ");
                select=in.readLine();                
            }// while
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // cierre de la base de datos y del flujo de entrada
        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);
    }
}// clase    

Estos son algunos de los resultados obtenidos:

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. Enviar una solicitud de actualización de la base de datos

Un objeto de tipo Statement permite almacenar las solicitudes SQL. El método que utiliza este objeto para enviar solicitudes de actualización SQL (INSERT, UPDATE, DELETE) ya no es el método executeQuery analizado anteriormente, sino el método executeUpdate:

    int executeUpdate(String requête)

La diferencia radica en el resultado: mientras que executeQuery devolvía la tabla de resultados (ResultSet), executeUpdate devuelve el número de filas afectadas por la operación de actualización.

6.2.3.8. Un tercer ejemplo

Retomamos el programa anterior y lo modificamos ligeramente: las consultas introducidas mediante el teclado son ahora consultas de actualización de la base de datos ARTICLES.

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

// muestra el contenido de una base de datos del sistema ARTICLES

public class sql2{

    static final String DB="ARTICLES";    // base de datos a explotar

    public static void main(String arg[]){

        Connection connect=null;        // conexión con la base de datos
        Statement S=null;                // objeto de envío de consultas
        ResultSet RS=null;            // tabla de resultados de una consulta
        String sqlUpdate;                // texto de la consulta SQL de actualización
        int nbLignes;                    // número de líneas afectadas por una actualización

        // creación de un flujo de entrada del teclado
        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{
            // conexión a la base de datos
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
            System.out.println("Connexion avec la base " + DB + " établie");
            // creación de un objeto Statement
            S=connect.createStatement();
            // bucle de ejecución de las consultas SQL introducidas mediante el teclado
            System.out.print("Requête : ");
            sqlUpdate=in.readLine();
            while(!sqlUpdate.equals("fin")){
                // ejecución de la consulta
                nbLignes=S.executeUpdate(sqlUpdate);
                // seguimiento
                System.out.println(nbLignes + " ligne(s) ont été mises à jour");
                // siguiente consulta
                System.out.print("Requête : ");
                sqlUpdate=in.readLine();                
            }// while
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // cierre de la base de datos y del flujo de entrada
        try{
            // se liberan los recursos asociados a la base de datos
            RS.close();
            S.close();
            connect.close();
            System.out.println("Base " + DB + " fermée");
            // cierre del flujo del teclado
            in.close();
        } catch (Exception e){}
    }// mano

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

Este es el resultado de varias ejecuciones de los programas sql1 y sql2:

Lista de líneas de la base de datos 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

Modificamos algunas líneas:


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

Comprobación:


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

Se añade una línea:


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

Comprobación:


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

Se elimina una línea:


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

Verificación:


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. Enviar cualquier solicitud SQL

El objeto Statement, necesario para enviar solicitudes SQL, dispone de un método execute capaz de ejecutar cualquier tipo de solicitud SQL:

    boolean execute(String requête)

El resultado devuelto es el valor booleano true si la consulta ha devuelto un ResultSet (executeQuery), y false si ha devuelto un número (executeUpdate). El valor ResultSet obtenido se puede recuperar mediante el método getResultSet, y el número de líneas actualizadas, mediante el método getUpdateCount. Por lo tanto, se escribirá:


Statement S=...;
ResultSet RS=...;
int nbLignes;
String requête=...;
// ejecución de una consulta SQL
if (S.execute(requête)){
    // tenemos un conjunto de resultados
    RS=S.getResultSet();
    // procesamiento del ResultSet
    ...
} else {
    // era una consulta de actualización
    nbLignes=S.getUpdateCount();
    ...
}

6.2.3.10. Cuarto ejemplo

Retomamos la idea de los programas sql1 y sql2 en un programa sql3, que ahora es capaz de ejecutar cualquier consulta SQL introducida mediante el teclado. Para que el programa sea más general, las características de la base de datos que se va a utilizar se pasan como parámetros al programa.

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

// llamada: pg PILOTE URL UID MDP
// se conecta a la base de datos URL mediante la clase JDBC PILOTE
// el usuario UID se identifica mediante una contraseña MDP

public class sql3{

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

    public static void main(String arg[]){



        // comprobación del número de argumentos
        if(arg.length<2 || arg.length>4)
            erreur(syntaxe,1);

        // inicialización de los parámetros de conexión
        Connection connect=null;
        String uid="";
        String mdp="";
        if(arg.length>=3) uid=arg[2];
        if(arg.length==4) mdp=arg[3];        

        // otros datos
        Statement S=null;                        // objeto de envío de consultas
        ResultSet RS=null;                    // tabla de resultados de una consulta
        String sqlText;                            // Texto de la consulta SQL que se va a ejecutar
        int nbLignes;                                // número de líneas afectadas por una actualización
        int nbColonnes;                            // Número de columnas de un ResultSet

        // creación de un flujo de entrada de teclado
        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{
            // conexión a la base de datos
            Class.forName(arg[0]);
            connect=DriverManager.getConnection(arg[1],uid,mdp);
            System.out.println("Connexion avec la base " + arg[1] + " établie");
            // creación de un objeto Statement
            S=connect.createStatement();
            // bucle de ejecución de las consultas SQL introducidas mediante el teclado
            System.out.print("Requête : ");
            sqlText=in.readLine();
            while(!sqlText.equals("fin")){
                // ejecución de la consulta
                try{
                    if(S.execute(sqlText)){
                        // se ha obtenido un ResultSet: se procesa
                        RS=S.getResultSet();
                        // número de columnas
                        nbColonnes=RS.getMetaData().getColumnCount();
                        // procesamiento de la tabla de resultados
                        System.out.println("\nRésultats obtenus\n-----------------\n");
                        while(RS.next()){                // mientras haya una fila que procesar
                            // se muestra en pantalla
                            for(int i=1;i<nbColonnes;i++)
                                System.out.print(RS.getString(i)+",");
                            System.out.println(RS.getString(nbColonnes));
                        }// línea siguiente de ResultSet
                    } else {
                        // era una solicitud de actualización
                        nbLignes=S.getUpdateCount();
                        // seguimiento
                        System.out.println(nbLignes + " ligne(s) ont été mises à jour");
                    }//if
                } catch (Exception e){
                    System.out.println("Erreur " +e);
                }
                // siguiente solicitud
                System.out.print("\nNouvelle Requête : ");
                sqlText=in.readLine();                
            }// while
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // cierre de la base de datos y del flujo de entrada
        try{
            // se liberan los recursos asociados a la base
            RS.close();
            S.close();
            connect.close();
            System.out.println("Base " + arg[1] + " fermée");
            // cierre del flujo del teclado
            in.close();
        } catch (Exception e){}
    }// main

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

Se crea el siguiente archivo de consultas:

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

El programa se ejecuta de la siguiente manera:

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

Por lo tanto, el programa lee los datos de entrada del archivo requetes y escribe los resultados en el archivo results. Los resultados obtenidos son los siguientes:

Connexion avec la base jdbc:odbc:articles établie

Requête : (requete 1 du fichier des requetes : select * from articles)
Résultats obtenus
-----------------

a300,vélo,1202,31,3
d600,arc,5000,10,3
d800,canoé,1502,13,7
x123,fusil,3000,10,3
s345,skis nautiques,1800,3,3
f450,essai3,3,3,4
f807,cachalot,200000,0,1
z400,léopard,500000,1,2
g457,panthère,800000,1,2

Nouvelle Requête : (requete 2 du fichier des requetes : update articles set stock_mini=stock_mini+5 where stock_mini<5)

8 ligne(s) ont é mises à jour

Nouvelle Requête : (requete 3 du fichier des requetes : select nom,stock_mini from articles)

Résultats obtenus
-----------------

vélo,8
arc,8
canoé,7
fusil,8
skis nautiques,8
essai3,9
cachalot,6
léopard,7
panthère,7

Nouvelle Requête : (requete 4 du fichier des requetes : insert into articles (code,nom,prix,stock_actu,stock_mini) values ('x400','nouveau',100,20,10))

1 ligne(s) ont é mises à jour

Nouvelle Requête : (requete 5 du fichier des requetes : select * from articles)

Résultats obtenus
-----------------

a300,vélo,1202,31,8
d600,arc,5000,10,8
d800,canoé,1502,13,7
x123,fusil,3000,10,8
s345,skis nautiques,1800,3,8
f450,essai3,3,3,9
f807,cachalot,200000,0,6
z400,léopard,500000,1,7
g457,panthère,800000,1,7
x400,nouveau,100,20,10

Nouvelle Requête : (requete 6 du fichier des requêtes : delete from articles where code='x400')


1 ligne(s) ont é mises à jour

Nouvelle Requête : (requete 7 du fichier des requêtes : select * from articles)

Résultats obtenus
-----------------

a300,vélo,1202,31,8
d600,arc,5000,10,8
d800,canoé,1502,13,7
x123,fusil,3000,10,8
s345,skis nautiques,1800,3,8
f450,essai3,3,3,9
f807,cachalot,200000,0,6
z400,léopard,500000,1,7
g457,panthère,800000,1,7

6.3. IMPOTS con una base de datos

La última vez que abordamos el tema del cálculo de impuestos, lo hicimos con una interfaz gráfica y los datos se almacenaban en un archivo. Retomamos esta versión, pero ahora suponiendo que los datos se encuentran en una base de datos ODBC-MySQL. MySQL es un SGBD de dominio público que se puede utilizar en diferentes plataformas, entre ellas Windows y Linux. Con este SGBD, se ha creado una base de datos llamada «dbimpots» que contiene una única tabla denominada «impots». El acceso a la base de datos se controla mediante un nombre de usuario y una contraseña, en este caso «admimpots» y «mdpimpots». La captura de pantalla muestra cómo utilizar la base de datos «dbimpots» con MySQL:


C:\Program Files\EasyPHP\mysql\bin>mysql -u admimpots -p
Enter password: *********
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 3.23.49-max-nt

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> use dbimpots;
Database changed

mysql> show tables;
+--------------------+
| Tables_in_dbimpots |
+--------------------+
| impots             |
+--------------------+
1 row in set (0.00 sec)

mysql> describe impots;
+---------+--------+------+-----+---------+-------+
| Field   | Type   | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limites | double | YES  |     | NULL    |       |
| coeffR  | double | YES  |     | NULL    |       |
| coeffN  | double | YES  |     | NULL    |       |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)

mysql> select * from impots;
+---------+--------+---------+
| limites | coeffR | coeffN  |
+---------+--------+---------+
|   12620 |      0 |       0 |
|   13190 |   0.05 |     631 |
|   15640 |    0.1 |  1290.5 |
|   24740 |   0.15 |  2072.5 |
|   31810 |    0.2 |  3309.5 |
|   39970 |   0.25 |    4900 |
|   48360 |    0.3 |    6898 |
|   55790 |   0.35 |  9316.5 |
|   92970 |    0.4 |   12106 |
|  127860 |   0.45 |   16754 |
|  151250 |    0.5 | 23147.5 |
|  172040 |   0.55 |   30710 |
|  195000 |    0.6 |   39312 |
|       0 |   0.65 |   49062 |
+---------+--------+---------+
14 rows in set (0.00 sec)

mysql>quit

La interfaz gráfica de la aplicación es la siguiente:

Image

La interfaz gráfica ha sufrido algunas modificaciones:

n.º
tipo
nombre
función
1
JTextField
txtConnexion
cadena de conexión a la base de datos ODBC
2
JScrollPane
JScrollPane1
contenedor para el campo de texto 3
3
JTextArea
txtStatus
muestra mensajes de estado, en particular mensajes de error

La cadena de conexión introducida en (1) tiene el siguiente formato: DSN;login;contraseña, con

DSN
el nombre DSN de la fuente de datos ODBC

login
la identidad de un usuario con derechos de lectura sobre la base de datos

motdepasse
su contraseña

La base de datos dbimpots se ha creado manualmente con MySQL. Se transforma en la fuente de datos ODBC de la siguiente manera:

  • se inicia el gestor de fuentes de datos ODBC de 32 bits

Image

  • se utiliza el botón [Add] para añadir una nueva fuente de datos ODBC

Image

  • Se selecciona el controlador MySQL y se realiza [Terminer]

Image

  • El controlador MySQL solicita una serie de datos:
1
el nombre DSN que se le va a dar a la fuente de datos ODBC —puede ser cualquiera—
2
el equipo en el que se ejecuta el SGBD MySQL —en este caso, localhost—. Cabe destacar que la base de datos podría ser una base de datos remota. Las aplicaciones locales que utilizan la fuente de datos ODBC no se darían cuenta de ello. Este sería el caso, en particular, de nuestra aplicación Java.
3
la base de datos MySQL que se va a utilizar. MySQL es un SGBD que gestiona bases de datos relacionales, que son conjuntos de tablas relacionadas entre sí mediante relaciones. Aquí se indica el nombre de la base de datos gestionada.
4
el nombre de un usuario con derechos de acceso a esta base de datos
5
su contraseña

Una vez definida la fuente de datos ODBC, podemos probar nuestro programa:

 

Analicemos el código que, en comparación con la versión gráfica sin base de datos, ha sido modificado. Recordemos el código de la clase impots utilizada hasta ahora:

// creación de una clase «impuestos»

public class impots{

    // los datos necesarios para el cálculo del impuesto
     // proceden de una fuente externa

    private double[] limites, coeffR, coeffN;

     // constructor
    public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
         // se comprueba que las tres tablas tengan el mismo tamaño
        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+")");
        // Todo correcto
        this.limites=LIMITES;
        this.coeffR=COEFFR;
        this.coeffN=COEFFN;
    }//fabricante

     // cálculo del impuesto
    public long calculer(boolean marié, int nbEnfants, int salaire){
         // cálculo del número de participaciones
        double nbParts;
        if (marié) nbParts=(double)nbEnfants/2+2;
        else nbParts=(double)nbEnfants/2+1;
        if (nbEnfants>=3) nbParts+=0.5;
         // cálculo de la base imponible y del coeficiente familiar
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // cálculo del impuesto
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // Devolución del resultado
        return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
    }//calcular
}//categoría

Esta clase crea las tres tablas limites, coeffR y coeffN a partir de tres tablas pasadas como parámetros a su constructor. Se decide añadirle un nuevo constructor que permita crear las mismas tres tablas a partir de una base de datos:

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

    // dsnIMPOTS: nombre DSN de la base de datos
     // userIMPOTS, mdpIMPOTS: nombre de usuario/contraseña de acceso a la base de datos

A modo de ejemplo, decidimos no implementar este nuevo constructor en la clase impots, sino en una clase derivada, impotsJDBC:

//: paquetes importados
import java.sql.*;
import java.util.*;

public class impotsJDBC extends impots{
  // adición de un constructor que permite crear
   // las tablas «limites», «coeffr» y «coeffn» a partir de la tabla
   // impuestos de una base de datos
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: nombre DSN de la base de datos
     // userIMPOTS, mdpIMPOTS: nombre de usuario/contraseña de acceso a la base de datos

     // las tablas de datos
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();

    // conexión a la base de datos
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
    // creación de un objeto Statement
    Statement S=connect.createStatement();
    // consulta SELECT
    String select="select limites, coeffr, coeffn from impots";
    // ejecución de la consulta
    ResultSet RS=S.executeQuery(select);
    while(RS.next()){
      // procesamiento de la línea actual
      aLimites.add(RS.getString("limites"));
      aCoeffR.add(RS.getString("coeffr"));
      aCoeffN.add(RS.getString("coeffn"));
    }// línea siguiente
     // cierre de recursos
    RS.close();
    S.close();
    connect.close();
     // transferencia de datos a tablas delimitadas
    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
}//clase

El constructor lee el contenido de la tabla impots de la base de datos que se le ha pasado como parámetro y rellena las tres tablas limites, coeffR y coeffN. Pueden producirse varios errores. El constructor no los gestiona, sino que los «transmite» al programa que lo invoca:

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

Si nos fijamos en el código anterior, veremos que la clase impotsJDBC utiliza directamente los campos limites, coeffR, coeffN de su clase base impots. Estos están declarados como privados:

    private double[] limites, coeffR, coeffN;

la clase impotsJDBC no tiene acceso directo a estos campos. Por lo tanto, realizamos una primera modificación en la clase base escribiendo:

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

El atributo protected permite a las clases derivadas de la clase impots tener acceso directo a los campos declarados con este atributo. Tenemos que realizar una segunda modificación. El constructor de la clase hija impotsJDBC se declara de la siguiente manera:

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

Sabemos que, antes de crear un objeto de una clase hija, primero hay que crear un objeto de la clase padre. Para ello, el constructor de la clase hija debe llamar explícitamente al constructor de la clase padre mediante una instrucción super(....). Aquí no se ha hecho porque no se ve a qué constructor de la clase padre se podría llamar. Por el momento solo hay uno y no es adecuado. El compilador buscará entonces en la clase padre un constructor sin parámetros al que pueda llamar. No lo encuentra y esto genera un error de compilación. Por lo tanto, añadimos un constructor sin argumentos a nuestra clase impots:

   // constructor vacío
  protected impots(){}

Lo declaramos como «protected» para que solo puedan utilizarlo las clases hijas. El esqueleto de la clase impots queda ahora así:

public class impots{

  // los datos necesarios para el cálculo del impuesto
   // proceden de una fuente externa

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

   // fabricante vacío
  protected impots(){}

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

   // cálculo del impuesto
  public long calculer(boolean marié, int nbEnfants, int salaire){
.............
    }//calcular
}//clase

La acción del menú Initialiser de nuestra aplicación queda así:

  void mnuInitialiser_actionPerformed(ActionEvent e) {
    // se recupera la cadena de conexión
    Pattern séparateur=Pattern.compile("\\s*;\\s*");
    String[] champs=séparateur.split(txtConnexion.getText().trim());
     // se necesitan tres campos
    if(champs.length!=3){
      // error
      txtStatus.setText("Chaîne de connexion (DSN;uid;mdp) incorrecte");
       // volver a la interfaz gráfica
      txtConnexion.requestFocus();
      return;
    }//if
     // se cargan los datos
    try{
       // creación del objeto impotsJDBC
      objImpots=new impotsJDBC(champs[0],champs[1],champs[2]);
       // confirmación
      txtStatus.setText("Données chargées");
       // el salario se puede modificar
      txtSalaire.setEditable(true);
       // No se pueden realizar más cambios
      mnuInitialiser.setEnabled(false);
      txtConnexion.setEditable(false);
    }catch(Exception ex){
       // problema
      txtStatus.setText("Erreur : " + ex.getMessage());
      // fin
      return;
    }//captura
  }

Una vez creado el objeto objImpots, la aplicación es idéntica a la aplicación gráfica ya escrita. Se recomienda al lector que consulte dicha aplicación.

6.4. Ejercicios

6.4.1. Ejercicio 1

Proporciona una interfaz gráfica al programa sql3 anterior.

6.4.2. Ejercicio 2

Un applet de Java solo puede acceder a una base de datos a través del servidor desde el que se ha cargado. De hecho, dado que un applet no tiene acceso al disco del equipo en el que se ejecuta, la base de datos no puede estar en el equipo del cliente que utiliza el applet. Por lo tanto, nos encontramos en la siguiente situación:

Image

El equipo que ejecuta el applet, el servidor y el equipo que alberga la base de datos pueden ser tres equipos diferentes. En este caso, suponemos que la base de datos se encuentra en el servidor.

Problema 1

Escribe en Java la siguiente aplicación de servidor:

  • la aplicación de servidor funciona en un puerto que se le pasa como parámetro
  • cuando un cliente se conecta, la aplicación de servidor envía el mensaje
200 - Bienvenue - Envoyez votre requête
  • A continuación, el cliente envía los parámetros necesarios para conectarse a una base de datos y la consulta que desea ejecutar en el formato:
Pilote Java/URL base/UID/MDP/requête SQL

Los parámetros están separados por una barra. Los cuatro primeros parámetros son los del programa sql3 descrito en este capítulo.

  • A continuación, la aplicación del servidor establece una conexión con la base de datos especificada, que debe encontrarse en el mismo equipo que ella, y ejecuta en ella la consulta SQL. Los resultados se envían al cliente en el siguiente formato:
100 - ligne1
100 - ligne2

...

si se trata del resultado de una consulta Select, o

101 - nbLignes

para devolver el número de filas afectadas por una consulta de actualización. Si se produce un error de conexión a la base de datos o de ejecución de la consulta, la aplicación devuelve

500 - Message d’erreur
  • una vez ejecutada la consulta, la aplicación del servidor cierra la conexión.

Problema 2

Crea un applet de Java que permita consultar el servidor anterior. Puedes inspirarte en la interfaz gráfica del ejercicio 1 anterior. Dado que el puerto del servidor puede variar, este deberá introducirse en la interfaz del applet. Lo mismo ocurrirá con todos los parámetros necesarios para enviar la línea:

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

que el cliente debe enviar al servidor.

6.4.3. Ejercicio 3

El texto siguiente expone un problema destinado inicialmente a ser resuelto en Visual Basic. Adáptalo para su resolución en Java en un applet. Este se basará en el servidor del ejercicio 2. La interfaz gráfica podrá modificarse para tener en cuenta el nuevo contexto de ejecución.

Se propone crear una aplicación que ponga de relieve las diferentes operaciones de actualización posibles de una tabla de una base de datos ACCESS. La base de datos ACCESS se llama articles.mdb. Tiene una única tabla llamada «artículos» que recoge los artículos vendidos por una empresa. Su estructura es la siguiente:

nombre
tipo
code
código del artículo de 4 caracteres
nom
su nombre (cadena de caracteres)
prix
su precio (real)
stock_actu
su stock actual (número entero)
stock_mini
el stock mínimo (en número entero) por debajo del cual hay que reponer el artículo

Nos proponemos visualizar y actualizar esta tabla a partir del siguiente formulario:

Image

Los campos de este formulario son los siguientes:

n.º
tipo
nombre
Función
1
cuadro de texto
ficha
Número de la ficha visualizada
«enabled» es «false»
2
cuadro de texto
código
código del artículo
3
cuadro de texto
nombre
nombre del artículo
4
cuadro de texto
precio
precio del artículo
5
cuadro de texto
actual
stock actual del artículo
6
cuadro de texto
mínimo
stock mínimo del artículo
7
datos
data1
Control de datos asociado a la base de datos
databasename=ruta del archivo artículos.mdb
recordsource=artículos
connect=access
8
HScrollBar
posición
permite navegar por la tabla
9
botón
OK
permite confirmar una actualización; solo aparece cuando se realiza dicha actualización
10
botón
Cancelar
permite cancelar una actualización; solo aparece durante la misma
11
frame
frame1
para que quede bonito
12
cuadro de texto
nombre_base
nombre de la base de datos abierta
«enabled» debe estar en «false»
13
cuadro de texto
sourcename
nombre de la tabla abierta
«enabled» está en «false»

Creación de la hoja en VB

control
Particularidades
data1
Los campos «databasename» y «recordsource» están rellenados. «databasename» debe indicar la base de datos de Access articles.mdb de su directorio y «recordsource», la tabla «artículos».
code
Se trata de un cuadro de texto que queremos vincular al campo «code» del registro actual de «data1». Para ello, rellenamos dos campos:
«datasource»: se introduce «data1» para indicar que el cuadro de texto está vinculado a la tabla asociada a «data1»
«datafield»: se selecciona el campo «código» de la tabla «artículos»
Tras estas operaciones, el cuadro de texto «código» siempre contendrá el campo «código» del registro actual de «data1». A la inversa, al modificar el contenido de este cuadro de texto se modificará el campo «código» del registro actual.
Se hace lo mismo con los demás cuadros de texto
nom
fuente de datos: data1 campo de datos: nombre
prix
fuente de datos: data1 campo de datos: precio
actuel
fuente de datos: data1 campo de datos: stock_actu
minimum
fuente de datos: data1 campo de datos: stock_mini

Los menús

La estructura de los menús es la siguiente

Edición
Examinar
Salir
Añadir
Atrás
 
Modificar
Siguiente
 
Eliminar
Primero
 
 
Último
 

La función de las distintas opciones es la siguiente:

menú
nombre
función
Ajouter
mnuañadir
para añadir un nuevo registro a la tabla de artículos
Supprimer
mnusupprimer
para eliminar de la tabla de artículos el registro que se está visualizando actualmente
Modifier
mnumodificar
para modificar, en la tabla de artículos, el registro que se está visualizando actualmente
Précédent
mnuprecedent
para pasar al registro anterior
Suivant
mnusuivant
para pasar al registro siguiente
Premier
mnupremier
para pasar al primer registro
Dernier
mnudernier
para pasar al último registro
Quitter
mnuquitter
para salir de la aplicación

Cargando la hoja

Durante el evento form_load, se abre la tabla asociada a data1 (data1.refresh). Si la apertura falla, se muestra un mensaje de error y el programa finaliza (end). De lo contrario, se muestra el formulario con el primer registro de la tabla articles visible. No es posible introducir datos en los campos (propiedad «enabled» establecida en «false»). La introducción de datos solo es posible mediante las opciones «Añadir» y «Modificar». Los botones OK y «Cancelar» están ocultos (visible=false).


Parte 1

Nos proponemos crear los procedimientos relacionados con las diferentes opciones del menú, así como con los botones OK y Cancelar. En un primer momento, ignoraremos los siguientes puntos:

  • la habilitación o deshabilitación de ciertas opciones del menú: por ejemplo, la opción «Siguiente» debe deshabilitarse si nos encontramos en el último registro de la tabla
  • la gestión de la barra de desplazamiento horizontal

el menú «Examinar/Siguiente»

  • pasa al siguiente registro (data1.RecordSet.MoveNext) si no se está al final del archivo (data1.RecordSet.EOF). Actualiza el cuadro de texto de la ficha (data1.recordset.absoluteposition/ data1.recordset.recordcount).

Menú «Examinar»/«Anterior»

  • pasa al registro anterior (data1.RecordSet.MovePrevious) si no se está al principio del archivo (data1.RecordSet.BOF). Actualiza el cuadro de texto de la ficha.

Menú «Examinar»/«Primero»

  • pasa al primer registro (data1.RecordSet.MoveFirst) si el archivo no está vacío (data1.recordset.recordcount=0). Actualiza el cuadro de texto de la ficha.

Menú «Examinar»/«Último»

  • pasa al último registro (data1.RecordSet.MoveLast) si el archivo no está vacío (data1.recordset.recordcount=0). Actualiza el cuadro de texto de la ficha.

Menú Edición/Añadir

  • permite añadir un registro a la tabla
  • activa el modo de añadir registro (data1.recordset.addnew)
  • permite introducir datos en los 5 campos: código, nombre, precio, etc. (enabled=true)
  • desactiva los menús Edición, Examinar y Salir (enabled=false)
  • muestra los botones OK y Cancelar (visible=true)

botón OK

  • valida una modificación del registro (data1.recordset.Update)
  • oculta los botones OK y Cancelar (visible=false)
  • habilita las opciones Editar, Examinar y Salir (enabled=true)
  • actualiza el cuadro de texto de la ficha

botón «Cancelar»

  • anula una modificación de registro (data1.recordset.CancelUpdate)
  • oculta los botones OK y Cancelar (visible=false)
  • habilita las opciones Edición, Examinar y Salir (enabled=true)
  • actualiza el cuadro de texto de la ficha

menú Edición/Modificar

  • permite modificar el registro que se muestra en el formulario
  • activa el modo de edición del registro (data1.recordset.edit)
  • permite introducir datos en los 4 campos «nombre», «precio»,... (enabled=true), pero no en el campo «código» (enabled=false)
  • desactiva los menús Edición, Examinar y Salir (enabled=false)
  • muestra los botones OK y Cancelar (visible=true)

menú Edición/Eliminar

  • permite eliminar (data1.recordset.delete) el registro visualizado de la tabla
  • pasa a la ficha siguiente (data1.recordset.movenext)

menú Salir

  • descarga la hoja (unload me)

evento form_unload (cancel as integer)

  • activado por la operación «unload me» o al cerrar el formulario con Alt-F4 o haciendo doble clic en el cuadro del sistema, por lo que no necesariamente mediante la opción «Salir».
  • muestra la pregunta «¿Desea salir realmente de la aplicación?» con dos botones Sí/No (msgbox con style=vbyes+vbno)
  • Si la respuesta es «No» (=vbno), se establece «cancel» en -1 y se sale del procedimiento form_unload. El valor de «cancel» en -1 indica que se rechaza el cierre de la ventana.
  • Si la respuesta es «Sí» (=vbyes), se cierra la base de datos (data1.recordset.close, data1.database.close).

Parte 2: Gestión de los menús

Aquí nos centramos en la habilitación o deshabilitación de los menús. Tras cada operación que modifique la ficha actual, se llamará a un procedimiento al que llamaremos Oueston. Este comprobará las siguientes condiciones:

. si el fichero está vacío,

  • desactivarán los menús «Examinar», «Editar/Eliminar»,
  • autorizaremos los demás

. Si la ficha actual es la primera,

  • se desactivará «Explorar/Anterior»
  • y se permitirán el resto

. Si la ficha actual es la última,

  • se desactivarán las opciones «Examinar» y «Siguiente»
  • se permitirán el resto

Parte 3 - Gestión del variador horizontal

Un variador horizontal tiene tres campos importantes:

  • min: su valor mínimo
  • máx.: su valor máximo
  • value: su valor actual

Inicialización del variador

Al cargar (form_load), el variador se inicializará de la siguiente manera:

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

Cabe señalar que, justo después de abrir la base de datos (data1.refresh), el número de registros de la tabla representada por data1.recordset.recordcount es incorrecto. Hay que ir al final de la tabla (MoveLast) y, a continuación, volver al principio de la tabla (MoveFirst) para que sea correcto.

Acción directa sobre el variador

La posición del cursor del variador representa la posición en la tabla.

Cuando el usuario modifica el variador (denominado «posición» aquí), se activa el evento position_change. En este evento, se modificará el registro actual de la tabla para que refleje el desplazamiento realizado en el variador. Para ello, se utilizará el campo «absoluteposition» de data1.recordset. Al asignar el valor «i» a este campo, la ficha n.º «i» de la tabla se convierte en la ficha actual. Los registros se numeran a partir de 0 y, por lo tanto, tienen un número dentro del intervalo [0,data1.recordset.recordcount-1]. En el procedimiento position_change, basta con escribir

    data1.recordset.absoluteposition=position.value

para que la ficha activa que se visualiza en el formulario refleje el desplazamiento realizado en el variador.

Una vez hecho esto, se llamará al procedimiento Oueston para actualizar los menús.

Actualización del variador

Dado que la posición del cursor del variador debe reflejar la posición en la tabla, es necesario actualizar el valor del variador cada vez que se produzca un cambio de ficha actual en la tabla, provocado por cualquiera de los menús. Como cada uno de ellos llama al procedimiento Oueston, lo mejor es incluir esta actualización también en dicho procedimiento. Basta con escribir aquí:

    position.value=data1.recordset.absoluteposition

Parte 4 - Opción «Buscar»

Se añade la opción «Examinar/Buscar», cuyo objetivo es permitir al usuario visualizar un artículo cuyo código indique.

Cuando se activa esta opción, se producen las siguientes secuencias:

  • se pasa al modo «Añadir ficha» (Addnew), con el único fin de no modificar la ficha actual en la que se encontraba el usuario cuando se activó la opción,
  • se permite la introducción de datos en el campo «Código» y se borra el contenido del campo «Ficha»,
  • se desactivan los menús y se muestran los botones «OK» y «Cancelar»
  • cuando el usuario pulsa OK, debemos buscar el registro correspondiente al código introducido por el usuario. Sin embargo, el procedimiento OK_click ya se utiliza para las opciones «Examinar/Añadir» y «Examinar/Modificar». Para distinguir entre estos casos, debemos gestionar una variable global que llamaremos aquí «estado» y que tendrá tres valores posibles: «añadir», «modificar» y «buscar». Los procedimientos vinculados a los botones OK y «Cancelar» utilizarán esta variable para saber en qué contexto se invocan.
  • Si «estado» es «buscar», en el procedimiento vinculado a OK,
    • se cancela la operación addnew (data1.recordset.cancelupdate), ya que no se tenía la intención de añadir un registro. Cabe señalar que, en ese caso, el registro actual vuelve a ser el que aparecía en pantalla antes de la operación «Examinar/Buscar».
    • Se construye el criterio de búsqueda relacionado con el código y se inicia la búsqueda (data1.recordset.findfirst criterio),
    • si la búsqueda falla (data1.recordset.nomatch=true), se avisa al usuario, se vuelve al modo de añadir (addnew) y se sale del procedimiento OK. El usuario deberá volver a introducir un nuevo código o seleccionar la opción Cancelar.
    • Si la búsqueda tiene éxito, la ficha encontrada se convierte en la nueva ficha activa. Se restablecen los menús, se ocultan los botones OK/Cancelar, se deshabilita la introducción de datos en el campo «código» y se sale del procedimiento.
  • . Si el estado es «buscar», en el procedimiento vinculado a «Cancelar», se
    • se cancela la operación addnew (data1.recordset.cancelupdate). A continuación, se volverá automáticamente a la ficha activa anterior a la operación «Examinar/Buscar».
    • Restablece los menús, oculta los botones OK/Cancelar, deshabilita la introducción de datos en el campo «código» y se sale del procedimiento.

Parte 5 - Gestión del código

Un artículo debe identificarse de forma única mediante su código. Asegúrese de que, en la opción «Examinar/Añadir», se rechace la adición si el registro que se va a añadir tiene un código de artículo que ya existe en la tabla.

6.4.4. Ejercicio 4

Aquí se presenta una aplicación web basada en el servidor del ejercicio 2. Se trata de un prototipo de aplicación de comercio electrónico.

El cliente realiza pedidos de artículos a través de la siguiente interfaz web:

Image

Puede realizar las siguientes operaciones:

  • seleccionar un artículo en la lista desplegable
  • especificar la cantidad deseada
  • confirmar su compra con el botón Acheter
  • su compra aparece en la lista de artículos comprados
  • puede eliminar artículos de esta lista seleccionando uno y pulsando el botón Retirer
  • al pulsar el botón Bilan, obtiene el siguiente resumen:

Image

El resumen permite al usuario consultar los detalles de su factura. El usuario tiene acceso a los detalles de un artículo seleccionado en la lista desplegable mediante el botón Informations:

Image

Una vez que el usuario ha solicitado el resumen de su factura, puede validarla en la página siguiente. Para ello, debe introducir su dirección de correo electrónico y confirmar su pedido pulsando el botón correspondiente.

Image

Antes de guardar su pedido, la aplicación solicita una confirmación:

Image

Una vez confirmado el pedido, la aplicación lo procesa y envía una página de confirmación:

Image

En realidad, la aplicación no procesa el pedido. Se limita a enviar un correo electrónico al usuario pidiéndole que abone el importe de las compras:

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

Pregunta: Crear el equivalente a esta aplicación web con un applet de Java.