Hospedagem Profissional

Hospedagem Profissional
Clique aqui e ganhe US$ 10,00 para testar durante 1 mês a melhor hospedagem: Digital Ocean!

quarta-feira, 22 de outubro de 2008

Retirando o SQL do seu código Java

Por Paulo Silveira 


Aprenda a usar o PreparedStatement para retirar todo código SQL do seu código java, e separá-lo em um arquivo de configuração. Além de uma boa prática, você não precisará mais recompilar o seu código quando necessário mudar pouca coisa de uma SQL.

Introdução

Uma das boas práticas de programação, não só em java, é diminuir ao máximo o uso de Strings literais no seu código fonte, técnica conhecida como "hardcoding". Um "antipadrão" de programação.

Porque isto é ruim? Bem, primeiro que você fica dependendo de recompilar o código se precisar mudar alguma coisa. Segundo que fica difícil você achar um mispelling (erro na hora de digitar) de uma String literal, e terceiro que é terrível ler Strings e magic numbers no código de uma pessoa.

O que é Magic Number?

1 if (this.codigo == 47) {
2 // faca algo
3 }


O número 47 não representa nada para ninguém, e esse código fica difícil de ser lido. Imagine agora com uma constante:

1 if (this.codigo == DATABASE_ACCESS_ERROR) {
2 // faca algo
3 }


O mesmo acontece para Strings literais. Claro que em java você utilizaria Exceptions ao invés de código de erros, isto foi apenas um exemplo.

Neste tutorial, aprenderemos a tirar as strings SQLs "hardcodadas" no seu .java, de maneira elegante e eficiente. Para isto, é necessário você ter um conhecimento básico de JDBC e conhecer ajava.util.Properties (ou leia a API!).

Uma vantagem enorme de retirar o seu SQL do .java, é que assim você torna a sua aplicação quase que indepedente de banco de dados (se você tomar bastante cuidado, vira 100% independente). Voce pode mudar em um arquivo de configuracao as SQLs, por exemplo, de TOP para LIMITm diferença clássica entre alguns banco de dados!



java.sql.PreparedStatement é uma interface que extende a interface Statement. A grande maioria dos drivers JDBCs atuais suportam a utilização desta interface. O que ela faz? Bem, vamos dar um exemplo concreto, para irmos explicando:

1 Connection conn = DriverManager.getConnection("jdbc:banco...");
2
3 PreparedStatement pstmt = con.prepareStatement("UPDATE EMPLOYEES SET SALARY = ? WHERE ID = ?");
4
5 pstmt.setBigDecimal(1153833.00)
6 pstmt.setInt(2110592)


Este é o exemplo que está na própria API. O que ele faz? Primeiro ele pega uma conexão com o banco de dados, exatamente como sempre fizemos.

Depois, ele devolve um PreparedStatement a partir de um SQL meio estranho: ele possui pontos de interrogação onde queremos reutilizar a SQL query. Para substituir esses pontos de interrogação, basta usar um dos vários métodos setters que a interface PreparedStatement lhe oferece. Lembre-se que o setter recebe um inteiro, indicando qual ponto de interrogação será utilizado: o primeiro vale 1, diferente de indices de arrays que começam com 0 em java.

Chamado o setter, o driver JDBC vai dar um jeito de traduzir aquele objeto/valor primitivo para dentro da SQL. Se você colocar uma String, não precisa por as aspas!!! Porque? Pois o driver JDBC cuida disso:

1 PreparedStatement pstmt = con.prepareStatement("select * from usuario where name = ?");
2
3 // não precisa de escape no '
4 pstmt.setString(1,"Joãozinho D'a Silva");


Uma maneira muito comum de utilizar o PreparedStatement, é usar o método setObject. Este método vai fazer vários instanceof e descobrir como deve colocar esse valor na sua SQL. Caso ele não descubra, ele vai chamar o toString deste método. Continuando o primeiro caso:

1 pstmt.setObject(1new Long(5000));
2 pstmt.setObject(2new Integet(333));


Se você tentar setar um índice que não exista vai receber uma RuntimeException. Por exemplo, no nosso caso, setObject(3,...) iria resultar em uma RuntimeException, indicando que não existe o 3o ponto de interrogação na SQL dada!

Para executar o PreparedStatement, é idêntnico ao Statement. Lembre-se de "setar" tudo antes de executá-lo, ou você receberá uma exceção:

1 ResultSet rs = pstmt.executeQuery()// sem passar argumento!


