Esse artigo faz parte da revista Java Magazine edição 15.
Clique aqui para ler todos os artigos desta edição
Clique aqui para ler esse artigo em PDF.
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>