quinta-feira, 31 de janeiro de 2013

Vivendo e aprendendo

Olá. No POST anterior, prometi falar um pouco sobre o Polimorfismo, porém ficará para uma próxima postagem, afinal, quando a gente está com "o fio da meada" na cabeça, é uma ótima oportunidade para transferir isso para um papel ou para um próprio blog! :) Desta forma então, hoje irei abordar um assunto de extrema importância: como trabalhar com a API JDBC do Java, mais especificamente, boas práticas em como utilizar tais recursos. No final do "artigo", há um estudo de caso. Provavelmente você já precisou fazer uma interface de conexão entre a sua aplicação e uma base de dados qualquer. Basicamente, não há mistério algum nisso. Um típico programa que se conecta com um banco de dados qualquer e executa alguns comandos simples se parece com isso:
1:  public class Foo {  
2:  public static void main(String[] args) {  
3:    Class.forName("oracle.jdbc.OracleDriver");    
4:    Connection c = DriverManager.getConnection(url, usuario, senha);  
5:    StringBuilder sql = new StringBuilder();  
6:    sql.append("select * from bar");  
7:    PreparedStatement stmt = c.preparedStatement(sql.toString());  
8:    try {  
9:     ResultSet rs = stmt.executeQuery();  
10:     while (rs.next) {  
11:      //faz bastante coisas aqui  
12:     }  
13:    } catch(SQLException e) {  
14:     e.printStackTrace();  
15:    }  
16:  }  
17:  }  
Como vimos, não há mistério nenhum nisso. Só que quando sua aplicação começar a "tomar forma", podemos tomar algumas ações para que seu código esteja protegido, que seja fácil de reutilizar e é claro, que não use recursos de forma aleatória e desnecessária.
Dica 1: Considere ter uma classe que lide com as particularidades de sua conexão. Conforme vemos no exemplo acima, expomos demais os detalhes de nosso banco de dados, como a URL de conexão, usuário e senha por exemplo, além de existir um complexo código para obter a conexão. O ideal seria criar uma classe ConnectionFactory que faria todo este trabalho para você, vamos ver como ficaria?
1:  public class ConnectionFactory {  
2:    private Connection conn;  
3:    private String driver;  
4:    private String usuario;  
5:    private String senha;  
6:    private String url;  
7:    public static ConnectionFactory conexao;  
8:    private ConnectionFactory() {  
9:     Class.forName(driver);  
10:     conn = DriverManager.getConnection(url,usuario,senha);     
11:     init();  
12:    }  
13:  public static getInstance() {  
14:     if (conexao == null) {  
15:       conexao = new ConnectionFactory();  
16:     }  
17:     return conexao;  
18:    }  
19:    private void init() {  
20:     //considere aqui ler um arquivo de propriedades que alimentará os 
        //campos necessários para a conexão  
21:    }  
22:  }  
E nossa classe principal ficaria assim:
1:  public static void main(String[] args) {   
2:    Connection c = ConnectionFactory.getInstance();  
3:    //restante do código omitido  
4:   }   
A propósito, nossa classe de conexão implementa um padrão de projeto chamado Singleton. Sugiro uma lida sobre Design Patterns.
Dica 2: Fique atento aos recursos de conexão! Como? Não podemos deixar recursos de conexão "largados" no servidor de banco de dados. Isso consome recursos do servidor, além de ocupar conexões desnecessárias. Se o limite de conexões simultâneas no seu servidor de banco de dados for relativamente baixo, isso pode ser um problema. E dos sérios. Até descobrir que o problema é na sua aplicação porque ela não faz a devida liberação de recursos, poderemos gastar horas e horas tentando achar o problema. (A propósito, quando as pessoas descobrirem isso, é melhor você correr - ou mudar de cidade). Nosso código ainda NÃO ESTÁ FAZENDO ISSO. Vamos ver como funciona?
1:  public static void main(String[] args) {   
2:  //código omitido  
3:    try {   
4:     ResultSet rs = stmt.executeQuery();   
5:     while (rs.next) {   
6:     //faz bastante coisas aqui   
7:     }   
8:    } catch(SQLException e) {   
9:     e.printStackTrace();   
10:    } finally {  
11:      rs.close();  
12:      stmt.close();  
13:      c.close();   
14:    }  
15:   }   
16:   }  
Veja o bloco finally em nosso código. Ele faz a liberação dos recursos sempre que o processamento terminar, nos livrando de vários problemas no futuro. Apesar de estarmos nos preocupando em fechar os recursos de conexão, ainda não estamos fazendo da forma correta. Mas como assim? O que está faltando? O bloco "finally" não garante que ele sempre será executado? Correto, mas não necessariamente tudo o que tiver no bloco finally será executado com sucesso!. Imagine a seguinte situação, se por algum motivo, na linha 11, rs.close() gere uma exceção. stmt.close() e c.close() jamais serão executados! Pense bem, a aplicação será abortada pois aconteceu alguma coisa estranha na chamada de rs.close(). Mas como então garantir de uma vez por todas que, mesmo que haja exceção em alguns desses comandos, o erro de um não influencie nos outros processamentos, evitando assim de fechar os recursos que já foram utilizados? A solução é claro, você já sabe: devemos executar cada close() em um bloco try...catch! Desta forma:
1:  public static void main(String[] args) {   
2:     //código omitido  
3:    try {   
4:        ResultSet rs = stmt.executeQuery();   
5:        while (rs.next) {   
6:           //faz bastante coisas aqui   
7:        }   
8:    } catch(SQLException e) {   
9:        e.printStackTrace();   
10:    } finally {  
11:        try {  
12:           rs.close();  
13:        } catch(SQLException e) {  
14:           //logar exceção  
15:        }  
16:        try {   
17:           stmt.close();   
18:        } catch(SQLException e) {  
19:           //logar exceção     
20:        }  
21:        try{  
22:           c.close();   
23:        } catch(SQLException e) {  
24:        //logar exceção  
25:        }  
26:    }  
27:   }  
Agora sim! Mesmo que aconteça alguma coisa errada com rs.close(), stmt.close() e c.close() ainda serão executados. O mesmo vale para stmt.close() ou c.close().
Dica 3: Tenha CERTEZA que os cursores criados durante o processamento de suas SQL's foram corretamente encerrados! Ahn? Como assim? Para cada chamada que o ResultSet recebe de stmt.executeQuery(), é criado um cursor implícito no servidor de banco de dados. Só que há um limite de cursores que podem ficar abertos. Quando esse limite é atingido, o servidor lança um erro dizendo que não pode mais lidar com tantos cursores abertos, te dá uma mensagem feia de erro e sua aplicação é interrompida. Esse erro é muito complicado de se resolver, dependendo do tamanho do seu projeto, pois você tem que se certificar em todos os lugares que faz conexão/manipulação com o servidor de banco de dados se seus recursos estão sendo devidamente encerrados. Isso tudo é muito legal, mas COMO garantir que os cursores serão devidamente encerrados? E é aí que entra um pouco de experiência da linguagem. O comando abaixo NÃO ENCERRA O CURSOR:
11:    try {  
12:           rs.close();  
13:        } catch(SQLException e) {  
14:           //logar exceção  
15:        } 
Mas isso aqui encerra:
11:    try {  
12:           rs.getStatement().close();  
13:        } catch(SQLException e) {  
14:           //logar exceção  
15:        } 
Veja a jogada! rs.getStatement().close(). É desta forma que você garante o encerramento dos cursores. Se formos analisar pela lógica da coisa, faz todo sentido. Quando chamamos rs.close(), estamos dizendo para o Java que não queremos mais trabalhar com o conjunto de resultados retornados pelo ResultSet. Ao fazer rs.getStatement().close(), estamos dizendo que queremos que aquela instrução executada seja "finalizada", ou seja, o cursor responsável por trazer a TUPLA para o ResultSet não necessita mais ficar aberta. Deu para entender? :) Estudo de caso Como eu mencionei no início do "artigo", vou compartilhar um estudo de caso que enfrentei por dois dias (sim, dois dias). Estou trabalhando com Oracle em um processo que envolve vários registros ao mesmo tempo, além de existirem conexões com duas bases de dados simultâneas. Ao trazer os registros para a memória, o Oracle me apresentava o seguinte erro: ORA-01000: maximum open cursors exceeded Ora, mas eu estava (aparentemente) fechando todos os meus recursos, como exemplifiquei aqui. Porém eu não tinha me tocado do rs.getStatement().close, sempre insistindo em apenas fechar o ResultSet. Foi só aplicar uma mudança de lógica do negócio e chamar rs.getStatement().close e pronto! Problema resolvido. Mas fiquei horas e horas trabalhando nisso, quando poderia estar implementando outras partes de minha aplicação. Então aí vai uma dica, cuidado! Não perca tempo como eu e já faça seu código livre desse erro maldito! :) Ufa! Por hoje é só, acabei me estendendo mais do que deveria, mas era isso que queria apresentar aqui hoje. Até a próxima!

Nenhum comentário:

Postar um comentário