Agora na prática

Chega de introdução, chegou a hora de ver a implementação e alguns exemplos. Veremos como o pacote pode ser usado para executar operações básicas select/insert/update/delete e também para formatar e validar dados. A Figura 1 apresenta o diagrama de classes da UML para as classes responsáveis por facilitar o uso do JDBC.

 

img1java.JPG

Figura 1. Diagrama UML das classes básicas.

 

A classe Database faz duas coisas: mantém um mapa de Formatter e implementa métodos para selecionar, inserir, atualizar e apagar linhas. Também há um método validate(), sobre o qual falarei brevemente. Formatter é uma classe abstrata que define três métodos: parse(), format() e validate(). Como mencionado antes, a visão de dados dos usuários está na forma de strings, assim os formatters são responsáveis por converter tipos de banco de dados de/para strings. Sempre é possível a inserção de dados inválidos, por exemplo, caracteres não numéricos em um campo numérico, data formatada incorretamente ou strings que não casam com um padrão. Para cada um dos dados de coluna esperados, existe uma subclasse Formatter.

Vejamos um exemplo simples de seleção de dados na Listagem 1.

 

   Connection con = getConnection();
   myDB = new Database(false);
   myDB.loadDefaultFormatters(con, "roles");
   String[] params = {"roles.id", "99"};
   Results rs = myDB.select(con, "select id,name,role from roles where id = ?", params, 10);
   System.out.println("Name is " + rs.getString(0, 1));
   System.out.println("Password is " + rs.getString(0, "roles.role"));

Listagem 1. Exemplo de seleção de dados.

 

A primeira linha obtém uma conexão com o banco de dados. Assumi que existem meios para obter a conexão, a maioria das aplicações o faz. Normalmente, provém de uma fonte de dados predefinida ou de uma chamada simples a DriverManager. O construtor do banco de dados possui um argumento booleano que diz à instância para fechar automaticamente a conexão depois de uma operação. Como provavelmente você deve saber, fechar conexões é um processo crítico e propenso a erros, portanto isto pode ser automatizado caso assim o prefira. O método loadDefaultFormatters() é um modo prático de carregar o formatter apropriado para uma determinada tabela. Este depende do método getColumnClass() em ResultSetMetaData. Possivelmente, isto poderia não funcionar, mas em minha experiência, este método parece funcionar com todos os bancos de dados que testei, inclusive com o Access, SQL Server, MySQL, Oracle e DB2. Alternativamente, podemos configurar o formatter para colunas específicas por nome. Provavelmente você desejará fazer isto em produção, já que o formatter padrão não possui muitas restrições de validação.

Este exemplo é de um simples select. Perceba que o SQL não é nada além do que você iria prover ao PreparedStatement. Na realidade, no fundo é exatamente isto, permitindo a utilização de qualquer SQL executado pelo seu banco de dados. O argumento params é um array de Strings que contem pares de nome de coluna e valor. A ordem dos parâmetros equivale a um HashMap ordenado, porém mais fácil de criar e inicializar. O nome das colunas é necessário, pois desta maneira a análise gramatical dos valores dos parâmetros poderá ser realizada corretamente.

O método select() retorna um objeto Results que equivale a um ResultSet, mas formata os resultados baseado em nomes de colunas. Se o nome de coluna não estiver disponível, tentará utilizar o tipo da coluna. Valores para uma determinada linha ou coluna podem ser recuperados em qualquer ordem. Perceba que o nome de coluna ou o numero da coluna pode ser especificado. O método select() levantará uma SQLException se algo der errado.

Agora analisaremos um exemplo de inserção na Listagem 2.

 

   String[] values = {
      "roles.id", "99",
      "roles.name", "ahab",
      "roles.role", "captain"
   };
   String[] errors = myDB.validate(values);
   if (errors != null) {
      for (int i=0; i<ERRORS.LENGTH; i+="1)" {
         System.out.println(values[2*i] + ": " + errors[i]);
      }
   } else {
      myDB.insertRow(con, values);
   }

Listagem 2. Exemplo de inserção.

 

Este exemplo mostra como a validação pode ser usada. A classe Database possui um método validate() que aceita um array de valores a ser validado e retorna mensagens de erro caso necessário. Cada valor é verificado contra seu formatter correspondente. Se qualquer valor não for válido, o array conterá as mensagens de erro correspondentes. Valores válidos contêm um string vazio (não-nulo). Um valor de retorno nulo indica que não houve erros de validação.

O método insertRow() aceita uma conexão e os valores a inserir. Os valores são formatados como um array de Strings que contêm pares de nome de coluna e valor. Um SQLException será levantado se algo der errado.

Agora analisaremos um exemplo de atualização na Listagem 3.

 

   String[] values = {
      "roles.id", "99",
      "roles.name", "ahab",
      "roles.role", "fool"
   };
   String[] params = {"roles.id", "99"};
   int n = myDB.updateRows(con, values, "id=?", params);

Listagem 3. Exemplo de atualização.

 

O terceiro parâmetro na última linha da Listagem 3 representa os critérios para uma cláusula opcional WHERE. Este parâmetro pode ser nulo, e neste caso, todas as linhas serão atualizadas.

E finalmente, analisemos um exemplo de delete na Listagem 4. Perceba que a validação pode (e deve) ser feita também com parâmetros.

 

   String[] params = {"roles.id", "99"};
   String[] errors = myDB.validate(params);
   if (errors != null) {
      for (int i=0; i<VALUES.LENGTH; i+="1)" {
         System.out.println(values[2*i] + ": " + errors[i]);
      }
   } else {
      int n = myDB.deleteRows(con, "roles", "id=?", params);
   }

Listagem 4. Exemplo de remoção.

Conclusão

Minha meta não foi substituir qualquer framework de banco de dados. Foi simplesmente um esforço para resolver um conjunto de problemas com pouco código. É possível utilizar estas classes como base para um framework mais completo, que faz cache de dados e se integra diretamente com JSP/servlets. Não seria difícil de construir taglibs personalizadas ou componentes para outros frameworks web.

Claro que esta solução não é perfeita. Configurar formatters para cada coluna de cada tabela que você planeja acessar pode ser muito trabalhoso. Felizmente, só precisamos fazer isto apenas uma vez. Idealmente, todas as informações deveriam ser definidas quando a tabela fosse definida, mas isso normalmente não acontece. As informações poderiam ser carregadas de um arquivo XML ou poderiam ser armazenadas no próprio banco de dados; isso é o que PowerBuilder faz.

É bastante simples? Você decide. É possível que seja simples demais; alguns poderiam argumentar que não é coisa para o horário nobre. Isso está certo. Em minha opinião, é mais fácil acrescentar funcionalidade a uma solução simples em lugar de simplificar uma solução complexa.

 

Leia a parte 1 desta matéria em: www.devmedia.com.br/visualizacomponente.aspx?comp=1274&site=6

Leia a parte 2 desta matéria em: www.devmedia.com.br/visualizacomponente.aspx?comp=1307&site=6