Esse artigo faz parte da revista Java Magazine edição 27. Clique aqui para ler todos os artigos desta edição

Atenção: por essa edição ser muito antiga não há arquivo PDF para download.Os artigos dessa edição estão disponíveis somente através do formato HTML. 

Uma Aplicação Completa

Parte 3: Banco de Dados e Preferências dos Usuários

Nesta parte concluímos a aplicação, implementando suporte a preferências e acesso a banco de dados, e fazendo ajustes finais

 

Neste artigo completamos aplicação de Lista de Tarefas (“Todo”), que iniciamos nesta coluna na Edição 25 – um exemplo completo do desenvolvimento de uma aplicação gráfica baseada no Swing, utilizando os recursos do IDE livre NetBeans.

O primeiro artigo desta série demonstrou como usar os recursos do editor visual do NetBeans para prototipar a interface como o usuário. O segundo mostrou como customizar a exibição de dados em JTable do Swing e como organizar o tratamento de eventos na aplicação, de acordo com a arquitetura Model-View-Controller.

Este último artigo mostra como filtrar as informações exibidas na tabela e como ler e gravar as informações em um banco de dados relacional.

Para manter a aplicação simples de instalar e distribuir, será utilizado o banco de dados HSQLDB (apresentado na Edição 7). Dessa forma, a aplicação final será formada por apenas dois pacotes jar: o Todo.jar gerado pelo NetBeans, contendo as classes da aplicação; e o hsqldb.jar do HSQLDB.

O quadro “Configurando o projeto no NetBeans” descreve como configurar o IDE para uso do HSQLDB durante o desenvolvimento da aplicação. E o leitor que preferir utilizar um banco de dados diferente pode consultar o quadro “Experimentando com outros bancos”.

 

Arquitetura da aplicação

No artigo anterior, chegamos a uma aplicação bastante funcional, que permitia edição e consulta de tarefas armazenadas em memoria, mas sem ainda persistir os dados em disco. Utilizamos a arquitetura MVC, pela qual as classes da aplicação devem atuar apenas num dos seguintes papéis: gerenciamento dos dados (Modelo), visualização dos dados (Visão), ou resposta às ações do usuário (Controle). Criamos um pacote para cada parte: o diagrama de classes UML da Figura 1 representa as principais classes desses pacotes.

 

Figura 1. Principais classes da aplicação de exemplo, desenvolvidas nas duas partes desta série. As classes em azul-claro são neste artigo, e a classe GerenciadorTarefas é praticamente reescrita.

 

No pacote todo.modelo, temos a classe Tarefa, construída com um VO (Value Object, um “repositório de dados” com pouca ou nenhuma inteligência própria). Há também a classe GerenciadorTarefas, cuja versão inicial tinha o objetivo de fazer uma “simulação” da persistência mantendo os dados em memória, pois o foco estava nas classes de visão e controle. Nesta edição vamos expandir a classe GerenciadorTarefas para conter o código de persistência num banco de dados. Também será criada uma nova classe de modelo, chamada Parametros, contendo os dados de configuração da aplicação – por exemplo, o diretório onde são salvos os arquivos do bando de dados.

O pacote todo.visao contém duas janelas que foram prototipadas na primeira parte, ListaTarefas (um JFrame) e EditaTarefas (um JDialog), além de várias classes auxiliares. A Figura 2 reapresenta as duas janelas, para que o leitor que não tenha idéia da aparência e funcionalidade da aplicação.

O pacote todo.controle contém uma única classe, ConsultaEditaTarefas, que responde às ações do usuário para criar, editar ou listar tarefas. Essas classe delega as ações em si para a classe de modelo GerenciadorTarefas e comanda a atualização das classes de visão, quando necessário. Neste artigo será criada outra classe controladora, chamada CriaAbreListaTarefas, responsável por criar novos bancos de dados existentes em outras localizações. Ela também receberá as funcionalidades de “miscelânea” da aplicação, por exemplo a exibição da caixa “Sobre”.

 

Figura 2. Janelas da aplicação Lista de Tarefas

 

Os três pacotes contêm ainda uma série de classes auxiliares não descritas aqui, por exemplo, exceções customizadas ou modelos para o JTable do Swing. Elas não afetam a arquitetura da aplicação e na maioria dos casos são utilizadas por classes fora dos respectivos pacotes.

A Figura 3 apresenta as classes da aplicação, na visão de projetos do NetBeans. O quadro “Todas as classes do exemplo” apresenta uma breve descrição do papel de cada uma.

 

Figura 3. Todas as classes da aplicação final, na visão de projeto do NetBeans.

 

Acesso ao banco de dados

