Aplicativo de Vendas – Parte II
Dando seguimento à nossa série de artigos, vamos ver hoje como construir um plugin de acesso a dados para o nosso programa.
Apesar de não estar diretamente relacionado à plataforma RCP, a construção deste plugin é essencial para os próximos passos, pois um programa de vendas é basicamente uma interface entre o usuário e seu banco de dados de clientes, produtos, vendas, etc.
Acesso direto x mapeamento objeto-relacional
Para termos acesso aos dados armazenados no banco de dados, existem hoje 2 alternativas principais: acesso direto ao banco, com os comandos SQL “embutidos” no meio do seu código Java e o mapeamento de objetos do seu modelo (suas classes) para tabelas no banco de dados, a chamada ORM (Objet – Relational Mapping).
As duas alternativas têm seus defensores e detratores. Não quero entrar no mérito da questão aqui, pois sou adepto da filosofia de utilizar a ferramenta certa para o problema certo. Lembre-se que quando tudo o que se conhece é um martelo, tudo se parece com pregos.
Sendo assim, vamos utilizar o acesso direto ao servidor de banco de dados em nossa aplicação, pois aprender Hibernate ou outro framework de persistência está fora do escopo desta série de artigos e seria, em minha humilde opinião, usar canhão para matar barata.
O padrão DAO
Apesar de não utilizarmos um framework de persistência, não iremos também poluir o nosso código de interface com o usuário com repetidas chamadas ao banco de dados. Iremos utilizar o pattern DAO (Data Access Objects).
Na verdade iremos utilizar um “mini” padrão DAO, pois não iremos criar a DAO Factory, principalmente por se tratar de um projeto pequeno.
Para os que não conhecem ainda, o padrão DAO consiste basicamente em você escrever seu próprio código de acesso a dados, mas de uma forma organizada, criando, para cada entidade em seu domínio um correspondente “mapeável” para banco de dados.
Desta forma, para cada classe de negócio que criarmos (Cliente, Produto, Venda, etc), criaremos também uma versão DAO da mesma (ClienteDAO, ProdutoDAO, VendaDAO, e por aí vai ...).
O objetivo é separar o domínio do problema (nosso esquema de classes de negócio) de sua representação física no banco de dados. Fazendo isso, para armazenar um novo cliente na base de dados por exemplo, poderíamos fazer o seguinte:
public void novoCliente() {
Cliente cli = new Cliente ();
cli.setNome ("Fulano de tal");
cli.setEndereco ("Rua tal de tal, S/N");
cli.setFone ("5555 - 1234");
// Armazena o novo cliente no banco de dados
ClienteDAO cliDAO = new ClienteDAO();
cliDAO.Create (cli);
}
Listagem 01: Pseudo código demonstrando o padrão DAO
Não se preocupe por enquanto com os detalhes de implementação das classes Cliente e ClienteDAO, pois veremos isso logo mais adiante. Note apenas que para cada entidade iremos criar uma versão DAO, e que é essa versão DAO a responsável pelo CRUD (acrônimo em inglês para as operações básicas de manipulação de dados em linguagem SQL: CReate, Update, Delete).
Value Objects
No exemplo mostrado na listagem 01 a classe cliente é um Value Object. Ela recebe esta denominação porque sua única finalidade é armazenar os valores referentes à sua entidade (neste caso os dados de um cliente específico).
Toda a manipulação destes objetos ocorre na parte “controller” da aplicação, que será o tema de nosso próximo artigo. Por hora, vamos nos ater nos detalhes da comunicação de nosso aplicativo de vendas com o servidor de banco de dados de nossa preferência.
A escolha do banco de dados
Qual servidor de banco de dados utilizar? Esta questão surge agora, uma vez que teremos de realizar consultas SQL ao banco de dados, e é sabido que existem diferenças significativas entre o padrão SQL adotado pelos diferentes fabricantes de SGBDs.
Existem grandes nomes neste mercado, como Oracle, IBM, Microsoft, Sybase entre outras. A maior parte deles possui versões free (gratuitas, não livres) de seus produtos. A escolha de qual servidor de banco de dados utilizar cabe à você. Sua familiaridade (ou não) com cada um destes produtos é que vai definir isto.
Existem também alternativas livres, como o PostgreSQL (que iremos utilizar neste artigo) e o MySQL. Ambos são desenvolvidos pela comunidade de código aberto e possuem muitos recursos avançados. Também possuem bastante documentação (em vários idiomas) disponível.
Criando o plugin de acesso a dados
Para criar o plugin de acesso a dados, utilize os procedimentos mostrados no artigo Introdução ao Eclipse RCP. Siga o passo a passo apresentado no artigo referenciado, com algumas modificações, conforme mostrado a seguir:
O nome do projeto será br.com.devmedia.DatabaseAccess
Como mostrado na figura 01 abaixo, você irá desmarcar a opção “Este plug-in fará contribuições para a UI” e, caso esteja marcada a opção Sim para a pergunta “Deseja criar um aplicativo de cliente rich?” você deve alterar para Não, pois este plugin é “interno”, não contendo nenhuma parte relativa à interface com o usuário.
Figura 01: Opções de criação do plugin de acesso a dados
Após a tela mostrada na figura 01, clique sobre o botão Concluir.
Conectando-se ao banco de dados
Para se conectar ao banco de dados existem alguns procedimentos a serem seguidos. O primeiro deles é colocar o driver jdbc do banco de dados no classpath de seu plugin.
NOTA: Às vezes é necessário adicionar o driver no plugin principal, ao invés de adicioná-lo ao plugin de acesso a dados somente.
Neste artigo, estaremos utilizando o servidor de banco de dados PostgreSQL, e portanto iremos mostrar agora como adicionar o driver jdbc deste banco de dados à sua aplicação.
Para adicionar o driver de acesso do banco de dados escolhido ao classpath de seu plugin de acesso a dados, proceda como mostrado abaixo:
Clique com o botão direito do mouse sobre o nome do projeto e selecione a opção Nova -> Pasta.
Figura 02: Criando uma nova sub-pasta no projeto.
Surgirá uma tela solicitando o nome da nova pasta a ser criada. Digite lib.
Figura 03: Criação de nova pasta
Após a criação desta nova pasta em nosso projeto, vamos adicionar o .jar do driver de acesso ao banco de dados dentro dela, e fazer com que seja adicionada ao classpath da aplicação.
Para adicionar o driver jdbc para o banco de dados PostgreSQL, proceda como mostrado abaixo:
Clique com o botão direito do mouse sobre a pasta recém-criada lib. No menu que surge, escolha a opção Importar.
Figura 04: Importando driver de acesso jdbc
Surgirá uma tela, solicitando o tipo de importação desejado. Selecione a opção Geral -> Sistema de Arquivos, conforme mostrado na figura 05 abaixo:
Figura 05: Selecione a opção Sistema de Arquivos para a importação do driver
Em seguida clique sobre o botão Avançar. Na tela mostrada agora, é necessário que você informe a localização do arquivo .jar a ser importado.
Figura 06: Selecionando o .jar do driver
Após especificar a localização do .jar contendo o driver de acesso jdbc ao banco de dados de sua preferência, clique sobre o botão Concluir.
A primeira parte está completa. Agora, vamos adicionar o .jar ao classpath de nosso plugin, para que seja encontrado pela JVM durante a execução de nosso programa.
Abra o arquivo MANIFEST.MF, localizado na pasta META-INF do projeto criado. Na EditorArea do Eclipse irá surgir a tela de configurações do plugin. Mude para a aba Tempo de Execução, conforme mostrado na figura 07:
Figura 07: Tela de preferências do plugin
Na seção Caminho de Classe, clique sobre o botão Incluir. Surgirá uma pequena tela solicitando o arquivo a ser incluído no classpath da aplicação. Selecione o driver JDBC que adicionamos na etapa anterior, que está localizado dentro da pasta lib de nosso projeto.
Figura 08: Adicionando o driver JDBC ao classpath do plugin
Pronto. O driver de acesso JDBC foi corretamente adicionado ao classpath do nosso novo plugin, e estará disponível para uso durante a execução da aplicação.
Vamos agora adicionar este plugin como uma dependência de nosso plugin principal. Assim, quando executarmos nossa aplicação de vendas, o plugin de acesso a dados - e conseqüentemente todas as suas classes públicas – estarão disponíveis no classpath do programa.
Para fazer isso, abra o arquivo MANIFEST.MF do plugin principal, localizado também numa subpasta de nome META-INF.
Após abrir o arquivo, vá até a aba Dependências, como mostrado na figura 09 abaixo:
Figura 09: Adicionando o plugin de acesso a dados como dependência da aplicação
Na seção Plug-ins Exigidos, clique sobre o botão Incluir e, na tela que surge, localize o recém-criado plugin de acesso a dados br.com.devmedia.DatabaseAccess.
Figura 10: Localizando plugin de acesso a dados
Pronto. Agora podemos partir para a criação das classes de nosso plugin de acesso a dados, que será nossa terceira camada, a “camada de baixo” da nossa aplicação de vendas.
Clique com o botão direito do mouse sobre o pacote br.com.devmedia.databaseaccess que está localizado dentro da pasta src de nosso plugin de acesso a dados. No menu que surge, selecione a opção Nova -> Classe.
Especifique o nome da classe como DbAccess, conforme ilustrado pela figura 11:
Figura 11: Criação da classe de acesso a dados
Esta classe servirá de intermediário entre os nossos DAOs e as chamadas “baixo nível” da API JDBC. Através desta classe poderemos prover suporte a vários servidores de banco de dados diferentes em nossa aplicação, entre outras vantagens.
Não é objetivo desta série de artigos mostrar como acessar banco de dados através de JDBC, mas sim o uso da plataforma RCP do eclipse. Deste modo, a classe DbAccess que vamos apresentar agora não será explicada em maiores detalhes neste momento. Prometo, entretanto, um artigo posterior, focado exclusivamente neste aspecto de nosso sistema.
Por hora, vamos apenas aceitar que a classe mostrada na listagem 02 é estável, pois é uma versão reduzida de uma outra classe maior que está atualmente em ambiente de produção e atende às nossas necessidades para este projeto.
Classe de acesso a dados:
package br.com.devmedia.databaseaccess;
import java.sql.*;
import java.util.ArrayList;
public class DbAccess
{
private Connection connection;
private String errorMessage, errorString;
public DbAccess (int dbtipo, String dsource, String dbase, String dbuser, String dbpass)
{
try
{
switch (dbtipo)
{
case 0: // PostgreSQL
Class.forName("org.postgresql.Driver");
connection = DriverManager.getConnection("jdbc:postgresql://" + dsource + "/" + dbase + "?user=" + dbuser + "&password=" + dbpass);
break;
case 1: // MySQL
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://" + dsource + "/" + dbase + "?user=" + dbuser + "&password=" + dbpass);
break;
case 2: // MS SQL Server
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
connection = DriverManager.getConnection("jdbc:microsoft:sqlserver://" + dsource + "/" + dbase + ";user=" + dbuser + ";password=" + dbpass);
break;
case 3: // IBM DB2
Class.forName("com.ibm.db2.jcc.DB2Driver");
connection = DriverManager.getConnection("jdbc:db2://" + dsource + "/" + dbase + ";user=" + dbuser + ";password=" + dbpass);
break;
case 4: // Oracle
Class.forName ("oracle.jdbc.driver.OracleDriver");
connection = DriverManager.getConnection("jdbc:oracle:oci8:@" + dsource + ":" + dbase, dbuser, dbpass);
break;
case 5: // Sybase
Class.forName ("com.ddtek.jdbc.sybase.SybaseDriver");
connection = DriverManager.getConnection("jdbc:datadirect:sybase://" + dsource + ";DatabaseName=" + dbase, dbuser, dbpass);
break;
case 6: // Interbase / Firebird
Class.forName ("org.firebirdsql.jdbc.FBDriver");
connection = DriverManager.getConnection("jdbc:firebirdsql:" + dsource + "/" + dbase, dbuser, dbpass);
break;
}
}
catch (ClassNotFoundException cnfe)
{
cnfe.printStackTrace ();
errorMessage = cnfe.getLocalizedMessage();
errorString = cnfe.toString();
}
catch (SQLException x)
{
x.printStackTrace ();
errorMessage = x.getLocalizedMessage();
errorString = x.toString();
}
}
public String getErrorMessage () { return errorMessage; }
public String getErrorString () { return errorString; }
public Connection getConnection () { return connection; }
public boolean beginTransaction()
{
try
{
connection.setAutoCommit(false);
return true;
}
catch (SQLException se)
{
errorMessage = se.getLocalizedMessage();
errorString = se.toString();
return false;
}
}
public boolean commmitTransaction()
{
try
{
connection.commit();
return true;
}
catch (SQLException se)
{
errorMessage = se.getLocalizedMessage();
errorString = se.toString();
return false;
}
}
public boolean rollbackTransaction()
{
try
{
connection.rollback();
return true;
}
catch (SQLException se)
{
errorMessage = se.getLocalizedMessage();
errorString = se.toString();
return false;
}
}
public boolean runSQL (String sql)
{
try
{
Statement stm = connection.createStatement ();
stm.execute (sql);
stm.close ();
return true;
}
catch (SQLException x)
{
errorMessage = x.getLocalizedMessage ();
errorString = x.toString ();
return false;
}
}
public int runSQLQueryInt (String sql)
{
try
{
Statement stm = connection.createStatement ();
ResultSet rst = stm.executeQuery (sql);
int ret = 0;
if (rst.next ()) ret = rst.getInt (1);
rst.close ();
stm.close ();
return ret;
}
catch (SQLException x)
{
errorMessage = x.getLocalizedMessage ();
errorString = x.toString ();
return -1;
}
}
public double runSQLQueryDbl (String sql)
{
try
{
Statement stm = connection.createStatement ();
ResultSet rst = stm.executeQuery (sql);
double ret = 0.0;
if (rst.next ()) ret = rst.getDouble (1);
rst.close ();
stm.close ();
return ret;
}
catch (SQLException x)
{
errorMessage = x.getLocalizedMessage ();
errorString = x.toString ();
return 0.0;
}
}
public String runSQLQueryStr (String sql)
{
try
{
Statement stm = connection.createStatement ();
ResultSet rst = stm.executeQuery (sql);
String ret = "";
if (rst.next ()) ret = rst.getString (1);
rst.close ();
stm.close ();
return ret;
}
catch (SQLException x)
{
errorMessage = x.getLocalizedMessage ();
errorString = x.toString ();
return "";
}
}
public ArrayList<ArrayList<Object>> getReader (String sql)
{
try
{
Statement stm = connection.createStatement ();
ResultSet rst = stm.executeQuery (sql);
ArrayList<ArrayList<Object>> arl;
arl = new ArrayList<ArrayList<Object>> (1);
while (rst.next ())
{
ArrayList<Object> tmp = new ArrayList<Object> (rst.getMetaData ().getColumnCount ());
for (int i = 1; i <= rst.getMetaData ().getColumnCount (); i++)
tmp.add (rst.getObject (i));
arl.add (tmp);
}
return arl;
}
catch (SQLException x)
{
errorMessage = x.getLocalizedMessage ();
errorString = x.toString ();
return null;
}
}
}
Listagem 02: Código da classe DbAccess
Com essa classe servindo de intermediária, nosso código de acesso a dados fica mais simples e num nível mais alto. Suponha por exemplo que você quer apagar o cliente de código 100;
public void apagaCliente ()
{
DbAccess db = new DbAccess (0, "localhost", "bancodados", "usuario", "senha");
if (!db.runSQL ("DELETE FROM Clientes WHERE codigo = 100"))
{
// Exibe uma msg de erro dizendo porque nao conseguiu remover
System.out.println ("Erro ao executar comando SQL : " + db.getErrorMessage());
}
}
Listagem 03: Exemplo de uso da classe DbAccess
Nada mais de lidar com abertura e fechamento de conexões, statements e NullPointerExceptions. Simplesmente execute seus comandos SQL e saiba se tudo correu bem ou não, com mensagens de erro formatadas.
Obviamente os parâmetros para o construtor não serão passados como no exemplo da listagem 03, em strings fixas no código, mas através de variáveis cujos valores poderão ser lidos e editados pelo usuário de sua aplicação.
Neste artigo, mostramos os passos necessários à construção de um plugin de acesso a dados. Mostramos também como adicionar JARs externos ao classpath de sua aplicação RCP e como configurar interdependências entre os plugins de nosso aplicativo de vendas.
No próximo artigo da série, iremos criar o modelo do nosso negócio, as classes que compõem o domínio do nosso problema. Até a próxima.