6. Gestão de bases de dados com o API JDBC
6.1. Informações gerais
Existem inúmeras bases de dados no mercado. Para uniformizar o acesso às bases de dados no Windows, a Microsoft desenvolveu uma interface denominada ODBC (Open DataBase Connectivity). Esta camada oculta as particularidades de cada base de dados por trás de uma interface padrão. No Windows, existem inúmeros controladores que facilitam o acesso às bases de dados. Aqui está, por exemplo, uma lista de controladores instalados num computador com o Win95:

Uma aplicação que utilize estes controladores pode aceder a qualquer uma das bases de dados acima referidas sem necessidade de reescrever o código.

Para que as aplicações Java também possam tirar partido da interface ODBC, a Sun criou a interface JDBC (Java DataBase Connectivity), que se intercalará entre a aplicação Java e a interface ODBC:

6.2. Etapas importantes na exploração de bases de dados
6.2.1. Introdução
Numa aplicação JAVA que utilize uma base de dados com a interface JDBC, encontram-se geralmente as seguintes etapas:
- Ligação à base de dados
- Envio de consultas SQL à base de dados
- Recepção e processamento dos resultados dessas consultas
- Encerramento da ligação
As etapas 2 e 3 são realizadas repetidamente, sendo que o encerramento da ligação só ocorre no final da exploração da base de dados. Trata-se de um esquema relativamente clássico, com o qual talvez esteja habituado se já tiver explorado uma base de dados de forma interativa. Vamos detalhar cada uma destas etapas através de um exemplo. Consideramos uma base de dados ACCESS denominada ARTICLES e com a seguinte estrutura:
nome | tipo |
code | código do artigo com 4 caracteres |
nom | o seu nome (cadeia de caracteres) |
prix | o seu preço (real) |
stock_actu | o seu stock atual (número inteiro) |
stock_mini | o stock mínimo (número inteiro) abaixo do qual é necessário reabastecer o artigo |
Esta base de dados ACCESS está definida como fonte de dados «utilizador» no gestor de bases de dados ODBC:


As suas características são especificadas através do botão Configurer da seguinte forma:

