Esse artigo faz parte da revista Java Magazine edição 15.

lass="subtitulo5" style="margin-right: 70.75pt;">Entity Beans no JBoss
Parte 2: Tuning da persistência

Obtendo o máximo de performance na interação entre o container e o banco de dados

Na primeira parte desta série apresentamos o mecanismo de persistência CMP/CMR do EJB. Vimos como ele nos permite enxergar os dados sob uma ótica orientada a objetos, deixando para o container EJB a tarefa de gerar os comandos SQL para recuperação e atualização desses dados em bancos relacionais. Mostramos também como o mapeamento entre objetos e tabelas pode ser customizado no JBoss.

Nesta segunda parte, veremos os recursos de tuning disponibilizados pelo JBoss, que permitem otimizar o container para diferentes necessidades de uma aplicação. E veremos como a programação de EJBs pode ser facilitada pelo uso de outra ferramenta livre, o XDoclet.

O objetivo das otimizações mostradas é minimizar o tráfego de rede entre o JBoss e o banco de dados. Para minimizar o tráfego de rede, deve-se diminuir a quantidade de comandos SQL enviados ao banco de dados, e reduzir a quantidade de dados retornada por cada comando. Em uma aplicação "tradicional", seria necessário modificar a lógica e os comandos SQL – algo demorado e arriscado (pela possibilidade de introduzir bugs no código). Em uma aplicação J2EE, podemos atuar apenas sobre a configuração do servidor e nos descritores de deployment da aplicação, sem modificar uma única linha de código Java, resultando em maior produtividade e menor risco.

Os princípios e estratégias utilizados podem ser aplicados para outros servidores de aplicações, mas os detalhes específicos de configuração e tuning mostrados aqui são específicos para o JBoss. Outros servidores possuirão seus próprios parâmetros e descritores específicos. 

Nota
Nos exemplos, foi usada a configuração padrão do JBoss, com o HSQLDB embutido. No entanto, os mesmos procedimentos se aplicariam a qualquer outro servidor de banco de dados, estando ele na mesma máquina ou numa estação independente. Bastaria utilizar os recursos do banco em questão para que ele exiba um log dos comandos SQL recebidos do servidor de aplicações.

Estudo de caso

Nosso estudo de caso para o tuning da persistência é a aplicação desenvolvida na primeira parte desta série. Trata-se de uma simples agenda de contatos que utiliza duas entidades: Contato e Categoria. O exemplo inclui também uma aplicação web, escrita segundo o modelo MVC, que permite fazer consultas e alterações sobre os dados da agenda. A Figura 1 ilustra as principais páginas dessa aplicação, para situar o leitor no nosso processo de tuning. A aplicação completa pode ser obtida do site da Java Magazine.

O que acontece quando o usuário requisita a primeira página, a listagem de categorias? Tudo inicia pelo link fornecido pela página inicial da aplicação, que provoca a execução do servlet controlador de contatos. O servlet requisita a relação de todas as categorias ao session bean de fachada, e o session bean invoca o método findAll() do entity bean Categoria. O retorno do método é uma coleção de referências locais a instâncias deste entity bean, e o session bean pede a cada referência o seu VO. Todos os VOs retornados são inseridos em uma nova coleção, que é devolvida ao servlet. O servlet então insere esta coleção como um atributo da requisição HTTP, que é encaminhada para a página JSP. Por fim, a página JSP formata a coleção em uma tabela HTML. A Figura 2 ilustra todo o processo.

Esta é a visão para o desenvolvedor das aplicações, mas o “Administrador do Servidor de Aplicações Java" (veja o quadro "Administração J2EE") necessita de uma visão mais detalhada, que inclua a interação entre o servidor de aplicações e o banco de dados. É fácil imaginar que o método findAll() provoca a execução de uma consulta SQL, mas será que esta consulta retorna todos os atributos da entidade? Ou será que os atributos são recuperados posteriormente, sob demanda (na execução dos métodos getXXX() correspondentes)?

