Skip to content

6. Gerenciamento de bancos de dados com o API JDBC

6.1. Informações gerais

Existem inúmeros bancos de dados no mercado. Para padronizar o acesso às bases de dados no Windows, a Microsoft desenvolveu uma interface chamada ODBC (Open DataBase Connectivity). Essa camada oculta as particularidades de cada base de dados por meio de uma interface padrão. No Windows, existem diversos drivers que facilitam o acesso aos bancos de dados. Aqui está, por exemplo, uma lista de drivers instalados em um computador com Windows 95:

Image

Um aplicativo que utilize esses drivers pode acessar qualquer um dos bancos de dados acima sem a necessidade de reescrever o código.

Image

Para que os aplicativos Java também possam aproveitar a interface ODBC, a Sun criou a interface JDBC (Java DataBase Connectivity), que se intercalará entre o aplicativo Java e a interface ODBC:

Image

6.2. Etapas importantes na operação de bancos de dados

6.2.1. Introdução

Em um aplicativo JAVA que utiliza um banco de dados com a interface JDBC, geralmente são encontradas as seguintes etapas:

  1. Conexão com o banco de dados
  2. Envio de consultas SQL ao banco de dados
  3. Recebimento e processamento dos resultados dessas consultas
  4. Encerramento da conexão

As etapas 2 e 3 são realizadas repetidamente, sendo que o encerramento da conexão ocorre somente ao final da consulta ao banco de dados. Trata-se de um esquema relativamente clássico, com o qual você talvez já esteja familiarizado caso já tenha consultado um banco de dados de forma interativa. Vamos detalhar cada uma dessas etapas por meio de um exemplo. Consideremos um banco de dados ACCESS denominado ARTICLES e com a seguinte estrutura:

nome
tipo
code
código do artigo com 4 caracteres
nom
nome (sequência de caracteres)
prix
seu preço (real)
stock_actu
seu estoque atual (número inteiro)
stock_mini
o estoque mínimo (número inteiro) abaixo do qual é necessário reabastecer o item

Esta base ACCESS está definida como fonte de dados “usuário” no gerenciador de bases ODBC:

Image

Image

Suas características são especificadas por meio do botão Configurer da seguinte forma:

Image

Essa configuração consiste essencialmente em associar à base ARTICLES o arquivo Access articles.mdb correspondente a essa base. Feito isso, a base ARTICLES fica acessível às aplicações que utilizam a interface ODBC.

6.2.2. A etapa de conexão

Para utilizar um banco de dados, um aplicativo Java deve primeiro realizar uma etapa de conexão. Isso é feito com o seguinte método de classe:

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

com

DriverManager: classe Java que contém a lista de drivers disponíveis para o aplicativo

Connection: classe Java que estabelece uma conexão entre a aplicação e o banco de dados, por meio da qual a aplicação enviará consultas SQL ao banco de dados e receberá os resultados

URL: nome que identifica o banco de dados. Esse 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

Em nossos exemplos, nos quais utilizaremos exclusivamente drivers ODBC, o driver é chamado odbc. A terceira parte do URL é constituída pelo nome da fonte, acompanhado de eventuais parâmetros. Em 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: Identidade do usuário (login)

mdp: senha do usuário (password)

Resumindo, o programa se conecta a um banco de dados:

  • identificada por um nome (URL)
  • sob a identidade de um usuário (id, senha)

Se esses três parâmetros estiverem corretos e se existir o driver capaz de garantir a conexão do aplicativo Java com o banco de dados especificado, então uma conexão é estabelecida entre o aplicativo Java e o banco de dados. Essa conexão é representada para o programa pelo objeto do tipo Connection retornado pela classe DriverManager. Como essa conexão pode falhar por diversos motivos, ela pode gerar uma exceção. Portanto, escreveremos:


    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 conexão com um banco de dados seja possível, é necessário dispor do driver adequado. Em nossos exemplos, trata-se do driver ODBC, capaz de gerenciar o banco de dados solicitado. Além de esse driver estar disponível na lista de drivers ODBC presentes na máquina, também é necessário dispor da classe JAVA, que atuará como interface com ele. Para isso, o aplicativo solicitará a classe necessária da seguinte maneira:

    Class.forName(String nomClasse)

