6. Administración de bases de datos con API JDBC
6.1. Información general
Existen numerosas bases de datos en el mercado. Con el fin de estandarizar el acceso a las bases de datos en Windows, Microsoft ha desarrollado una interfaz llamada 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, por ejemplo, se muestra una lista de controladores instalados en una máquina 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 el manejo de bases de datos
6.2.1. Introducción
En una aplicación JAVA que utiliza una base de datos con la interfaz JDBC, por lo general se seguirán 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 se realiza al finalizar el procesamiento de la base de datos. Se trata de un esquema relativamente clásico con el que quizá ya esté familiarizado si ha trabajado con una base de datos de manera interactiva. 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 (entero) |
stock_mini | el stock mínimo (entero) por debajo del cual hay que reabastecer el artículo |
Esta base ACCESS se define como fuente de datos «de usuario» en el administrador de bases ODBC:


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

Esta configuración consiste esencialmente en asociar a la base ARTICLES el archivo de Access articles.mdb correspondiente a dicha base. Una vez hecho esto, la base ARTICLES queda disponible 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 primero realizar una fase de conexión. Esto se lleva a cabo con 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 un enlace entre la aplicación y la base de datos, a través del 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 llama odbc. La tercera parte del URL está compuesta por el nombre de la fuente con posibles parámetros. En nuestros ejemplos, se tratará de fuentes ODBC reconocidas por el sistema. Así, el URL de la fuente de datos Articles definida anteriormente será
jdbc:odbc:Artículos
id: Identificación del 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, entonces 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 genere 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){
// procesar la excepción
}
Para que sea posible la conexión a una base de datos, se debe contar con el 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 la máquina, también se debe contar con la clase JAVA, que servirá de 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 para la gestión de clases. Su método estático forName permite cargar dinámicamente una clase y, por lo tanto, aprovechar 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){
// tratar 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 con una contraseña MDP
public class connexion1{
static String syntaxe="pg PILOTE URL UID MDP";
public static void main(String arg[]){
// verificació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 supone que el lector conoce los conceptos básicos 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 contar con 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 manera 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 las palabras clave de la primera línea son obligatorias; las demás son opcionales. Existen otras palabras clave que no se muestran aquí.
- Se realiza una unión con todas las tablas que se encuentran detrás de la palabra clave «from»
- Solo se conservan las columnas que aparecen después de 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 la tabla ARTICLES anterior y queremos 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 ordenado por 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 emitir.
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 enviar 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, llamada fila actual. Al crear inicialmente el ResultSet, la fila actual es la fila n.º 1 si el ResultSet no está vacío. Para pasar a la siguiente fila, la clase ResultSet cuenta con el método next:
Este método intenta pasar a la siguiente línea de ResultSet y devuelve true si tiene éxito, false en caso contrario. Si tiene éxito, la siguiente línea se convierte en la nueva línea actual. La línea anterior se pierde y no se podrá retroceder para recuperarla. La tabla de ResultSet tiene columnas col1, col2,.... Para utilizar los diferentes campos de la línea actual, se dispone de los siguientes métodos:
para obtener el campo «coli» de la fila actual. Type designa el tipo del campo coli. Con bastante frecuencia se utiliza el método getString en todos los campos, lo que permite obtener el contenido del campo como una cadena de caracteres. Luego 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
Statement S=null; // objeto de emisión 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, se utilizará 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 brinda esta información. Es la clase ResultSetMetaData la que nos la proporciona. En términos más generales, esta clase nos brinda información sobre la estructura de la tabla, es decir, sobre la naturaleza de sus columnas.
Para acceder a la información sobre la estructura de un ResultSet, primero hay que instanciar 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 escriba en el teclado.
import java.sql.*;
import java.io.*;
// muestra el contenido de una base de 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
Statement S=null; // objeto de emisión de las consultas
ResultSet RS=null; // tabla de resultados de una consulta
String select; // texto de la consulta SQL select
int nbColonnes; // número de columnas del 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 ingresadas desde 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 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
// siguiente consulta
System.out.print("Requête : ");
select=in.readLine();
}// mientras
} 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 ingresadas desde el teclado ahora son consultas de actualización de la base de datos ARTICLES.
import java.sql.*;
import java.io.*;
// muestra el contenido de una base de 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
Statement S=null; // objeto de emisión 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 filas 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 ingresadas desde 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 vinculados a la base
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 diversas 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
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
Se agrega 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
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
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, cuenta con un método execute capaz de ejecutar cualquier tipo de solicitud SQL:
El resultado devuelto es el valor booleano true si la consulta devolvió un ResultSet (executeQuery), false si devolvió un número (executeUpdate). El valor ResultSet obtenido se puede recuperar con el método getResultSet y el número de filas 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();
// análisis 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 que se escriba en 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 con una contraseña MDP
public class sql3{
static String syntaxe="pg PILOTE URL UID MDP";
public static void main(String arg[]){
// verificació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 a ejecutar
int nbLignes; // número de filas 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 ingresadas desde 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 obtuvo 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));
}// siguiente fila de ResultSet
} else {
// era una solicitud de actualización
nbLignes=S.getUpdateCount();
// seguimiento
System.out.println(nbLignes + " ligne(s) ont été mises à jour");
}//si
} 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 vinculados 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 toma sus entradas del archivo requetes y guarda sus salidas 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 llamada impots. El acceso a la base de datos se controla mediante un nombre de usuario y contraseña, en este caso admimpots/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 ingresada 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 derecho de lectura sobre la base de datos |
| su contraseña |
La base de datos dbimpots se creó manualmente junto con MySQL. Se transforma en la fuente de datos ODBC de la siguiente manera:
- se inicia el administrador de fuentes de datos ODBC de 32 bits

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

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

