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:

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

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:

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:
- Conexión a la base de datos
- Envío de consultas SQL a la base de datos
- Recepción y procesamiento de los resultados de estas consultas
- 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:


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

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:
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:
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á:
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:
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:
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í.
- Se realiza una unión con todas las tablas que aparecen después de la palabra clave «from»
- Solo se conservan las columnas que aparecen tras la palabra clave «select»
- Solo se conservan las filas que cumplen la condición de la palabra clave «where»
- 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:
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:
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:
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
, 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:
Cabe destacar dos métodos útiles en la clase ResultSetMetaData:
- int getColumnCount(), que devuelve el número de columnas del ResultSet
- 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:
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:
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:
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 été mises à jour
Nouvelle Requête : (requete 3 du fichier des requetes : select nom,stock_mini from articles)
Résultats obtenus
-----------------
vélo,8
arc,8
canoé,7
fusil,8
skis nautiques,8
essai3,9
cachalot,6
léopard,7
panthère,7
Nouvelle Requête : (requete 4 du fichier des requetes : insert into articles (code,nom,prix,stock_actu,stock_mini) values ('x400','nouveau',100,20,10))
1 ligne(s) ont été mises à jour
Nouvelle Requête : (requete 5 du fichier des requetes : select * from articles)
Résultats obtenus
-----------------
a300,vélo,1202,31,8
d600,arc,5000,10,8
d800,canoé,1502,13,7
x123,fusil,3000,10,8
s345,skis nautiques,1800,3,8
f450,essai3,3,3,9
f807,cachalot,200000,0,6
z400,léopard,500000,1,7
g457,panthère,800000,1,7
x400,nouveau,100,20,10
Nouvelle Requête : (requete 6 du fichier des requêtes : delete from articles where code='x400')
1 ligne(s) ont été mises à jour
Nouvelle Requête : (requete 7 du fichier des requêtes : select * from articles)
Résultats obtenus
-----------------
a300,vélo,1202,31,8
d600,arc,5000,10,8
d800,canoé,1502,13,7
x123,fusil,3000,10,8
s345,skis nautiques,1800,3,8
f450,essai3,3,3,9
f807,cachalot,200000,0,6
z400,léopard,500000,1,7
g457,panthère,800000,1,7
6.3. IMPOTS 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:

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
el nombre DSN de la fuente de datos ODBC | |
| la identidad de un usuario con derechos de lectura sobre la base de datos |
| 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

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

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

- 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:
la clase impotsJDBC no tiene acceso directo a estos campos. Por lo tanto, realizamos una primera modificación en la clase base escribiendo:
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:
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:

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
- 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:
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:
...
si se trata del resultado de una consulta Select, o
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
- 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:
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:

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» |
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 |
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 |
mnuañadir | para añadir un nuevo registro a la tabla de artículos | |
mnusupprimer | para eliminar de la tabla de artículos el registro que se está visualizando actualmente | |
mnumodificar | para modificar, en la tabla de artículos, el registro que se está visualizando actualmente | |
mnuprecedent | para pasar al registro anterior | |
mnusuivant | para pasar al registro siguiente | |
mnupremier | para pasar al primer registro | |
mnudernier | para pasar al último registro | |
mnuquitter | para salir de la aplicación |
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).
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).
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
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
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í:
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.
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:

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:

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:

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.

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

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

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.