A classe Class não está de forma alguma ligada à interface JDBC. Trata-se de uma classe geral de gerenciamento de classes. Seu método estático forName permite carregar dinamicamente uma classe e, assim, aproveitar seus atributos e métodos estáticos. A classe que faz a interface com os drivers ODBC do Windows, MS, chama-se “sun.jdbc.odbc.JdbcOdbcDriver”. Portanto, escreveremos (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 estão no pacote java.sql. Portanto, no início do programa, escreveremos:

    import java.sql.*;

Aqui está um programa que permite a conexão a um banco de dados:

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

// chamada: pg PILOTE URL UID MDP
// conecta-se ao banco de dados URL por meio da classe JDBC PILOTE
// o usuário UID é identificado por uma senha 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);
        // conexão com o banco 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 do banco 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    

Aqui está 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 ao banco de dados

A interface JDBC permite o envio de consultas SQL para o banco de dados conectado ao aplicativo Java, bem como o processamento dos resultados dessas consultas. A linguagem SQL (Structured Query Language) é uma linguagem de consultas padronizada para bancos de dados relacionais. Nela, distinguem-se vários tipos de consultas:

  • consultas de pesquisa no banco de dados (SELECT)
  • as consultas de atualização do banco de dados (INSERT, DELETE, UPDATE)
  • as consultas de criação/eliminação de tabelas (CREATE, DELETE)

Supõe-se aqui que o leitor conheça os fundamentos da linguagem SQL.

6.2.3.1. A classe Statement

Para enviar uma consulta SQL, seja ela qual for, a um banco de dados, o aplicativo Java deve dispor de um objeto do tipo Statement. Esse objeto armazenará, entre outras coisas, o texto da consulta. Esse objeto está necessariamente vinculado à conexão atual. Portanto, é um método da conexão estabelecida que permite criar os objetos Statement necessários para a emissão das consultas SQL. Se connexion é o objeto que representa a conexão com o banco de dados, um objeto Statement é obtido da seguinte maneira:

    Statement requete=connexion.CreateStatement();

Uma vez obtido um objeto Statement, é possível emitir consultas SQL. Isso será feito de maneira diferente, dependendo se a consulta é de consulta ou de atualização do banco de dados.

6.2.3.2. Enviar uma consulta de consulta ao banco de dados

Uma consulta é, normalmente, uma consulta do tipo:

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

Apenas as palavras-chave da primeira linha são obrigatórias; as demais são opcionais. Existem outras palavras-chave que não foram apresentadas aqui.

  1. É realizada uma junção com todas as tabelas que aparecem após a palavra-chave `from`
  2. Somente as colunas que aparecem após a palavra-chave `select` são mantidas
  3. Somente as linhas que atendem à condição da palavra-chave `where` são mantidas
  4. As linhas resultantes, ordenadas de acordo com a expressão da palavra-chave `order by`, formam o resultado da consulta.

O resultado de um select é uma tabela. Se considerarmos a tabela ARTICLES anterior e quisermos os nomes dos artigos cujo estoque atual está abaixo do limite mínimo, escreveremos: select nome from artigos where stock_actu<stock_mini. Se quisermos listá-los em ordem alfabética pelos nomes, escreveremos: select nome from artigos where stock_actu<stock_mini order by nome

Para executar esse tipo de consulta, a classe Statement oferece o método executeQuery:

    ResultSet executeQuery(String requête)

onde requête é o texto da consulta SELECT a ser emitida.

Assim, se

  • connexion for o objeto que representa a conexão com o banco 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 resultado 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. A qualquer momento, só se tem acesso a uma linha da tabela, chamada de linha atual. Na criação inicial do ResultSet, a linha atual é a linha nº 1, caso o ResultSet não esteja vazio. Para passar para a linha seguinte, a classe ResultSet dispõe do método next:

    boolean next()

Esse método tenta avançar para a próxima linha do ResultSet e retorna true se for bem-sucedido; caso contrário, retorna false. Se for bem-sucedido, a próxima linha passa a ser a nova linha atual. A linha anterior é perdida e não será possível voltar atrás para recuperá-la. A tabela do ResultSet possui colunas col1, col2,.... Para explorar os diferentes campos da linha atual, estão disponíveis os seguintes métodos:

Type getType("coli") 

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 sequência de caracteres. Em seguida, realiza-se a conversão, se necessário. Caso não se conheça o nome da coluna, é possível utilizar os métodos

Type getType(i) 

onde i é o índice da coluna desejada (i>=1).

6.2.3.4. Um primeiro exemplo

Aqui está um programa que exibe o conteúdo do banco de dados ARTICLES criado anteriormente:

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

// exibe o conteúdo de um banco de dados do sistema ARTICLES

public class articles1{

    static final String DB="ARTICLES";        // banco de dados a ser utilizado

    public static void main(String arg[]){

        Connection connect=null;        // conexão com o banco de dados
        Statement S=null;                // objeto de emissão das consultas
        ResultSet RS=null;            // tabela de resultados de uma consulta
        try{
            // conexão com o banco 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 ser processada
                // ela é exibida na tela
                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);
        }
        // fechamento do banco 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 usar o método getType(nom_colonne). Em vez disso, utilizaremos getType(n.º da coluna). No entanto, para obter todas as colunas, precisaríamos saber quantas colunas possui o ResultSet obtido. A classe ResultSet não nos fornece essa informação. É a classe ResultSetMetaData que nos fornece essa informação. De maneira mais geral, essa classe nos fornece informações sobre a estrutura da tabela, ou seja, sobre a natureza de suas colunas.

É possível acessar as informações sobre a estrutura de um ResultSet instanciando, primeiro, um objeto ResultSetMetaData. Se RS for um ResultSet, o ResultSetMetaData associado é obtido por:

    RS.getMetaData()

Destacam-se dois métodos úteis na classe ResultSetMetaData:

  1. int getColumnCount(), que retorna o número de colunas do ResultSet
  2. String getColumnLabel(int i), que retorna o nome da coluna i do ResultSet (i >= 1)

6.2.3.6. Um segundo exemplo

O programa anterior exibia o conteúdo do banco de dados ARTICLES. Aqui, escrevemos um programa que executa no banco de dados ARTICLES qualquer consulta SQL Select que o usuário digite no teclado.

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

// exibe o conteúdo de um banco de dados do sistema ARTICLES

public class sql1{

    static final String DB="ARTICLES";        // banco de dados a ser explorado

    public static void main(String arg[]){

        Connection connect=null;        // conexão com o banco 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{
            // conexão ao banco 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 digitadas no 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 ser processada
                    // ela é exibida na tela
                    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);
        }
        // fechamento 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    

Aqui estão alguns 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 uma solicitação de atualização do banco de dados

Um objeto do tipo Statement permite armazenar as solicitações SQL. O método que esse objeto utiliza para emitir solicitações SQL de atualização (INSERT, UPDATE, DELETE) não é mais o método executeQuery analisado anteriormente, mas sim o método executeUpdate:

    int executeUpdate(String requête)

A diferença está no resultado: enquanto executeQuery retornava a tabela de resultados (ResultSet), executeUpdate retorna o número de linhas afetadas pela operação de atualização.

6.2.3.8. Um terceiro exemplo

Retomamos o programa anterior e o modificamos ligeiramente: as consultas digitadas no teclado agora são consultas de atualização do banco de dados ARTICLES.

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

// exibe o conteúdo de um banco de dados do sistema ARTICLES

public class sql2{

    static final String DB="ARTICLES";    // banco de dados a ser explorado

    public static void main(String arg[]){

        Connection connect=null;        // conexão com o banco 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{
            // conexão com o banco 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 digitadas no 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);
        }
        // fechamento do banco de dados e do fluxo de entrada
        try{
            // os recursos associados ao banco de dados são liberados
            RS.close();
            S.close();
            connect.close();
            System.out.println("Base " + DB + " fermée");
            // fechamento 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    

Aqui está o resultado de várias execuções dos programas sql1 e sql2:

Lista de linhas do banco 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

Adicionamos 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

Excluímos 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. Enviar uma solicitação SQL qualquer

O objeto Statement, necessário para a emissão de solicitações SQL, possui um método execute capaz de executar qualquer tipo de solicitação SQL:

    boolean execute(String requête)

O resultado retornado é o valor booleano true se a consulta retornou um ResultSet (executeQuery), false se ela retornou um número (executeUpdate). O valor ResultSet obtido pode ser recuperado com o método getResultSet e o número de linhas atualizadas, pelo método getUpdateCount. Assim, escreveremos:


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 em um programa sql3, agora capaz de executar qualquer consulta SQL digitada no teclado. Para tornar o programa mais genérico, as características do banco de dados a ser utilizado são passadas como parâmetros para o programa.

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

// chamada: pg PILOTE URL UID MDP
// conecta-se ao banco de dados URL por meio da classe JDBC PILOTE
// o usuário UID é identificado por uma senha 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 conexã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 envio das consultas
        ResultSet RS=null;                    // tabela de resultados de uma consulta
        String sqlText;                            // texto da consulta SQL a ser executada
        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{
            // conexão com o banco 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 digitadas no 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 — vamos analisá-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 ser processada
                            // é exibido na tela
                            for(int i=1;i<nbColonnes;i++)
                                System.out.print(RS.getString(i)+",");
                            System.out.println(RS.getString(nbColonnes));
                        }// linha seguinte do ResultSet
                    } else {
                        // essa era uma solicitação 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);
                }
                // próxima solicitação
                System.out.print("\nNouvelle Requête : ");
                sqlText=in.readLine();                
            }// while
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // fechamento do banco de dados e do fluxo de entrada
        try{
            // os recursos associados à base são liberados
            RS.close();
            S.close();
            connect.close();
            System.out.println("Base " + arg[1] + " fermée");
            // fechamento 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 arquivo 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 maneira:

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

O programa, portanto, lê suas entradas do arquivo requetes e grava suas saídas no arquivo 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 é mises à jour

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

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

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

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

1 ligne(s) ont é mises à jour

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

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

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

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


1 ligne(s) ont é mises à jour

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

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

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

6.3. IMPOTS com um banco de dados

Da última vez que abordamos o problema do cálculo de impostos, foi por meio de uma interface gráfica e os dados estavam armazenados em um arquivo. Retomamos essa versão, partindo agora do pressuposto de que os dados estão em um banco de dados ODBC-MySQL. O MySQL é um SGBD de domínio público que pode ser utilizado em diversas plataformas, incluindo Windows e Linux. Com este SGBD, foi criado um banco de dados dbimpots contendo uma única tabela chamada impots. O acesso ao banco de dados é controlado por um login/senha, neste caso admimpots/mdpimpots. A captura de tela mostra como utilizar o banco 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 do aplicativo é a seguinte:

Image

A interface gráfica sofreu algumas alterações:

n.º
tipo
nome
função
1
JTextField
txtConnexion
cadeia de conexão ao banco de dados ODBC
2
JScrollPane
JScrollPane1
contêiner para a área de texto 3
3
JTextArea
txtStatus
exibe mensagens de status, incluindo mensagens de erro

A sequência de conexão digitada em (1) tem o seguinte formato: DSN;login;senha, com

DSN
o nome DSN da fonte de dados ODBC

login
identidade de um usuário com direito de leitura na base de dados

motdepasse
sua senha

O banco de dados dbimpots foi criado manualmente com o MySQL. Ele é transformado na fonte de dados ODBC da seguinte maneira:

  • inicia-se o gerenciador de fontes de dados ODBC de 32 bits

Image

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

Image

  • seleciona-se o driver MySQL e executa-se [Terminer]

Image

  • o driver MySQL solicita algumas informações:
1
o nome DSN a ser atribuído à fonte de dados ODBC — pode ser qualquer um
2
a máquina na qual o SGBD MySQL é executado — neste caso, localhost. Vale ressaltar que o banco de dados pode ser remoto. Os aplicativos locais que utilizam a fonte de dados ODBC não perceberiam isso. Esse seria o caso, notadamente, do nosso aplicativo Java.
3
o banco de dados MySQL a ser utilizado. MySQL é um SGBD que gerencia bancos de dados relacionais, que são conjuntos de tabelas interligadas por relações. Aqui, fornece-se o nome do banco de dados gerenciado.
4
o nome de um usuário com direito de acesso a essa base de dados
5
sua senha

Depois de definida a fonte de dados ODBC, podemos testar nosso programa:

 

Vamos examinar o código que, em comparação com a versão gráfica sem banco de dados, foi modificado. Recorde-se o código da classe impots utilizada até agora:

// criação de uma classe de 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+")");
        // tudo certo
        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 cotas
        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

Essa 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 a ela um novo construtor que permita criar as mesmas três tabelas a partir de um banco de dados:

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

    // dsnIMPOTS: nome DSN do banco de dados
     // userIMPOTS, mdpIMPOTS: login/senha de acesso ao banco de dados

Para este exemplo, decidimos não implementar esse novo construtor na classe impots, mas sim em uma 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 um banco de dados
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: nome DSN do banco de dados
     // userIMPOTS, mdpIMPOTS: login/senha de acesso ao banco de dados

     // as tabelas de dados
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();

    // conexão com o banco 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
     // fechamento 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 do banco de dados que lhe foi passado como parâmetro e preenche as três tabelas limites, coeffR e coeffN. Podem ocorrer alguns erros. O construtor não os trata, mas os “repassa” 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 de sua classe base impots. Esses campos estão declarados como privados:

    private double[] limites, coeffR, coeffN;

a classe impotsJDBC não tem acesso direto a esses campos. Portanto, fazemos uma primeira modificação na classe base, escrevendo:

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

O atributo protected permite que as classes derivadas da classe impots tenham acesso direto aos campos declarados com esse atributo. Precisamos fazer uma segunda modificaçã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 instanciar um objeto de uma classe filha, é preciso primeiro instanciar um objeto da classe pai. Para isso, 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 fica claro qual construtor da classe pai poderia ser chamado. Por enquanto, existe apenas um, e ele não é adequado. O compilador, então, procura na classe pai um construtor sem parâmetros que possa ser chamado. Ele não encontra nenhum, e isso gera um erro na compilação. Portanto, adicionamos um construtor sem argumentos à nossa classe impots:

   // construtor vazio
  protected impots(){}

Nós o declaramos como “protected” para que ele só possa ser usado por classes filhas. A estrutura da classe impots ficou agora da seguinte forma:

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 do nosso aplicativo passa a ser a seguinte:

  void mnuInitialiser_actionPerformed(ActionEvent e) {
    // recuperamos a string de conexã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");
       // retorno à interface gráfica
      txtConnexion.requestFocus();
      return;
    }//if
     // carregando 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 é mais possível fazer alterações
      mnuInitialiser.setEnabled(false);
      txtConnexion.setEditable(false);
    }catch(Exception ex){
       // problema
      txtStatus.setText("Erreur : " + ex.getMessage());
      // fim
      return;
    }//interceptação
  }

Após a criação do objeto objImpots, a aplicação é idêntica à aplicação gráfica já desenvolvida. 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 acessar um banco de dados por meio do servidor a partir do qual foi carregado. De fato, como um applet não tem acesso ao disco da máquina na qual está sendo executado, o banco de dados não pode estar na máquina do cliente que está utilizando o applet. Portanto, estamos na seguinte situação:

Image

A máquina que executa o applet, o servidor e a máquina que hospeda o banco de dados podem ser três máquinas diferentes. Supõe-se aqui que o banco de dados esteja no servidor.

Problema 1

Escreva em Java o seguinte aplicativo de servidor:

  • a aplicação de servidor opera em uma porta que lhe é passada como parâmetro
  • quando um cliente se conecta, a aplicação de servidor envia a mensagem
200 - Bienvenue - Envoyez votre requête
  • o cliente então envia os parâmetros necessários para a conexão com um banco de dados e a consulta que deseja executar no formato:
Pilote Java/URL base/UID/MDP/requête SQL

Os parâmetros são separados por uma barra. Os quatro primeiros parâmetros são os do programa sql3 descrito neste capítulo.

  • O aplicativo servidor cria então uma conexão com o banco de dados especificado, que deve estar na mesma máquina que ele, e executa a consulta SQL nele. Os resultados são retornados ao cliente no formato:
100 - ligne1
100 - ligne2

...

se for o resultado de uma consulta Select ou

101 - nbLignes

para retornar o número de linhas afetadas por uma consulta de atualização. Se ocorrer um erro de conexão com o banco de dados ou de execução da consulta, o aplicativo retorna

500 - Message d’erreur
  • assim que a consulta for executada, o aplicativo servidor encerra a conexão.

Problema 2

Crie um applet Java que permita consultar o servidor anterior. É possível se inspirar na interface gráfica do exercício 1 anterior. Como a porta do servidor pode variar, ela deverá ser inserida na interface do applet. O mesmo se aplica a todos os parâmetros necessários para o envio da linha:

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

que o cliente deve enviar ao servidor.

6.4.3. Exercício 3

O texto a seguir apresenta um problema inicialmente destinado a ser resolvido em Visual Basic. Adapte-o para ser resolvido em Java em um applet. Este applet utilizará o servidor do exercício 2. A interface gráfica poderá ser modificada para levar em conta o novo contexto de execução.

Propõe-se a criação de um aplicativo que destaque as diferentes operações de atualização possíveis de uma tabela do banco de dados ACCESS. O banco de dados ACCESS é denominado articles.mdb. Ele possui uma única tabela chamada “artigos”, que lista os artigos vendidos por uma empresa. Sua estrutura é a seguinte:

nome
tipo
code
código do artigo com 4 caracteres
nom
seu nome (sequência de caracteres)
prix
seu preço (real)
stock_actu
seu estoque atual (número inteiro)
stock_mini
o estoque mínimo (número inteiro) abaixo do qual é necessário reabastecer o item

Propomos visualizar e atualizar essa tabela a partir do seguinte formulário:

Image

Os campos de verificação deste formulário são os seguintes:

n.º
tipo
nome
Função
1
caixa de texto
ficha
número do registro exibido
o atributo “enabled” está definido como “false”
2
caixa de texto
código
código do artigo
3
campo de texto
nome
nome do artigo
4
campo de texto
preço
preço do artigo
5
caixa de texto
atual
estoque atual do artigo
6
caixa de texto
mínimo
estoque mínimo do item
7
data
data1
Controle de dados associado ao banco de dados
databasename=caminho do arquivo articles.mdb
recordsource=artigos
connect=access
8
HScrollBar
posição
permite navegar pela tabela
9
botão
OK
permite confirmar uma atualização — só aparece durante a atualização
10
botão
Cancelar
permite cancelar uma atualização — só aparece durante a atualização
11
frame
frame1
para dar um toque de beleza
12
caixa de texto
basename
nome do banco de dados aberto
enabled está definido como false
13
caixa de texto
sourcename
nome da tabela aberta
enabled está definido como false

Criação da planilha no VB

verificação
Particularidades
data1
Os campos databasename e recordsource estão preenchidos. O campo databasename deve indicar o banco de dados Access articles.mdb do seu diretório e o campo recordsource, a tabela de artigos.
code
Trata-se de uma caixa de texto que queremos vincular ao campo “code” do registro atual de “data1”. Para isso, preenchemos dois campos:
datasource: insira “data1” para indicar que a caixa de texto está vinculada à tabela associada a “data1”
datafield: selecione o campo “código” da tabela “artigos”
Após essas operações, a caixa de texto “código” conterá sempre o campo “código” do registro atual de data1. Por outro lado, alterar o conteúdo dessa caixa de texto modificará o campo “código” do registro atual.
Faz-se o mesmo para as demais 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

Os menus

A estrutura dos menus é a seguinte

Edição
Navegar
Sair
Adicionar
Anterior
 
Editar
Próximo
 
Excluir
Primeiro
 
 
Último
 

A função das diferentes opções é a seguinte:

menu
nome
função
Ajouter
mnuadicionar
para adicionar um novo registro à tabela de itens
Supprimer
mnusupprimer
para excluir da tabela de artigos o registro atualmente visualizado
Modifier
mnumodificar
para modificar, na tabela de artigos, o registro atualmente visualizado
Précédent
mnuprecedent
para passar para o registro anterior
Suivant
mnusuivant
para passar para o registro seguinte
Premier
mnupremier
para ir para o primeiro registro
Dernier
mnudernier
para ir para o último registro
Quitter
mnuquitter
para sair do aplicativo

Carregando a planilha

Durante o evento form_load, a tabela associada a data1 é aberta (data1.refresh). Se a abertura falhar, uma mensagem de erro é exibida e o programa é encerrado (end). Caso contrário, o formulário é exibido com o primeiro registro da tabela articles visível. Não é possível inserir dados nos campos (propriedade enabled definida como false). A inserção de dados só é possível por meio das opções “Adicionar” e “Modificar”. Os botões OK e “Cancelar” estão ocultos (visible=false).


Parte 1

Propomos construir os procedimentos relacionados às diferentes opções do menu, bem como aos botões OK e Cancelar. Inicialmente, ignoraremos os seguintes pontos:

  • a ativação/desativação de certas opções do menu: por exemplo, a opção “Próximo” deve ser desativada se estivermos no último registro da tabela
  • o gerenciamento da barra de rolagem horizontal

menu “Procurar/Próximo”

  • avança para o próximo registro (data1.RecordSet.MoveNext) se não estivermos no final do arquivo (data1.RecordSet.EOF). Atualiza a caixa de texto do registro (data1.recordset.absoluteposition/ data1.recordset.recordcount).

Menu Navegar/Anterior

  • avança para o registro anterior (data1.RecordSet.MovePrevious) se não estiver no início do arquivo (data1.RecordSet.BOF). Atualiza a caixa de texto do registro.

menu Navegar/Primeiro

  • avança para o primeiro registro (data1.RecordSet.MoveFirst) se o arquivo não estiver vazio (data1.recordset.recordcount=0). Atualiza a caixa de texto do registro.

menu Navegar/Último

  • vai para o último registro (data1.RecordSet.MoveLast) se o arquivo não estiver vazio (data1.recordset.recordcount=0). Atualiza a caixa de texto do registro.

menu Edição/Adicionar

  • permite adicionar um registro à tabela
  • entra no modo de adição de registro (data1.recordset.addnew)
  • permite preencher os 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

  • confirma uma alteração no registro (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 registro

botão Cancelar

  • invalida uma alteração no registro (data1.recordset.CancelUpdate)
  • oculta os botões OK e Cancelar (visible=false)
  • ativa as opções Editar, Navegar, Sair (enabled=true)
  • atualiza a caixa de texto do registro

menu Editar/Modificar

  • permite modificar o registro exibido no formulário
  • entra no modo de edição do registro (data1.recordset.edit)
  • permite preenchimentos 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/Excluir

  • permite excluir (data1.recordset.delete) o registro visualizado da tabela
  • avança para o próximo registro (data1.recordset.movenext)

menu Sair

  • descarrega a planilha (unload me)

evento form_unload (cancel as integer)

  • ativado pela operação “unload me” ou pelo fechamento do formulário com Alt-F4 ou um clique duplo no campo do sistema, portanto, não necessariamente pela opção “Sair”.
  • exibe a pergunta “Deseja realmente sair do aplicativo?” 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 do procedimento form_unload. O valor “cancel” igual a -1 indica que o fechamento da janela foi recusado.
  • Se a resposta for “Sim” (=vbyes), o banco de dados é fechado (data1.recordset.close, data1.database.close).

Parte 2 – Gerenciamento dos menus

Aqui, abordamos a ativação/desativação dos menus. Após cada operação que altere o registro atual, será chamado um procedimento que poderemos chamar de Oueston. Este verificará as seguintes condições:

. se o arquivo estiver vazio,

  • desativaremos os menus “Navegar”, “Editar/Excluir”,
  • autorizaremos os demais

. Se o registro atual for o primeiro,

  • desativaremos “Navegar/Anterior”
  • e permitirá o restante

. Se o registro atual for o último,

  • desativaremos “Navegar/Próximo”
  • permitirá o restante

Parte 3 - Gerenciamento do variador horizontal

Um variador horizontal possui três campos importantes:

  • min: seu valor mínimo
  • máx.: seu valor máximo
  • value: seu valor atual

Inicialização do inversor

Ao carregar (form_load), o inversor será inicializado da seguinte forma:

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

Observe que, logo após a abertura do banco de dados (data1.refresh), o número de registros da tabela representada por data1.recordset.recordcount está incorreto. É necessário ir até o final da tabela (MoveLast) e, em seguida, voltar ao início da tabela (MoveFirst) para que o valor fique correto.

Ação direta no inversor

A posição do cursor do variador representa a posição na tabela.

Quando o usuário altera o variador (denominado “posição” aqui), o evento position_change é acionado. Nesse evento, alteraremos o registro atual da tabela para que ele reflita o deslocamento realizado no variador. Para isso, utilizaremos o campo absoluteposition de data1.recordset. Ao atribuir o valor i a esse campo, o registro nº i da tabela passa a ser o registro atual. Os registros são numerados a partir de 0 e, portanto, possuem um número no intervalo [0,data1.recordset.recordcount-1]. No procedimento position_change, basta escrever

    data1.recordset.absoluteposition=position.value

para que o registro atual exibido no formulário reflita o deslocamento realizado no variador.

Feito isso, chamaremos então o procedimento Oueston para atualizar os menus.

Atualização do variador

Como 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 mudança no registro atual da tabela, provocada por qualquer um dos menus. Como cada um deles chama o procedimento Oueston, o melhor é incluir essa atualização também nesse procedimento. Basta escrever aqui:

    position.value=data1.recordset.absoluteposition

Parte 4 - Opção “Pesquisar”

Adicionamos a opção “Procurar”, cujo objetivo é permitir que o usuário visualize um item cujo código ele forneça.

Quando essa opção é ativada, ocorrem as seguintes etapas:

  • entra-se no modo “Adicionar registro” (Addnew), com o único objetivo de não alterar o registro atual em que se estava quando a opção foi ativada,
  • permitimos a digitação no campo “código” e apagamos o conteúdo do campo “ficha”,
  • os menus são desativados e os botões OK e “Cancelar” são exibidos
  • quando o usuário clica em OK, precisamos procurar o registro correspondente ao código digitado pelo usuário. No entanto, o procedimento OK_click já é utilizado nas opções “Procurar/Adicionar” e “Procurar/Modificar”. Para distinguir entre esses casos, precisamos gerenciar uma variável global que chamaremos aqui de “estado”, que terá três valores possíveis: “adicionar”, “editar” e “pesquisar”. Os procedimentos associados aos botões OK e Cancelar usarão essa variável para saber em que contexto estão sendo chamados.
  • Se “estado” for “pesquisar”, no procedimento associado a OK,
    • cancela a operação addnew (data1.recordset.cancelupdate), pois não havia a intenção de adicionar um registro. É importante observar que, nesse caso, o registro atual volta a ser aquele que estava na tela antes da operação “Navegar/Pesquisar”.
    • Constrói-se o critério de pesquisa relacionado ao código e inicia-se a pesquisa (data1.recordset.findfirst critério),
    • se a pesquisa falhar (data1.recordset.nomatch=true), isso é sinalizado ao usuário; em seguida, retorna-se ao modo de adição (addnew) e sai-se do procedimento OK. O usuário deverá digitar um novo código ou selecionar a opção Cancelar.
    • Se a pesquisa for bem-sucedida, o registro encontrado passa a ser o novo registro ativo. Os menus são restaurados, os botões OK/Cancelar são ocultados, a entrada no campo “código” é desativada e o procedimento é encerrado.
  • . Se o estado for “procurar”, no procedimento vinculado a Cancelar,
    • cancela-se a operação addnew (data1.recordset.cancelupdate). Em seguida, retorna-se automaticamente ao registro atual anterior à operação “Navegar/Pesquisar”.
    • Recupera os menus, oculta os botões OK/Cancelar, impede a entrada de dados no campo código e sai do procedimento.

Parte 5 - Gerenciamento do código

Um artigo deve ser identificado de forma exclusiva por seu código. Certifique-se de que, na opção “Navegar/Adicionar”, a adição seja recusada se o registro a ser adicionado 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 faz pedidos de itens por meio da seguinte interface da Web:

Image

Ele pode realizar as seguintes operações:

  • selecionar um item na lista suspensa
  • especificar a quantidade desejada
  • confirma a compra com o botão Acheter
  • sua compra é exibida na lista de itens comprados
  • ele pode remover itens dessa lista, selecionando um item e clicando no botão Retirer
  • ao clicar no botão Bilan, ele obtém o seguinte resumo:

Image

O extrato permite que o usuário conheça os detalhes de sua fatura. O usuário tem acesso aos detalhes relativos a um item selecionado na lista suspensa por meio do botão Informations:

Image

Depois que o usuário solicitar o resumo da fatura, ele poderá confirmá-la na página seguinte. Para isso, ele deverá fornecer seu endereço de e-mail e confirmar o pedido clicando no botão correspondente.

Image

Antes de salvar o pedido, o aplicativo solicita uma confirmação:

Image

Assim que o pedido for confirmado, o aplicativo o processa e exibe uma página de confirmação:

Image

Na verdade, o aplicativo não processa o pedido. Ele se limita a enviar um e-mail ao usuário solicitando que ele pague o 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 este aplicativo da Web usando um applet Java.