A nova versão da classe GerenciadorTarefas foi construída como um DAO. A idéia é que as demais classes da aplicação não tenham conhecimento do uso de banco de dados relacionais ou de outra tecnologia de armazenamento e recuperação de informações – elas apenas chamam os métodos da classe DAO para ler e gravar objetos. Nosso DAO fornece métodos como listaTarefas() e editaTarefa(), que serão chamados pelo controlador apropriado (no caso, ConsultaEditaTarefas) de acordo com a operação solicitada pelo usuário. A Listagem 1 fornece o código completo dessa classe, que será detalhado a seguir.

A nova classe de modelo irá interagir com a classe Parametros para obter as configurações de acesso ao banco de dados, que serão utilizadas em conecta() para criar uma conexão ao HSQLDB. A conexão é mantida como variável de instância (atributo) da classe DAO. É fornecido também o método desconecta(), para que a aplicação possa fechar a conexão quando for encerrada.

O próprio HSQLDB cria automaticamente uma base de dados vazio no momento da conexão, caso o arquivo referenciado na URL JDBC não exista. Como padrão, estamos utilizando a base dB/todo (que corresponde ao arquivo dB/todo.script) no diretório pessoal do usuário, que será o $HOME em sistemas Linux ou a pasta correspondente em sistemas Windows. Para simplificar a utilização, o próprio DAO irá criar as tabelas na base de dados, caso elas não existam. Isso é feito pelos métodos privativos existemTabelas() e criaTabelas().

Visando simplificar a escrita dos métodos de consulta e alteração de dados propriamente ditos, são fornecidos os métodos auxiliares finaliza(), executa(), prepara(), consulta() e altera(), que encapsulam seqüências comuns chamadas JDBC. Todos eles, exceto os dois últimos, poderiam ser movidos para uma superclasse abstrata numa aplicação com vários DAOs. Os métodos consulta() e altera() seriam praticamente iguais, variando apenas quanto aos nomes dos campos, em outras classes DAO de uma aplicação maior.

O método finaliza() tem importância especial, pois temos que garantir que os recursos do banco de dados sejam liberados o quanto antes, mesmo que ocorram exceções durante o acesso. Todos os métodos de acesso ou alteração devem conter uma cláusula finally que chama finaliza() (veja por exemplo o método executa()).

Os métodos específicos de acesso e alteração de dados – adicionaTarefa(), editatarefa(), marcaComoConcluida(), removeTarefa(), listaTarefas() e listaTarefasComAlarme() – são todos escritos chamando a finaliza() é garantia em caso de erros.

 Por fim, o DAO fornece o método validaTarefa(), que é chamado pelo controlador antes de se inserir ou modificar uma tarefa. Isso permite que a operação seja abortada, e o usuário informado de que houve erros na digitação dos atributos da tarefa.

Observe que os métodos da classe de todo.modelo, nunca retornam exceções de baixo nível ao controlador, por exemplo SQLException ou IOException. São retornadas subclasses de ModeloException, como a BancoDeDadosException. Estas classes, por sua vez, encapsulam a exceção de baixo nível como “causa” (acessível pelo método getCause() de Exception), de modo que a informação esteja disponível durante a depuração da aplicação. Isso foi feito para que o controlador fique completamente isolado de qualquer conhecimento relativo ao banco de dados ou outro mecanismo de persistência. O mesmo estilo de programação poderia ser utilizado caso fosse empregada outra forma de persistência, por exemplo arquivos XML.

 

Listagem 1. Classe de modelo GerenciadorTarefas

 

package todo.modelo;

 

import java.io.*;

import java.sql .*;

import java.text.*;

import java.util.List;

import java.util.ArrayList;

 

public class GerenciadorTarefas {

private Parametros params;

private Connection con;

private Statement stmt;

private ResultSet rs;

 

public GerenciadorTarefas(Parametros params) throws

BancoDeDadosException

{

  this.params = params;

  conecta();

}

 

public void reconecta(String database) throws

BancoDeDadosException

{

desconecta();

params.setDatabase(database);

conecta();

}

 

private void conecta() throws BancoDeDadosException {

    try {

Class.forName(params.getJdbcDriver());

con = DriverManager.getConnection(params.getJdbcUrl().

       "sa", "");

if (!existemTabelasC)

  criaTabelas() ;

}

catch (BancoDeDadosException e) {

   throw new BancoDeDadosException(

      "Não foi possível criar as tabelas no banco de dados",

           e.getCause());

}

catch (ClassNotFoundException e) {

   throw new BancoDeDadosException(

        "Não foi possível carregar o driver do banco de da

           dos" , e);

}

catch (SQLException e) {

throw new BancoDeDadosException(

    Não foi possível conectar ao banco de dados" , e);

 }

}

private boolean existemTabelas() {

  try {

String sql = "SELECT COUNT(*) FROM todo";

stmt = con.createStatement();

rs = stmt.executeQuery(sql);

return true;

}

catch (SQLException e) {

return false;

...

Quer ler esse conteúdo completo? Tenha acesso completo