- El controlador MySQL solicita cierta información:
1 | el nombre DSN que se le dará a la fuente de datos ODBC —puede ser cualquiera— |
2 | la máquina en la que se ejecuta el SGBD MySQL —en este caso, localhost—. Es interesante señalar 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 utilizará. MySQL es un SGBD que administra bases de datos relacionales, las cuales son conjuntos de tablas relacionadas entre sí mediante relaciones. Aquí se indica el nombre de la base de datos administrada. |
4 | el nombre de un usuario con derecho 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 de impuestos
public class impots{
// los datos necesarios para el cálculo del impuesto
// provienen de una fuente externa
private double[] limites, coeffR, coeffN;
// constructor
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
// se verifica 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+")");
// está bien
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 cuotas
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 renta imponible y el 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++;
// 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 que se pasan como parámetros a su constructor. Se decide agregarle 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{
// agregación de un generador 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 fila 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 generador lee el contenido de la tabla impots de la base de datos que se le ha pasado como parámetros y llena las tres tablas limites, coeffR y coeffN. Pueden ocurrir varios errores. El constructor no los maneja, sino que los «transmite» al programa que lo llama:
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
Si observamos 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 que las clases derivadas de la clase impots tengan acceso directo a los campos declarados con este atributo. Necesitamos 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 con una instrucción super(....). Aquí no se ha hecho porque no se ve 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 durante la compilación. Por lo tanto, agregamos 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
// provienen de una fuente externa
protected double[] limites=null;
protected double[] coeffR=null;
protected double[] coeffN=null;
// fabricante en blanco
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");
// regreso a la interfaz visual
txtConnexion.requestFocus();
return;
}//si
// 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);
// ya 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 invita al lector a consultarla.
6.4. Ejercicios
6.4.1. Ejercicio 1
Proporcione 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 cual se cargó. De hecho, dado que un applet no tiene acceso al disco de la máquina en la que se ejecuta, la base de datos no puede estar en la máquina del cliente que utiliza el applet. Por lo tanto, nos encontramos en la siguiente situación:

La máquina que ejecuta el applet, el servidor y la máquina que alberga la base de datos pueden ser tres máquinas diferentes. Aquí 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 opera 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 primeros cuatro parámetros son los del programa sql3 descrito en este capítulo.
- La aplicación del servidor establece entonces una conexión con la base de datos especificada, la cual debe estar en la misma máquina 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 ocurre 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. Como 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 siguiente texto presenta un problema que inicialmente estaba destinado a resolverse en Visual Basic. Adáptalo para resolverlo en Java dentro de 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 muestre las diferentes operaciones de actualización posibles de una tabla de la base de datos ACCESS. La base de datos ACCESS se llama articles.mdb. Cuenta con una única tabla llamada «artículos» que enumera 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 (entero) |
stock_mini | el stock mínimo (entero) por debajo del cual hay que reabastecer el artículo |
Nos proponemos visualizar y actualizar esta tabla a partir del siguiente formulario:

Los controles de este formulario son los siguientes:
N.º | tipo | nombre | Función |
1 | cuadro de texto | ficha | Número de la ficha visualizada «enabled» está en «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 nombre_base_datos=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 durante el proceso |
10 | botón | Cancelar | permite cancelar una actualización; solo aparece durante la misma |
11 | frame | frame1 | para que se vea mejor |
12 | cuadro de texto | nombre de 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» deben estar completados. «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, completamos dos campos: datasource: se ingresa «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, modificar el contenido de este cuadro de texto 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 |
Agregar | Anterior | |
Modificar | Siguiente | |
Eliminar | Primero | |
Último |
La función de las diferentes opciones es la siguiente:
menú | nombre | función |
mnuañadir | para agregar 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 se cierra (end). De lo contrario, se muestra el formulario con el primer registro de la tabla articles visible. No es posible ingresar datos en los campos (la propiedad enabled está en false). Solo se puede ingresar información mediante las opciones Agregar 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 del control deslizante horizontal
el menú «Examinar/Siguiente»
- pasa al registro siguiente (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 inicio del archivo (data1.RecordSet.BOF). Actualiza el cuadro de texto de la ficha.
Menú Navegar/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/Agregar
- permite agregar un registro a la tabla
- activa el modo de Agregar registro (data1.recordset.addnew)
- permite ingresar 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 de registro (data1.recordset.Update)
- oculta los botones OK y Cancelar (visible=false)
- habilita las opciones Editar, Examinar, 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 Editar, Examinar, 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 ingresar datos en los 4 campos: nombre, precio, etc. (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 al siguiente registro (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 al hacer doble clic en el cuadro del sistema, por lo que no necesariamente por la opción «Salir».
- muestra la pregunta «¿De verdad quieres salir 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. Un valor de «cancel» de -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 enfocamos en la habilitación o deshabilitación de los menús. Después de cada operación que modifique el registro actual, se llamará a un procedimiento al que llamaremos Oueston. Este verificará las siguientes condiciones:
. si el archivo está vacío,
- desactivaremos los menús «Examinar», «Editar/Eliminar»,
- autorizaremos los demás
. Si el registro actual es el primero,
- se desactivará «Examinar/Anterior»
- y se permitirán el resto
. Si la ficha actual es la última,
- se desactivarán las opciones «Navegar» y «Siguiente»
- se permitirán las demás
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, inmediatamente 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. Es necesario ir al final de la tabla (MoveLast) y luego regresar al inicio de la tabla (MoveFirst) para que sea correcto.
Acción directa en 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 movimiento realizado en el variador. Para ello, se utilizará el campo absoluteposition de data1.recordset. Al asignar el valor i a este campo, el registro n.º i de la tabla se convierte en el registro 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, solo tenemos que escribir
para que el registro activo que se visualiza en el formulario refleje el cambio 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 haya un cambio de registro 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 agrega 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, ocurren las siguientes secuencias:
- se pasa al modo de Agregar registro (Addnew), con el único fin de no modificar el registro actual en el que se encontraba al activar la opción,
- se permite la entrada 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 presiona OK, debemos buscar el registro correspondiente al código ingresado por el usuario. Sin embargo, el procedimiento OK_click ya se utiliza para las opciones «Examinar/Agregar» y «Examinar/Modificar». Para distinguir entre estos casos, debemos manejar una variable global que llamaremos aquí «estado», la cual tendrá tres valores posibles: «agregar», «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) porque no se tenía la intención de agregar un registro. Cabe señalar que, en ese caso, el registro actual vuelve a ser el que se mostraba en la 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 le notifica al usuario, luego se vuelve al modo de agregar (addnew) y se sale del procedimiento OK. El usuario deberá volver a ingresar un nuevo código o seleccionar la opción Cancelar.
- Si la búsqueda tiene éxito, el registro encontrado se convierte en el nuevo registro activo. Se restablecen los menús, se ocultan los botones OK/Cancelar, se deshabilita la entrada en el campo de 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 regresará automáticamente a la ficha activa anterior a la operación «Examinar/Buscar».
- Restablece los menús, oculta los botones OK/Cancelar, deshabilita la entrada en el campo de código y se sale del procedimiento.
Un artículo debe identificarse de manera única mediante su código. Asegúrese de que, en la opción «Examinar/Agregar», se rechace el agregado si el registro que se va a agregar 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 de la lista desplegable
- especifica la cantidad deseada
- confirma 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 un artículo y utilizando el botón Retirer
- al presionar el botón Bilan, obtiene el resumen siguiente:

El resumen le permite al usuario conocer 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 ingresar su dirección de correo electrónico y confirmar su pedido con 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. Simplemente envía un correo electrónico al usuario pidiéndole que pague 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: Crea el equivalente a esta aplicación web con un applet de Java.