O que mais o PreparedStatement é responsável por fazer?
  • Fazer o escape das aspas, aspas simples, e do próprio caractere de escape - isso é muito útil;
  • Traduzir muitos objetos para o formato String correspondente com o do banco de dados, como o java.util.Date por exemplo, e o java.io.InputStream, que eu considero o mais útil de todos: fica muito fácil utilizar um campo Blob com um PreparedStatement.

    Antes que alguém pergunte, o driver de JDBC para o mysql tem sim, suporte a PreparedStatement. Aliás, o GUJ usa e abusa deste recurso, inclusive para guardar as imagens no banco de dados em BLOBs.

    Aqui você já tem uma idéia legal de como aproveitar o PreparedStatement, mas sugiro que você conheça a api dele,clicando aqui.




    Após aprendermos a usar o PreparedStatement, vamos fazer algo para retira-los do seu .java. Para isso, vamos passar todas as Strings que representam uma SQL para um arquivo de Properties, algo do tipo:

    usuario.selectByName=select * from usuario where name = ?
    usuario.selectByEmail=select * from usuario where email = ?


    Então, ao invés de criarmos o PreparedStatement a partir de um String literal, nós pegamos ele a partir deste arquivo Properties. Um esboço:

    1 Properties queries = new Properties();
    2 queries.load("arquivoMysql.txt");


    Na hora de criar um PreparedStatement, você faz assim:

    1 PreparedStatement ps = connection.prepareStatement(queries.getProperty(USUARIO_SELECT_BY_NAME));


    Onde USUARIO_SELECT_BY_NAME é uma constante: uma String static final e pública. Porque isso? evitar o hardcoding e erros de execução.

    Com isso, você não terá mais nenhum SQL dentro do seu .java, o que é um grande passo. Vamos melhorar nosso esboço de como controlar essa properties que contém os SQLs.



    Vamos criar aqui uma classe (esboço novamente) para controlar essa properties que tem as SQLs. Não podemos ficar lendo do arquivo texto toda vez que pegarmos uma SQL, precisamos de uma classe (instância dela) para fazer isso.

    Você não precisa saber o que é um Singleton. É basicamente uma solução para você não fazer uma classe que tenha tudo static, desta maneira, você perderia a orientação a objetos.

    01 public class QueryManager {
    02
    03 protected Properties queries;
    04
    05 // nao deveria estar hardcodado:
    06 protected static queryManager = new Querymanager("arquivoMysql.txt");
    07
    08 private QueryManager(String arquivo) {
    09 queries = new Properties();
    10 // le as sqls e guarda!
    11 queries.load(new FileInputStream(arquivo));
    12 }
    13
    14 ...
    15
    16 }

    Até aqui temos o padrão singleton implementado, mas vamos adicionar o método que retorna um PreparedStatement, dado uma conexão e o nome do statement que você quer!

    1
    2 public PreparedStatement getPreparedStatement(Connection conn, String queryNamethrows SQLException {
    3 String query = this.queries.getProperty(queryname);
    4 return conn.prepareStatement(query);
    5 }


    Cuidado: este método pode dar NullPointerException no caso da query requerida não existir no arquivo de configuração!

    Para utilizar esta classe:

    1 QueryManager manager = QueryManager.getQueryManager();
    2
    3 PreparedStatement ps = manager.getPreparedStatement(conn, USUARIO_SELECT_BY_NAME);
    4 ps.setString(1"Joaozinho")// poderia ser setObject, recomendo!
    5 ResultSet rs = ps.executeQuery();


    Simples não? Não tenha medo do singleton, a única coisa que ele faz é impedir que exista mais de uma instância desta classe por ClassLoader.


    Este tutorial foi realizado devido a muitos pedidos de como retirar o SQL do codigo java. Você pode refinar o processo, utilizando XML no caso que você tenha uma hierarquia entre as SQLs. O problema seria escrever um parser apenas para este arquivo de configurações.

    Você ainda pode melhorar essa solução: utilizando alguma biblioteca/ferramenta que possibilite o "mapeamento objeto relacional". Usar uma ConnectioPool também é muito útil!

    Espero que tenha ficado claro a melhoria que você ganha a partir do momento que para de lidar com literais: maior reuso do código, melhor leitura, maior adaptação e facilidade de reescrita. E é claro: quem não gosta de um arquivinho de configurações :).


  • Fonte : http://www.guj.com.br/java.tutorial.artigo.115.1.guj