Artigo Java Magazine 26 - Persistência Turbinada II

Artigo publicado pela Java Magazine edição 26.

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

Clique aqui para ler essa revista em PDF.

Persistência Turbinada II

O lado Negro da força JDBC

 

Explorando as técnicas de mais baixo nível da JDBC – para desenvolvedores dispostos a ganhar desempenho a (quase) qualquer preço.

 

 

Na edição anterior, apresentamos as técnicas de programação JDBC, estendendo o padrão de projeto DAO de forma a obter os mesmos benefícios de desempenho de ferramentas O/R – mas mantendo as vantagens de um estilo de programação de “baixo nível”, acessando o bando de dados diretamente.

Este artigo dá seqüência a nossa série sobre JDBC e persistência em geral, com uma variedade de técnicas e dicas mais curtas que não renderiam sozinhas um artigo inteiro. Um ponto em comum dessas dicas é que vamos explorar “truques sujos” como extensões de JDBC e de SQL, que muitas vezes são inevitáveis em nome do desempenho máximo.

Uma observação; muitas dicas deste artigo não são garantidamente independentes de banco de dados. Minha maior experiência é com o Oracle, mais, em muitos casos, diversos bancos suportam as mesmas técnicas apenas com diferenças nos detalhes (como sintaxes SQL diferentes).

Neste artigo, usamos “banco de dados” (ou “BD” ou “banco”) como termo mais simples para Sistema de Gerenciamento de Banco de Dados (SGBD).

 

Statements ou PreparedStatments?

É comum a dúvida de que tipo de statement JDBC utilizar: o simples Statement ou sua extensão, o PreparedStatement. Revisando, o PreparedStatement estende Statement de forma a resolver três problemas.

  • Simplifica a representação dos parâmetros: não é necessário envolver strings com ‘’ (aspas simples) nem converter datas na sintaxe aceita pelo banco de dados, não é preciso converter booleanos para ‘Y’/‘N’ ou 1/0, e assim por diante.
  • Aumenta o desempenho: a execução é mais eficiente porque o driver e/ ou banco de dados processam o texto SQL uma só vez, fazendo um cache da sua representação compilada. Além disso, quando uma consulta é executada, antes de ler os dados do resultado o driver também precisa receber o banco de dados os metadados sobre o resultset (nome, tipo e outras informações sobre cada coluna do cursor de resultados1). Com PreparedStatement, esses metadados só são transferidos na primeira execução; depois também ficam num cache do driver.

Suporta updates em batch2: o PreparedStatement permite executar updates repetitivos, que variam somente pelos parâmetros, com os métodos addBatch() e executeBatch(). (Note que estamos usando o termo “update” em minúsculas para significar qualquer alteração, seja via INSERT, UPDATE ou DELETE, no banco.)

Num capitulo disponível online do livro “Java Programming with Oracle JDBC”,

(oreilly. com/catalog /jorajdbc/chapter/ch 19.html), o autor Donald Bales apresenta um

benchmark comparando ambos os tipos de statements. Ele mostra que Statement é mais

rápido para um número pequeno de execuções de um INSERT (entre ~60 e ~120, variando conforme o driver). Para quantidades maiores de operações, PreparedStatement é até duas vezes mais rápido. Bales adverte contra o uso de PreparedStatement para um número pequeno de operações, mas eu discordo, pois os caches do banco de dados e do driver são de longo prazo: mesmo que você não execute um determinado INSERT cem vezes num loop, certamente irá executá-lo muito mais vezes ao longo de horas ou dias3.Prefiro usar PreparedStatement por default, sempre que não houver evidência de não ser a melhor opção - inclusive em casos onde a funcionalidade de Statement seja suficiente. Esta recomendação é especialmente

forte para bancos de dados de maior porte, que possuem otimizadores de consultas

mais poderosos. Nestes casos o custo de compilação das consultas é maior, pois envolve a análise de estatísticas das tabelas, geração de planos de execução complexos, paralelização de consultas etc.

A documentação da JDBC recomenda o  uso de Statement para consultas dinâmicas.

Um exemplo simples de consulta dinâmica é uma aplicação “cliente SQL” que

permite ao usuário digitar comandos SQL arbitrários. Esse caso extremo é OK para

Statement, mas e um caso raro. Para aplicações comuns, o mais usual é ter consultas

parcialmente dinâmicas: comandos SQL complexos, montados segundo condições que mudam a cada invocação, como no método a seguir:

 

Venda[] findVendas (long codltem, Data datalni,

Date dataFim, int status) {

StringBuffer sql = new StringBuffer(

“SELECT FROM VENDAS WHERE item=?”);

if(datalni!= null) sql.append(“ AND data >= ?”);

if(dataFim!= null) sql.append(“ AND data <= ?”);

if (status!= -1) sql.append(“ ANDstatus = 1”);

PreparedStatement stmt =

getConn().prepareStatement(sql.toString());

...

}

 

Com três trechos opcionais, o número de possíveis consultas distintas de findVendas()

sera 23 = 8. Isso ainda é muito pouco para nos preocuparmos; mas e se fossem 16

parâmetros opcionais, gerando até 65.536 consultas diferentes? O senso comum (e o

javadoc) nos diria que não é boa idéia usar PreparedStatement - para não encher o cache nem pagar os custos maiores de inicialização com consultas raramente repetidas.

Mas na prática, métodos desse tipo tendem a ter algumas combinações de parâmetros

muito mais freqüentes que outras. Além disso, os bancos de dados costumam ser

inteligentes para manter no cache as consultas executadas com mais freqüência Statement era bem mais útil nos primeiros tempos da JDBC, quando havia drivers que

nem implementavam PreparedStatement.

Por outro lado, se a sua aplicação tiver muitas consultas “extremamente dinâmicas”,

isso e mau sinal. Pode ser melhor mudar sua implementação de forma a ter

consultas mais fixas. Os maiores problemas de consultas dinâmicas estão na depuração

e no tuning. Quando alguma coisa funciona de forma errada ou muito lentamente, a

correção será mais difícil se cada execução gerar um comando SQL diferente. Uma alteração que corrige um caso pode estragar outro, sendo difícil testar todas as consultas

resultantes de cada permutação de muitos parâmetros opcionais.

Alguns desenvolvedores já repararam que certas consultas SQL executam com

eficiência maior com um Statement. Isso aconteceu comigo, em operações como:

 

  1. DELETE FROM WHERE data
  2. DELETE FROM VENDAS WHERE data < TO_DATE('01/03/2005')

O DELETE 1 era usado para limpar registros desnecessários de uma tabela extremamente grande (com dezenas de milhões de registros). O problema é que o 1 demorava no mínimo um minuto para executar, mesmo que " [...] continue lendo...

Artigos relacionados