Esta configuração consiste essencialmente em associar à base de dados ARTICLES o ficheiro Access articles.mdb correspondente a essa base de dados. Feito isto, a base de dados ARTICLES fica acessível às aplicações que utilizam a interface ODBC.
6.2.2. A etapa de ligação
Para utilizar uma base de dados, uma aplicação Java deve, em primeiro lugar, efetuar uma fase de ligação. Esta é realizada através do seguinte método de classe:
com
DriverManager: classe Java que contém a lista de controladores disponíveis para a aplicação
Connection: classe Java que estabelece uma ligação entre a aplicação e a base de dados, através da qual a aplicação irá enviar consultas SQL à base de dados e receber resultados
URL: nome que identifica a base de dados. Este nome é análogo aos URL da Internet. É por isso que faz parte da classe URL. No entanto, a Internet não intervém de forma alguma aqui. O URL tem o seguinte formato:
jdbc:nom_du_pilote:nom_de_la_source;param=val1;param2=val2
Nos nossos exemplos, em que utilizaremos exclusivamente controladores ODBC, o controlador chama-se odbc. A terceira parte do URL é constituída pelo nome da fonte, com eventuais parâmetros. Nos nossos exemplos, tratar-se-á de fontes ODBC conhecidas pelo sistema. Assim, o URL da fonte de dados Articles definida anteriormente será
jdbc:odbc:Artigos
id: Identificação do utilizador (login)
mdp: palavra-passe do utilizador (password)
Em resumo, o programa liga-se a uma base de dados:
- identificada por um nome (URL)
- sob a identidade de um utilizador (id, palavra-passe)
Se estes três parâmetros estiverem corretos e se existir o controlador capaz de assegurar a ligação da aplicação Java à base de dados especificada, então é estabelecida uma ligação entre a aplicação Java e a base de dados. Esta ligação é materializada para o programa pelo objeto do tipo Connection devolvido pela classe DriverManager. Como esta ligação pode falhar por diversas razões, é suscetível de lançar uma exceção. Por isso, escrever-se-á:
Connection connexion=null;
URL base=...;
String id=...;
String mdp=...;
try{
connexion=DriverManager.getConnection(base,id,mdp);
} catch (Exception e){
// tratar a exceção
}
Para que a ligação a uma base de dados seja possível, é necessário dispor do controlador adequado. Nos nossos exemplos, trata-se do controlador ODBC, capaz de gerir a base de dados solicitada. Para que este controlador esteja disponível na lista de controladores ODBC presentes no computador, é também necessário dispor da classe JAVA, que servirá de interface com o mesmo. Para tal, a aplicação irá solicitar a classe de que necessita da seguinte forma:
A classe Class não está de forma alguma ligada à interface JDBC. Trata-se de uma classe geral de gestão de classes. O seu método estático forName permite carregar dinamicamente uma classe e, assim, beneficiar dos seus atributos e métodos estáticos. A classe que faz a interface com os controladores ODBC do Windows MS chama-se «sun.jdbc.odbc.JdbcOdbcDriver». Assim, escrever-se-á (o método pode gerar uma exceção):
try{
Class.forName(« sun.jdbc.odbc.JdbcOdbcDriver ») ;
} catch (Exception e){
// tratar a exceção (classe inexistente)
}
As classes necessárias para a interface JDBC encontram-se no pacote java.sql. Por isso, no início do programa, escrever-se-á:
Eis um programa que permite a ligação a uma base de dados:
import java.sql.*;
import java.io.*;
// chamada: pg PILOTE URL UID MDP
// liga-se à base de dados URL através da classe JDBC PILOTE
// o utilizador UID é identificado por uma palavra-passe MDP
public class connexion1{
static String syntaxe="pg PILOTE URL UID MDP";
public static void main(String arg[]){
// verificação do número de argumentos
if(arg.length<2 || arg.length>4)
erreur(syntaxe,1);
// ligação à base de dados
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);
}
// encerramento da base de dados
try{
connect.close();
System.out.println("Base " + arg[1] + " fermée");
} catch (Exception e){}
}// main
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// classe
Eis um exemplo de execução:
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. Envio de consultas à base de dados
A interface JDBC permite o envio de consultas SQL para a base de dados ligada à aplicação Java, bem como o processamento dos resultados dessas consultas. A linguagem SQL (Structured Query Language) é uma linguagem de consultas padronizada para bases de dados relacionais. Distinguem-se vários tipos de consultas:
- as consultas de interrogação da base de dados (SELECT)
- as consultas de atualização da base de dados (INSERT, DELETE, UPDATE)
- as consultas de criação/eliminação de tabelas (CREATE, DELETE)
Partimos do princípio de que o leitor conhece os conceitos básicos da linguagem SQL.
6.2.3.1. A classe Statement
Para enviar uma consulta SQL, seja ela qual for, para uma base de dados, a aplicação Java deve dispor de um objeto do tipo Statement. Este objeto armazenará, entre outras coisas, o texto da consulta. Este objeto está necessariamente ligado à ligação em curso. É, portanto, um método da ligação estabelecida que permite criar os objetos Statement necessários para a emissão das consultas SQL. Se connexion é o objeto que simboliza a ligação à base de dados, um objeto Statement é obtido da seguinte forma:
Uma vez obtido um objeto Statement, é possível emitir consultas SQL. Isto será feito de forma diferente, dependendo se a consulta é uma consulta de interrogação ou de atualização da base de dados.
6.2.3.2. Enviar uma consulta à base de dados
Uma consulta é, normalmente, uma consulta do tipo:
Apenas as palavras-chave da primeira linha são obrigatórias; as restantes são opcionais. Existem outras palavras-chave que não são apresentadas aqui.
- É efetuada uma junção com todas as tabelas que se encontram a seguir à palavra-chave «from»
- Apenas as colunas que se encontram a seguir à palavra-chave «select» são mantidas
- Apenas as linhas que satisfazem a condição da palavra-chave «where» são mantidas
- As linhas resultantes, ordenadas de acordo com a expressão da palavra-chave «order by», constituem o resultado da consulta.
O resultado de um select é uma tabela. Se considerarmos a tabela ARTICLES anterior e quisermos os nomes dos artigos cujo stock atual está abaixo do limiar mínimo, escreveremos: select nome from artigos where stock_actu<stock_mini. Se quisermos ordená-los por ordem alfabética dos nomes, escreveremos: select nome from artigos where stock_actu<stock_mini order by nome
Para executar este tipo de consulta, a classe Statement disponibiliza o método executeQuery:
onde requête é o texto da consulta SELECT a ser emitida.
Assim, se
- connexion for o objeto que simboliza a ligação à base de dados
- Statement s=connexion.createStatement() cria o objeto Statement necessário para a emissão das consultas SQL
- ResultSet rs=s.executeQuery(« select nome from artigos where stock_actu<stock_mini») executa uma consulta select e atribui as linhas de resultados da consulta a um objeto do tipo ResultSet.
6.2.3.3. A classe ResultSet: resultado de uma consulta SELECT
Um objeto do tipo ResultSet representa uma tabela, ou seja, um conjunto de linhas e colunas. Num determinado momento, só se tem acesso a uma linha da tabela, denominada linha atual. Aquando da criação inicial do ResultSet, a linha atual é a linha n.º 1, se o ResultSet não estiver vazio. Para passar para a linha seguinte, a classe ResultSet dispõe do método next:
Este método tenta avançar para a linha seguinte do ResultSet e devolve true se for bem-sucedido, false caso contrário. Se for bem-sucedido, a linha seguinte torna-se a nova linha atual. A linha anterior é perdida e não será possível voltar atrás para a recuperar. A tabela do ResultSet tem as colunas col1, col2,.... Para explorar os diferentes campos da linha atual, dispõe-se dos seguintes métodos:
para obter o campo «coli» da linha atual. Type designa o tipo do campo coli. É bastante comum utilizar o método getString em todos os campos, o que permite obter o conteúdo do campo como uma cadeia de caracteres. Em seguida, efetua-se a conversão, se necessário. Se não se souber o nome da coluna, podem ser utilizados os métodos
onde i é o índice da coluna pretendida (i>=1).
6.2.3.4. Um primeiro exemplo
Eis um programa que apresenta o conteúdo da base de dados ARTICLES criada anteriormente:
import java.sql.*;
import java.io.*;
// exibe o conteúdo de uma base de dados do sistema ARTICLES
public class articles1{
static final String DB="ARTICLES"; // base de dados a explorar
public static void main(String arg[]){
Connection connect=null; // ligação à base de dados
Statement S=null; // objeto de emissão das consultas
ResultSet RS=null; // tabela de resultados de uma consulta
try{
// ligação à base de dados
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
System.out.println("Connexion avec la base " + DB + " établie");
// criação de um objeto Statement
S=connect.createStatement();
// execução de uma consulta SELECT
RS=S.executeQuery("select * from " + DB);
// análise da tabela de resultados
while(RS.next()){ // enquanto houver uma linha a analisar
// esta é apresentada no ecrã
System.out.println(RS.getString("code")+","+
RS.getString("nom")+","+
RS.getString("prix")+","+
RS.getString("stock_actu")+","+
RS.getString("stock_mini"));
}// linha seguinte
} catch (Exception e){
erreur("Erreur " + e,2);
}
// fecho da base de dados
try{
connect.close();
System.out.println("Base " + DB + " fermée");
} catch (Exception e){}
}// mão
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// classe
Os resultados obtidos são os seguintes:
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. A classe ResultSetMetadata
No exemplo anterior, conhecemos os nomes das colunas do ResultSet. Se não os conhecermos, não podemos utilizar o método getType(nom_colonne). Em vez disso, utilizar-se-á getType(n.º da coluna). No entanto, para obter todas as colunas, seria necessário saber quantas colunas tem o ResultSet obtido. A classe ResultSet não nos fornece essa informação. É a classe ResultSetMetaData que nos fornece essa informação. De um modo mais geral, esta classe fornece-nos informações sobre a estrutura da tabela, ou seja, sobre a natureza das suas colunas.
É possível aceder às informações sobre a estrutura de um ResultSet instanciando, em primeiro lugar, um objeto ResultSetMetaData. Se RS for um ResultSet, o ResultSetMetaData associado é obtido através de:
De salientar dois métodos úteis na classe ResultSetMetaData:
- int getColumnCount(), que devolve o número de colunas do ResultSet
- String getColumnLabel(int i), que devolve o nome da coluna i do ResultSet (i >= 1)
6.2.3.6. Um segundo exemplo
O programa anterior apresentava o conteúdo da base de dados ARTICLES. Escrevemos aqui um programa que executa, na base de dados ARTICLES, qualquer consulta SQL Select que o utilizador digite no teclado.
import java.sql.*;
import java.io.*;
// exibe o conteúdo de uma base de dados do sistema ARTICLES
public class sql1{
static final String DB="ARTICLES"; // base de dados a explorar
public static void main(String arg[]){
Connection connect=null; // ligação à base de dados
Statement S=null; // objeto de emissão das consultas
ResultSet RS=null; // tabela de resultados de uma consulta
String select; // texto da consulta SQL select
int nbColonnes; // número de colunas do ResultSet
// criação de um fluxo de entrada do 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{
// ligação à base de dados
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
System.out.println("Connexion avec la base " + DB + " établie");
// criação de um objeto Statement
S=connect.createStatement();
// ciclo de execução das consultas SQL introduzidas pelo teclado
System.out.print("Requête : ");
select=in.readLine();
while(!select.equals("fin")){
// execução da consulta
RS=S.executeQuery(select);
// número de colunas
nbColonnes=RS.getMetaData().getColumnCount();
// análise da tabela de resultados
System.out.println("Résultats obtenus\n\n");
while(RS.next()){ // enquanto houver uma linha a processar
// é apresentada no ecrã
for(int i=1;i<nbColonnes;i++)
System.out.print(RS.getString(i)+",");
System.out.println(RS.getString(nbColonnes));
}// linha seguinte
// próxima consulta
System.out.print("Requête : ");
select=in.readLine();
}// enquanto
} catch (Exception e){
erreur("Erreur " + e,2);
}
// encerramento da base de dados e do fluxo 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);
}
}// classe
Eis alguns dos resultados obtidos:
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 um pedido de atualização da base de dados
Um objeto do tipo Statement permite armazenar as solicitações SQL. O método que este objeto utiliza para emitir solicitações SQL de atualização (INSERT, UPDATE, DELETE) já não é o método executeQuery analisado anteriormente, mas sim o método executeUpdate:
A diferença está no resultado: enquanto o executeQuery devolvia a tabela de resultados (ResultSet), o executeUpdate devolve o número de linhas afetadas pela operação de atualização.
6.2.3.8. Um terceiro exemplo
Retomamos o programa anterior e modificamo-lo ligeiramente: as consultas introduzidas pelo teclado são agora consultas de atualização da base de dados ARTICLES.
import java.sql.*;
import java.io.*;
// exibe o conteúdo de uma base de dados do sistema ARTICLES
public class sql2{
static final String DB="ARTICLES"; // base de dados a explorar
public static void main(String arg[]){
Connection connect=null; // ligação à base de dados
Statement S=null; // objeto de emissão das consultas
ResultSet RS=null; // tabela de resultados de uma consulta
String sqlUpdate; // texto da consulta SQL de atualização
int nbLignes; // número de linhas afetadas por uma atualização
// criação de um fluxo de entrada do 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{
// ligação à base de dados
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
System.out.println("Connexion avec la base " + DB + " établie");
// criação de um objeto Statement
S=connect.createStatement();
// Ciclo de execução das consultas SQL introduzidas pelo teclado
System.out.print("Requête : ");
sqlUpdate=in.readLine();
while(!sqlUpdate.equals("fin")){
// execução da consulta
nbLignes=S.executeUpdate(sqlUpdate);
// acompanhamento
System.out.println(nbLignes + " ligne(s) ont été mises à jour");
// próxima consulta
System.out.print("Requête : ");
sqlUpdate=in.readLine();
}// while
} catch (Exception e){
erreur("Erreur " + e,2);
}
// encerramento da base de dados e do fluxo de entrada
try{
// libertação dos recursos associados à base de dados
RS.close();
S.close();
connect.close();
System.out.println("Base " + DB + " fermée");
// encerramento do fluxo do teclado
in.close();
} catch (Exception e){}
}// mão
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// classe
Eis o resultado de várias execuções dos programas sql1 e sql2:
Lista de linhas da base de dados 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
Alteramos algumas linhas:
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
Verificação:
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
Adiciona-se uma linha:
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
Verificação:
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
Elimina-se uma linha:
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
Verificação:
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. Emitir uma consulta SQL qualquer
O objeto Statement, necessário para a emissão de requisições SQL, dispõe de um método execute capaz de executar qualquer tipo de requisição SQL:
O resultado devolvido é o valor booleano true se a consulta tiver devolvido um ResultSet (executeQuery), false se tiver devolvido um número (executeUpdate). O valor ResultSet obtido pode ser recuperado através do método getResultSet e o número de linhas atualizadas, através do método getUpdateCount. Assim, escrever-se-á:
Statement S=...;
ResultSet RS=...;
int nbLignes;
String requête=...;
// execução de uma consulta SQL
if (S.execute(requête)){
// temos um conjunto de resultados
RS=S.getResultSet();
// análise do ResultSet
...
} else {
// era uma consulta de atualização
nbLignes=S.getUpdateCount();
...
}
6.2.3.10. Quarto exemplo
Retomamos a lógica dos programas sql1 e sql2 num programa sql3, agora capaz de executar qualquer consulta SQL introduzida através do teclado. Para tornar o programa mais genérico, as características da base de dados a utilizar são passadas como parâmetros ao programa.
import java.sql.*;
import java.io.*;
// chamada: pg PILOTE URL UID MDP
// liga-se à base de dados URL através da classe JDBC PILOTE
// o utilizador UID é identificado por uma palavra-passe MDP
public class sql3{
static String syntaxe="pg PILOTE URL UID MDP";
public static void main(String arg[]){
// verificação do número de argumentos
if(arg.length<2 || arg.length>4)
erreur(syntaxe,1);
// inicialização dos parâmetros de ligação
Connection connect=null;
String uid="";
String mdp="";
if(arg.length>=3) uid=arg[2];
if(arg.length==4) mdp=arg[3];
// outros dados
Statement S=null; // objeto de emissão das consultas
ResultSet RS=null; // tabela de resultados de uma consulta
String sqlText; // texto da consulta SQL a executar
int nbLignes; // número de linhas afetadas por uma atualização
int nbColonnes; // número de colunas de um ResultSet
// criação de um fluxo de entrada do 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{
// ligação à base de dados
Class.forName(arg[0]);
connect=DriverManager.getConnection(arg[1],uid,mdp);
System.out.println("Connexion avec la base " + arg[1] + " établie");
// criação de um objeto Statement
S=connect.createStatement();
// ciclo de execução das consultas SQL introduzidas pelo teclado
System.out.print("Requête : ");
sqlText=in.readLine();
while(!sqlText.equals("fin")){
// execução da consulta
try{
if(S.execute(sqlText)){
// obtivemos um ResultSet — processamo-lo
RS=S.getResultSet();
// número de colunas
nbColonnes=RS.getMetaData().getColumnCount();
// análise da tabela de resultados
System.out.println("\nRésultats obtenus\n-----------------\n");
while(RS.next()){ // enquanto houver uma linha a analisar
// é apresentada no ecrã
for(int i=1;i<nbColonnes;i++)
System.out.print(RS.getString(i)+",");
System.out.println(RS.getString(nbColonnes));
}// linha seguinte do ResultSet
} else {
// era um pedido de atualização
nbLignes=S.getUpdateCount();
// acompanhamento
System.out.println(nbLignes + " ligne(s) ont été mises à jour");
}//if
} catch (Exception e){
System.out.println("Erreur " +e);
}
// pedido seguinte
System.out.print("\nNouvelle Requête : ");
sqlText=in.readLine();
}// while
} catch (Exception e){
erreur("Erreur " + e,2);
}
// encerramento da base de dados e do fluxo de entrada
try{
// libertação dos recursos associados à base de dados
RS.close();
S.close();
connect.close();
System.out.println("Base " + arg[1] + " fermée");
// encerramento do fluxo do teclado
in.close();
} catch (Exception e){}
}// main
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// classe
Cria-se o seguinte ficheiro 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
O programa é executado da seguinte forma:
O programa lê, portanto, os seus dados de entrada do ficheiro requetes e grava os seus resultados no ficheiro results. Os resultados obtidos são os seguintes:
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 com uma base de dados
Da última vez que abordámos o problema do cálculo de impostos, foi com uma interface gráfica e os dados estavam armazenados num ficheiro. Retomamos esta versão, partindo agora do princípio de que os dados se encontram numa base de dados ODBC-MySQL. O MySQL é um SGBD de domínio público, utilizável em diferentes plataformas, incluindo Windows e Linux. Com este SGBD, foi criada uma base de dados dbimpots contendo uma única tabela denominada impots. O acesso à base de dados é controlado por um nome de utilizador/palavra-passe, neste caso admimpots/mdpimpots. A captura de ecrã mostra como utilizar a base de dados dbimpots com o 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
A interface gráfica da aplicação é a seguinte:

A interface gráfica sofreu algumas alterações:
n.º | tipo | nome | função |
1 | JTextField | txtConnexion | cadeia de ligação à base de dados ODBC |
2 | JScrollPane | JScrollPane1 | recipiente para a área de texto 3 |
3 | JTextArea | txtStatus | exibe mensagens de estado, nomeadamente mensagens de erro |
A cadeia de ligação introduzida em (1) tem o seguinte formato: DSN;login;palavra-passe, com
o nome DSN da fonte de dados ODBC | |
| identidade de um utilizador com direitos de leitura na base de dados |
| a sua palavra-passe |
A base de dados dbimpots foi criada manualmente com MySQL. Transforma-se na fonte de dados ODBC da seguinte forma:
- inicia-se o gestor de fontes de dados ODBC de 32 bits

- utiliza-se o botão [Add] para adicionar uma nova fonte de dados ODBC

- seleciona-se o controlador MySQL e executa-se [Terminer]

- O controlador MySQL solicita uma série de informações:
1 | o nome DSN a atribuir à fonte de dados ODBC — pode ser qualquer um |
2 | a máquina na qual o SGBD MySQL está a ser executado — neste caso, localhost. É importante referir que a base de dados pode ser uma base de dados remota. As aplicações locais que utilizam a fonte de dados ODBC não se aperceberiam disso. Seria esse o caso, nomeadamente, da nossa aplicação Java. |
3 | a base de dados MySQL a utilizar. MySQL é um SGBD que gere bases de dados relacionais, que são conjuntos de tabelas interligadas por relações. Aqui, indica-se o nome da base de dados gerida. |
4 | o nome de um utilizador com direitos de acesso a esta base de dados |
5 | a sua palavra-passe |
Depois de definida a fonte de dados ODBC, podemos testar o nosso programa:
![]() | ![]() |
Vamos analisar o código que, em comparação com a versão gráfica sem base de dados, foi alterado. Recorde-se o código da classe impots utilizada até agora:
// criação de uma classe «impostos»
public class impots{
// os dados necessários para o cálculo do imposto
// provêm de uma fonte externa
private double[] limites, coeffR, coeffN;
// construtor
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
// verifica-se se as três tabelas têm o mesmo tamanho
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á tudo bem
this.limites=LIMITES;
this.coeffR=COEFFR;
this.coeffN=COEFFN;
}//fabricante
// cálculo do imposto
public long calculer(boolean marié, int nbEnfants, int salaire){
// cálculo do número de quotas
double nbParts;
if (marié) nbParts=(double)nbEnfants/2+2;
else nbParts=(double)nbEnfants/2+1;
if (nbEnfants>=3) nbParts+=0.5;
// cálculo do rendimento tributável e do quociente familiar
double revenu=0.72*salaire;
double QF=revenu/nbParts;
// cálculo do imposto
limites[limites.length-1]=QF+1;
int i=0;
while(QF>limites[i]) i++;
// retorno do resultado
return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
}//calcular
}//classe
Esta classe cria as três tabelas limites, coeffR e coeffN a partir de três tabelas passadas como parâmetros ao seu construtor. Decide-se adicionar-lhe um novo construtor que permita criar as mesmas três tabelas a partir de uma base de dados:
public impots(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: nome DSN da base de dados
// userIMPOTS, mdpIMPOTS: nome de utilizador/palavra-passe de acesso à base de dados
Para este exemplo, decidimos não implementar este novo construtor na classe impots, mas sim numa classe derivada, a impotsJDBC:
// pacotes importados
import java.sql.*;
import java.util.*;
public class impotsJDBC extends impots{
// adição de um construtor que permite construir
// as tabelas «limites», «coeffr» e «coeffn» a partir da tabela
// impostos de uma base de dados
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: nome DSN da base de dados
// userIMPOTS, mdpIMPOTS: nome de utilizador/palavra-passe de acesso à base de dados
// as tabelas de dados
ArrayList aLimites=new ArrayList();
ArrayList aCoeffR=new ArrayList();
ArrayList aCoeffN=new ArrayList();
// ligação à base de dados
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
// criação de um objeto Statement
Statement S=connect.createStatement();
// consulta SELECT
String select="select limites, coeffr, coeffn from impots";
// execução da consulta
ResultSet RS=S.executeQuery(select);
while(RS.next()){
// análise da linha atual
aLimites.add(RS.getString("limites"));
aCoeffR.add(RS.getString("coeffr"));
aCoeffN.add(RS.getString("coeffn"));
}// linha seguinte
// encerramento de recursos
RS.close();
S.close();
connect.close();
// transferência de dados para tabelas 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
}//construtor
}//classe
O construtor lê o conteúdo da tabela impots da base de dados que lhe foi passada como parâmetros e preenche as três tabelas limites, coeffR e coeffN. Podem ocorrer alguns erros. O construtor não os trata, mas «encaminha-os» para o programa chamador:
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
Se prestarmos atenção ao código anterior, veremos que a classe impotsJDBC utiliza diretamente os campos limites, coeffR, coeffN da sua classe base impots. Estes estão declarados como privados:
a classe impotsJDBC não tem acesso direto a esses campos. Por isso, fazemos uma primeira alteração na classe base, escrevendo:
O atributo protected permite que as classes derivadas da classe impots tenham acesso direto aos campos declarados com este atributo. É necessário efetuar uma segunda alteração. O construtor da classe filha impotsJDBC é declarado da seguinte forma:
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
Sabemos que, antes de criar um objeto de uma classe filha, é necessário criar primeiro um objeto da classe pai. Para tal, o construtor da classe filha deve chamar explicitamente o construtor da classe pai com uma instrução super(....). Aqui, isso não foi feito, pois não se identifica qual o construtor da classe pai que se poderia chamar. De momento, existe apenas um e este não é adequado. O compilador procura então na classe pai um construtor sem parâmetros que possa chamar. Não encontra nenhum, o que gera um erro na compilação. Por isso, adicionamos um construtor sem argumentos à nossa classe impots:
Declaramo-lo como «protected» para que só possa ser utilizado por classes filhas. A estrutura da classe impots é agora a seguinte:
public class impots{
// os dados necessários para o cálculo do imposto
// provêm de uma fonte externa
protected double[] limites=null;
protected double[] coeffR=null;
protected double[] coeffN=null;
// fabricante em branco
protected impots(){}
// fabricante
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
...........
}//fabricante
// cálculo do imposto
public long calculer(boolean marié, int nbEnfants, int salaire){
.............
}//calcular
}//classe
A ação do menu Initialiser da nossa aplicação passa a ser a seguinte:
void mnuInitialiser_actionPerformed(ActionEvent e) {
// recuperamos a cadeia de ligação
Pattern séparateur=Pattern.compile("\\s*;\\s*");
String[] champs=séparateur.split(txtConnexion.getText().trim());
// são necessários três campos
if(champs.length!=3){
// erro
txtStatus.setText("Chaîne de connexion (DSN;uid;mdp) incorrecte");
// regresso à interface visual
txtConnexion.requestFocus();
return;
}//if
// carregam-se os dados
try{
// criação do objeto impotsJDBC
objImpots=new impotsJDBC(champs[0],champs[1],champs[2]);
// confirmação
txtStatus.setText("Données chargées");
// o salário pode ser alterado
txtSalaire.setEditable(true);
// não é possível efetuar mais alterações
mnuInitialiser.setEnabled(false);
txtConnexion.setEditable(false);
}catch(Exception ex){
// problema
txtStatus.setText("Erreur : " + ex.getMessage());
// fim
return;
}//catch
}
Depois de criado o objeto objImpots, a aplicação é idêntica à aplicação gráfica já escrita. Recomenda-se ao leitor que consulte essa aplicação.
6.4. Exercícios
6.4.1. Exercício 1
Crie uma interface gráfica para o programa sql3 anterior.
6.4.2. Exercício 2
Um applet Java só pode aceder a uma base de dados através do servidor a partir do qual foi carregado. Com efeito, uma vez que um applet não tem acesso ao disco da máquina em que está a ser executado, a base de dados não pode estar na máquina do cliente que utiliza o applet. Encontramo-nos, portanto, na seguinte situação:

A máquina que executa o applet, o servidor e a máquina que aloja a base de dados podem ser três máquinas diferentes. Supõe-se aqui que a base de dados se encontra no servidor.
Problema 1
Escreva em Java a seguinte aplicação de servidor:
- a aplicação de servidor funciona numa porta que lhe é passada como parâmetro
- quando um cliente se liga, a aplicação de servidor envia a mensagem
- o cliente envia então os parâmetros necessários para a ligação a uma base de dados e a consulta que pretende executar, na forma:
Os parâmetros são separados por uma barra. Os primeiros quatro parâmetros são os do programa sql3 descrito neste capítulo.
- A aplicação do servidor cria então uma ligação à base de dados especificada, que deve estar na mesma máquina que a própria aplicação, e executa a consulta SQL nessa base de dados. Os resultados são devolvidos ao cliente no seguinte formato:
...
se se tratar do resultado de uma consulta Select ou
para indicar o número de linhas afetadas por uma consulta de atualização. Se ocorrer um erro de ligação à base de dados ou de execução da consulta, a aplicação devolve
- assim que a consulta for executada, a aplicação do servidor encerra a ligação.
Problema 2
Crie um applet Java que permita consultar o servidor anterior. Pode inspirar-se na interface gráfica do exercício 1 anterior. Como a porta do servidor pode variar, esta deverá ser introduzida na interface do applet. O mesmo se aplica a todos os parâmetros necessários para o envio da linha:
que o cliente deve enviar ao servidor.
6.4.3. Exercício 3
O texto seguinte apresenta um problema inicialmente destinado a ser resolvido em Visual Basic. Adapte-o para ser resolvido em Java num applet. Este applet basear-se-á no servidor do exercício 2. A interface gráfica poderá ser alterada para ter em conta o novo contexto de execução.
Propõe-se a criação de uma aplicação que destaque as diferentes operações de atualização possíveis de uma tabela de uma base de dados ACCESS. A base de dados ACCESS chama-se articles.mdb. Possui uma única tabela denominada «artigos», que regista os artigos vendidos por uma empresa. A sua estrutura é a seguinte:
nome | tipo |
code | código do artigo com 4 caracteres |
nom | o seu nome (cadeia de caracteres) |
prix | o seu preço (real) |
stock_actu | o seu stock atual (número inteiro) |
stock_mini | o stock mínimo (número inteiro) abaixo do qual é necessário reabastecer o artigo |
Propõe-se visualizar e atualizar esta tabela a partir do seguinte formulário:

Os campos de verificação deste formulário são os seguintes:
n.º | tipo | nome | Função |
1 | caixa de texto | ficha | número da ficha visualizada o valor de «enabled» é «false» |
2 | caixa de texto | código | código do artigo |
3 | caixa de texto | nome | nome do artigo |
4 | caixa de texto | preço | preço do artigo |
5 | caixa de texto | atual | stock atual do artigo |
6 | caixa de texto | mínimo | stock mínimo do artigo |
7 | data | data1 | Controlo de dados associado à base de dados databasename=caminho do ficheiro articles.mdb recordsource=artigos connect=access |
8 | HScrollBar | posição | permite navegar na tabela |
9 | botão | OK | permite confirmar uma atualização — só aparece durante a mesma |
10 | botão | Cancelar | permite cancelar uma atualização — só aparece durante a mesma |
11 | frame | frame1 | para dar um toque estético |
12 | caixa de texto | basename | nome da base de dados aberta enabled está definido como false |
13 | caixa de texto | sourcename | nome da tabela aberta «enabled» está definido como «false» |
verificação | Particularidades |
data1 | Os campos «databasename» e «recordsource» estão preenchidos. «databasename» deve indicar a base de dados Access articles.mdb do seu diretório e «recordsource» a tabela «artigos». |
code | Trata-se de uma caixa de texto que pretendemos associar ao campo «code» do registo atual de «data1». Para tal, preenchemos dois campos: datasource: introduz-se «data1» para indicar que a caixa de texto está ligada à tabela associada a «data1» datafield: seleciona-se o campo «code» da tabela «articles» Após estas operações, a caixa de texto «code» conterá sempre o campo «code» do registo atual de «data1». Por outro lado, alterar o conteúdo desta caixa de texto modificará o campo «code» do registo atual. Faz-se o mesmo para as outras caixas de texto |
nom | fonte de dados: data1 campo de dados: nome |
prix | fonte de dados: data1 campo de dados: preço |
actuel | fonte de dados: data1 campo de dados: stock_actu |
minimum | fonte de dados: data1 campo de dados: stock_mini |
A estrutura dos menus é a seguinte
Editar | Navegar | Sair |
Adicionar | Anterior | |
Editar | Seguinte | |
Eliminar | Primeiro | |
Último |
A função das diferentes opções é a seguinte:
menu | nome | função |
mnuadicionar | para adicionar um novo registo à tabela de artigos | |
mnusupprimer | para eliminar da tabela de artigos o registo atualmente visualizado | |
mnumodificar | para alterar, na tabela de artigos, o registo atualmente visualizado | |
mnuprecedent | para passar para o registo anterior | |
mnusuivant | para passar para o registo seguinte | |
mnupremier | para passar para o primeiro registo | |
mnudernier | para passar para o último registo | |
mnuquitter | para sair da aplicação |
Durante o evento form_load, a tabela associada a data1 é aberta (data1.refresh). Se a abertura falhar, é exibida uma mensagem de erro e o programa termina (end). Caso contrário, o formulário é exibido com o primeiro registo da tabela articles visível. Não é possível efetuar qualquer introdução nos campos (propriedade enabled definida como false). A introdução de dados só é possível através das opções «Adicionar» e «Modificar». Os botões OK e «Cancelar» estão ocultos (visible=false).
Propõe-se a criação dos procedimentos relacionados com as diferentes opções do menu, bem como com os botões OK e Cancelar. Numa primeira fase, ignoraremos os seguintes pontos:
- a ativação/desativação de certas opções do menu: por exemplo, a opção «Seguinte» deve ser desativada se estivermos no último registo da tabela
- a gestão da barra de deslocamento horizontal
menu «Procurar/Seguinte»
- avança para o registo seguinte (data1.RecordSet.MoveNext) se não estivermos no fim do ficheiro (data1.RecordSet.EOF). Atualiza a caixa de texto do registo (data1.recordset.absoluteposition/ data1.recordset.recordcount).
Menu «Procurar»/«Anterior»
- avança para o registo anterior (data1.RecordSet.MovePrevious) se não estiver no início do ficheiro (data1.RecordSet.BOF). Atualiza a caixa de texto do registo.
menu Navegar/Primeiro
- avança para o primeiro registo (data1.RecordSet.MoveFirst) se o ficheiro não estiver vazio (data1.recordset.recordcount=0). Atualiza a caixa de texto do registo.
menu Navegar/Último
- avança para o último registo (data1.RecordSet.MoveLast) se o ficheiro não estiver vazio (data1.recordset.recordcount=0). Atualiza a caixa de texto do registo.
menu Edição/Adicionar
- permite adicionar um registo à tabela
- entra no modo de adição de registo (data1.recordset.addnew)
- permite introduzir dados nos 5 campos código, nome, preço, etc. (enabled=true)
- desativa os menus Editar, Procurar e Sair (enabled=false)
- exibe os botões OK e Cancelar (visible=true)
botão OK
- valida uma alteração no registo (data1.recordset.Update)
- oculta os botões OK e Cancelar (visible=false)
- ativa as opções Editar, Procurar, Sair (enabled=true)
- atualiza a caixa de texto do registo
botão Cancelar
- anula uma alteração no registo (data1.recordset.CancelUpdate)
- oculta os botões OK e Cancelar (visible=false)
- ativa as opções Editar, Procurar, Sair (enabled=true)
- atualiza a caixa de texto do registo
menu Editar/Alterar
- permite alterar o registo visualizado no formulário
- coloca no modo de edição do registo (data1.recordset.edit)
- permite introduzir dados nos 4 campos nome, preço, etc. (enabled=true), mas não no campo código (enabled=false)
- desativa os menus Editar, Procurar e Sair (enabled=false)
- exibe os botões OK e Cancelar (visible=true)
menu Editar/Eliminar
- permite eliminar (data1.recordset.delete) o registo visualizado da tabela
- avança para o registo seguinte (data1.recordset.movenext)
menu Sair
- descarrega a folha (unload me)
evento form_unload (cancel as integer)
- ativado pela operação «unload me» ou pelo fecho do formulário com Alt-F4 ou um duplo clique na caixa de sistema, portanto, não necessariamente pela opção «Sair».
- apresenta a pergunta «Deseja mesmo sair da aplicação?» com dois botões Sim/Não (msgbox com style=vbyes+vbno)
- Se a resposta for «Não» (=vbno), define-se «cancel» como -1 e sai-se da rotina form_unload. O valor «cancel» igual a -1 indica que o encerramento da janela foi recusado.
- Se a resposta for «Sim» (=vbyes), a base de dados é encerrada (data1.recordset.close, data1.database.close).
Aqui, estamos interessados na autorização/inibição dos menus. Após cada operação que altere o registo atual, será chamado um procedimento a que poderemos chamar Oueston. Este verificará as seguintes condições:
. se o ficheiro estiver vazio,
- desativaremos os menus «Procurar», «Editar/Eliminar»,
- autorizaremos os restantes
. Se o registo atual for o primeiro,
- desativaremos «Procurar/Anterior»
- e autorizará o restante
. Se o registo atual for o último,
- desativar as opções «Navegar/Seguinte»
- autorizará o resto
Um variador horizontal tem três campos importantes:
- min: o seu valor mínimo
- máx.: o seu valor máximo
- value: o seu valor atual
Inicialização do variador
Ao carregar (form_load), o variador será inicializado da seguinte forma:
- min=0
- max=data1.recordset.recordcount-1
- value=1
Note-se que, logo após a abertura da base de dados (data1.refresh), o número de registos da tabela representada por data1.recordset.recordcount está incorreto. É necessário ir até ao fim da tabela (MoveLast) e, em seguida, regressar ao início da tabela (MoveFirst) para que o valor fique correto.
Ação direta no variador
A posição do cursor do variador representa a posição na tabela.
Quando o utilizador altera o variador (aqui denominado «posição»), o evento position_change é acionado. Neste evento, alteraremos o registo atual da tabela para que este reflita o deslocamento efetuado no variador. Para tal, utilizaremos o campo «absoluteposition» de data1.recordset. Quando se atribui o valor i a este campo, o registo n.º i da tabela torna-se o registo atual. Os registos são numerados a partir de 0 e têm, portanto, um número no intervalo [0,data1.recordset.recordcount-1]. No procedimento position_change, basta escrever
para que o registo atual visualizado no formulário reflita o deslocamento efetuado no variador.
Feito isto, chamaremos em seguida o procedimento Oueston para atualizar os menus.
Atualização do variador
Uma vez que a posição do cursor do variador deve refletir a posição na tabela, é necessário atualizar o valor do variador sempre que houver uma alteração do registo atual na tabela, provocada por qualquer um dos menus. Como cada um destes menus chama o procedimento Oueston, o melhor é colocar esta atualização também neste procedimento. Basta escrever aqui:
Adiciona-se a opção «Procurar», cujo objetivo é permitir ao utilizador visualizar um artigo cujo código for indicado.
Quando esta opção está ativada, ocorrem as seguintes sequências:
- passa-se para o modo «Adicionar ficha» (Addnew), com o único objetivo de não alterar a ficha atual em que se encontrava quando a opção foi ativada,
- permite-se a introdução de dados no campo «código» e apaga-se o conteúdo do campo «ficha»,
- desativam-se os menus e exibem-se os botões OK e «Cancelar»
- quando o utilizador clica em OK, é necessário procurar o registo correspondente ao código introduzido pelo utilizador. No entanto, o procedimento OK_click já é utilizado nas opções «Procurar/Adicionar» e «Procurar/Modificar». Para distinguir entre estes casos, temos de gerir uma variável global a que chamaremos aqui «estado», que terá três valores possíveis: «adicionar», «editar» e «procurar». Os procedimentos associados aos botões OK e «Cancelar» utilizarão esta variável para saber em que contexto são chamados.
- Se «estado» for «procurar», no procedimento associado a OK,
- anula a operação addnew (data1.recordset.cancelupdate), uma vez que não era intenção adicionar um registo. É importante notar que, nesse caso, o registo atual volta a ser aquele que estava presente no ecrã antes da operação «Procurar».
- Constrói-se o critério de pesquisa associado ao código e inicia-se a pesquisa (data1.recordset.findfirst critério),
- se a pesquisa falhar (data1.recordset.nomatch=true), avisa-se o utilizador, volta-se ao modo de adição (addnew) e sai-se do procedimento OK. O utilizador terá de introduzir novamente um novo código ou selecionar a opção Cancelar.
- Se a pesquisa for bem-sucedida, o registo encontrado torna-se o novo registo ativo. Restabelecem-se os menus, ocultam-se os botões OK/Cancelar, inibe-se a introdução de dados no campo «código» e sai-se da rotina.
- . Se o estado for «procurar», no procedimento associado a Cancelar,
- anula-se a operação addnew (data1.recordset.cancelupdate). Voltar-se-á então automaticamente ao registo atual anterior à operação «Navegar/Pesquisar».
- Recupera os menus, oculta os botões OK/Cancelar, impede a introdução de dados no campo «código» e sai do procedimento.
Um artigo deve ser identificado de forma única pelo seu código. Certifique-se de que, na opção «Navegar/Adicionar», a adição seja recusada se o registo a adicionar tiver um código de artigo que já exista na tabela.
6.4.4. Exercício 4
Apresentamos aqui uma aplicação Web baseada no servidor do exercício 2. Trata-se de um protótipo de aplicação de comércio eletrónico.
O cliente encomenda artigos através da seguinte interface Web:

Pode realizar as seguintes operações:
- selecionar um artigo na lista suspensa
- especificar a quantidade pretendida
- confirma a sua compra com o botão Acheter
- a sua compra é apresentada na lista de artigos comprados
- pode remover artigos dessa lista, selecionando um artigo e utilizando o botão Retirer
- quando clica no botão Bilan, obtém o seguinte resumo:

O resumo permite ao utilizador conhecer os detalhes da sua fatura. O utilizador tem acesso aos detalhes relativos a um artigo selecionado na lista suspensa através do botão Informations:

Depois de solicitar o resumo da sua fatura, o utilizador pode validá-la na página seguinte. Para tal, deve indicar o seu endereço de e-mail e confirmar o seu pedido através do botão adequado.

Antes de registar o pedido, a aplicação solicita uma confirmação:

Assim que o pedido for confirmado, a aplicação processa-o e apresenta uma página de confirmação:

Na realidade, a aplicação não processa a encomenda. Limita-se a enviar um e-mail ao utilizador a solicitar-lhe que efetue o pagamento do valor das 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
Pergunta: Criar o equivalente a esta aplicação Web com um applet Java.