Um mecanismo objeto-relacional qualquer irá atuar de forma semelhante a um DataControl do Windows ou outras ferramentas de acesso transparente ao banco: será executada uma versão da consulta que retorna apenas as chaves primárias dos registros desejados. Este conjunto de chaves é mantido em memória (ou em um arquivo temporário), e posteriormente os campos individuais de cada registro são recuperados, um a um, conforme a demanda. Isto gera o problema “N+1”, onde uma consulta qualquer gera N+1 acessos ao banco de dados, sendo “N” a quantidade de registros retornados. Entity beans BMP não têm como escapar desse problema mas, com entidades CMP, o container tem a capacidade de otimizar o acesso, utilizando recursos de carga antecipada e caches.

O primeiro passo é descobrir se a configuração padrão do servidor gera ou não o problema “N+1”. Para isso, siga as instruções do quadro "Exibindo comandos do HSQLDB", depois reinicie o JBoss, preferencialmente depois de já ter feito o deployment da aplicação de exemplo e inseridos alguns contatos e categorias.

 
Nota
Este estudo de caso foi realizado em uma instalação nova da versão 3.2.3 do JBoss. Outras versões podem utilizar configurações padrão distintas, gerando resultados um pouco diferentes dos apresentados; ou mesmo podem implementar algoritmos diferenciados de otimização de acesso ao banco e assim gerar seqüências diferentes de comandos SQL. Entretanto, a mecânica geral será essencialmente igual e, desde que seja utilizada uma versão da série 3.2.x, os mesmos parâmetros de tuning estarão disponíveis.

Ao executar a página de listagem de categorias, vemos no log do JBoss as seguintes entradas, geradas pelo HSQLDB (as entradas foram reduzidas e quebradas, para maior legibilidade): 

[STDOUT] 0:SELECT t0_c.id FROM CATEGORIA t0_c

[STDOUT] 0:SELECT id, nome, descricao FROM CATEGORIA

           WHERE (id=1) OR (id=2) OR (id=3)

[STDOUT] 0:COMMIT

Bem, o resultado foi um pouco melhor do que o “N+1”. Foram executadas duas consultas, uma retornando as chaves primárias de todas as categorias, e outra trazendo todos os atributos das três primeiras (e únicas) categorias existentes. O parâmetro page-size, que veremos mais adiante, determina quantos registros serão recuperados em cada acesso ao banco. Observe que as categorias foram recuperadas baseando-se nos valores explícitos de suas chaves primárias. Isso significa que, em uma tabela contendo uma maior quantidade de dados, apenas os primeiros registros teriam sido recuperados.

Encontramos a explicação para este comportamento examinando o arquivo <JBOSS>/server/default/conf/standardjbosscmp-jdbc.xml (aqui "<JBOSS>" indica o diretório de instalação do servidor). Este XML define os padrões, caso o pacote ejb-jar não inclua seu próprio descritor jbosscmp-jdbc.xml, ou para os parâmetros não fornecidos pelo descritor incluso na aplicação. No início desse arquivo, temos:

<defaults>

  <datasource>java:/DefaultDS</datasource>

  <datasource-mapping>Hypersonic SQL</datasource-mapping> 

  <create-table>true</create-table>

  <remove-table>false</remove-table>

  <read-only>false</read-only>

  <read-time-out>300000</read-time-out>

  <row-locking>false</row-locking>

  <pk-constraint>true</pk-constraint>

  <fk-constraint>false</fk-constraint>

  <preferred-relation-mapping>foreign-key</preferred-relation-mapping>

  <read-ahead>

    <strategy>on-load</strategy>

    <page-size>1000</page-size>

    <eager-load-group>*</eager-load-group>

  </read-ahead>

  <list-cache-max>1000</list-cache-max>

...                                                                          

Este trecho, além de definir como padrão o uso do banco de dados HSQLDB embutido, estabelece uma estratégia on-load de leitura antecipada (read-ahead), com tamanho de página 1000. Isto significa que os resultados da execução de um método finder serão carregados em blocos de no máximo mil entidades, e mantidas no preload cache do JBoss (apresentado adiante).

Antes de explorar as otimizações possíveis, vamos analisar um pouco mais o comportamento da aplicação. Clique em uma categoria para exibir o formulário de edição. O log do JBoss irá mostrar os seguintes comandos SQL:

[STDOUT] 0:SELECT id

           FROM CATEGORIA WHERE id=2

[STDOUT] 0:SELECT id, nome, descricao

           FROM CATEGORIA WHERE id=2 ...

Quer ler esse conteúdo completo? Tenha acesso completo