Amada por uns e odiada por outros, a especificação Enterprise JavaBeans (EJB) é o coração de grandes sistemas desenvolvidos com a plataforma J2EE. Uma parte importante da especificação são os mecanismos de persistência CMP e CMR, que permitem desenvolver a camada de persistência de uma aplicação de modo orientado a objetos, sem a necessidade de lidar com tabelas, linhas e colunas nas classes de negócios.
Infelizmente, a especificação EJB, mesmo em sua versão mais recente (2.1), omite detalhes em pontos importantes. Isso leva na prática a sistemas que não são facilmente portados entre servidores de aplicações diferentes, e estimula desenvolvedores desiludidos (ou pouco persistentes) a adotar alternativas como JDO ou Hibernate. Por outro lado, a especificação EJB evoluiu muito desde a sua versão 1.0 e será a melhor solução para persistência em uma grande variedade de sistemas.
Neste artigo veremos de maneira prática como implementar entity beans, com exemplos escritos para o servidor livre JBoss, além de mostrar onde a portabilidade da sua aplicação pode ser comprometida. Algum conhecimento prévio sobre EJBs será de grande valia – mas não imprescindível – para a execução e o entendimento dos exemplos apresentados. E, curiosamente, muito pouco deste artigo será realmente específico para o JBoss; com poucas adaptações – talvez nenhuma – os exemplos poderão ser executados em outros servidores J2EE disponíveis no mercado.
Nossos exemplos serão focados na versão 2.0 da especificação. Isso porque o JBoss 3.2.x, assim como a maioria dos servidores J2EE no mercado, ainda não foi atualizado para o EJB 2.1. De toda forma, as mudanças são pequenas, especialmente nas áreas de EJB aqui tratadas (mostraremos algumas delas no decorrer do artigo).
A abordagem será de “aprenda com exemplos”, que oferece resultados rápidos mas não substitui uma base sólida nos conceitos e tecnologias abordados; para tal o leitor deve recorrer à documentação indicada ao final do artigo. Recomendo ainda a consulta aos artigos “JBoss Inicial” (Edição 5) e “Banco de Dados com JBoss” (Edição 8), além dos "Tira-dúvidas" sobre pacotes J2EE publicados nas Edições 9 e 10.
Esta é a primeira parte de uma série de dois artigos. A segunda parte tratará de tuning dos mecanismos de persistência e otimizações de acesso ao banco de dados subjacente.
Entity beans CMP e BMP
Entity beans são componentes persistentes, cujo estado é preservado entre diferentes sessões do usuário, ou entre reinicializações do container. São identificados por um atributo chamado de chave primária, em analogia aos bancos relacionais; podem ser de dois tipos: CMP (Container Managed Persistence – persistência gerenciada pelo container) ou BMP (Bean Managed Persistence – persistência gerenciada pelo bean).
Durante a versão 1.x da especificação de EJB o tipo BMP foi muito popular, não pelas suas vantagens, mas pelas deficiências do CMP original. O uso do BMP tornava entity beans ruins em termos de performance, exigindo grande quantidade de acessos ao banco, além de forçar o desenvolvedor a enxergar seus dados sob a ótica relacional – o que ia contra um dos principais propósitos de usar entity beans: enxergar os dados como objetos.
A partir da versão 2.0 o CMP se tornou poderoso o suficiente para aplicações do “mundo real”. Com isso, hoje o uso de BMP deve ser reservado apenas para situações especiais, no caso dos recursos de persistência do container não atenderem às necessidades da aplicação – por exemplo, se seu container é capaz de lidar apenas com bancos relacionais, mas sua aplicação necessita manipular também informações em um banco de dados legado (como o IMS do mainframe); ou se a aplicação precisa consultar informações de um diretório LDAP.
Entity beans CMP têm toda a interação com o banco de dados gerenciada pelo container EJB, que decide quando e como recuperar ou atualizar os dados. Bons containers manterão um extenso cache de entity beans, de modo que manipular informações pelo código Java pode fazer mais sentido do que programar stored procedures no banco de dados. Containers de qualidade também postergarão atualizações até o final da transação, para minimizar a quantidade de comandos UPDATE requeridos.
Um entity bean é pouco mais que uma coleção de atributos e seus métodos get/set – além de um ou mais métodos de criação e um método de deleção. Não há métodos explícitos de atualização ou de recuperação de informações do banco. Pressupõe-se que os resultados dos métodos get/set reflitam sempre o estado corrente do banco de dados. Isto significa que a lógica de negócios construída sobre entity beans costuma ser mais simples do que seria com o uso de chamadas JDBC ou com o uso de DAOs (Data Access Objects).
Primeiro entity bean
A definição de um componente EJB inicia-se pela sua interface local do componente (ou então pela sua interface remota); na Listagem 1 é mostrada a interface local (o tipo recomendado para entity beans). A interface home é definida na Listagem 2. O exemplo é uma aplicação simples de agenda de contatos corporativos.
Iniciamos pela entidade “Categoria”, que seria utilizada para filtrar os contatos, como amigos, colegas de trabalho, família, grupo de usuários etc. (no texto, será às vezes usado o termo "entidade" como sinônimo de entity bean). É uma prática padrão não expor os atributos individuais de uma entidade, mas fazê-lo por meio de um Value Object (veja mais adiante a seção “Padrões de projeto para EJBs”). O Value Object, ou VO, utilizado nos exemplos é apresentado na Listagem 3.
Não há nenhuma surpresa na interface local do componente; afinal o que mais poderia ser feito com ele a não ser obter e salvar os valores de seus atributos? Já a interface home contém um método que merece explicação: findByPrimaryKey() retorna a entidade dada sua chave primária. Todas as referências locais ou remotas à mesma entidade (com a mesma chave primária) atuam sobre a mesma instância do EJB no servidor de aplicações.
A Listagem 4 fornece a classe de implementação do EJB Categoria. Observe que os métodos get/set correspondentes aos atributos persistentes devem ser declarados como públicos e abstratos. O código real desses métodos será gerado pelo container EJB. É comum que os métodos definidos na interface javax.ejb.EntityBean (de implementação obrigatória) recebam implementações vazias, por isso foram omitidos. Note que o método findByPrimaryKey() definido na interface home não aparece na classe de implementação do EJB. Ele será gerado pelo container durante o deployment do componente.
Finalmente, o método create() da interface home corresponde a dois métodos na classe de implementação: ejbCreate() e ejbPostCreate() (o porquê de haver dois métodos será explicado mais adiante, quando falarmos de relacionamentos). Para completar o componente, falta apenas o descritor de deployment, que aparece na Listagem 5. Ele define cada um dos atributos persistentes do entity bean, qual é sua chave primária, e quais são os métodos “finder” disponíveis.
A implementação do EJB faz referência à classe GeradorChaveUtil, uma classe utilitária responsável por gerar valores únicos para utilização como chaves primárias “artificiais” (essa é uma prática comum quando a entidade não possui um atributo adequado para chave primária, como CPF ou código ISBN). Esta classe utiliza outro entity bean chamado GeradorChave para armazenar o último valor gerado para cada entidade, fornecendo um meio seguro e portável de geração de chaves seqüenciais (mecanismos do banco de dados, como campos auto-incremento ou seqüências, não são portáveis). Essas classes utilitárias não são listadas no artigo para poupar espaço, mas estão disponíveis para download no site da Java Magazine.
A atomicidade da operação de geração de chave (que consiste em ler o último valor de chave gerado, incrementá-lo e salvar o resultado) é garantida pelo gerenciamento de transações do container.
Consultas EJB-QL
Já seria possível agora fazer o deployment do EJB e escrever um cliente para testar sua funcionalidade – mas poucas aplicações seriam viáveis sem a capacidade de realizar consultas sobre os dados persistentes.
A realização de consultas sobre entity beans CMP é baseada em uma espécie de “SQL Orientado a Objetos”, o EJB-QL (Enterprise JavaBeans Query Language). As consultas EJB-QL são inseridas dentro de elementos aninhados dentro do elemento , no descritor do pacote EJB-JAR.
A Listagem 6 indica os acréscimos a serem feitos nas listagens correspondentes ao EJB Categoria para definir o método findAll(), que retorna todas as categorias disponíveis na agenda de contatos. De acordo com a especificação, os métodos que realizam consultas (métodos "finder") devem receber o prefixo “find”, e serem definidos na interface home do componente. Um método finder pode ter três tipos de valores de retorno:
- Uma referência à interface local (ou remota) do objeto, indicando que o método retorna uma única entidade
- Uma referência a uma coleção que implementa java.util.Set, indicando que o método retorna zero ou mais entidades, sem nenhuma ordenação em particular
- Uma referência a uma coleção que implementa java.util.Collection, indicando que o método retorna zero ou mais entidades sob uma ordenação específica, dependente do método. (O correto seria retornar uma referência a java.util.List, mas este pequeno deslize conceitual foi cometido pelo grupo que definiu a primeira especificação EJB; depois disso uma correção prejudicaria a compatibilidade retroativa dos componentes)
Padrões de projeto para EJBs
O leitor pode estar se perguntando porque foram definidas apenas interfaces locais em vez de interfaces remotas para a entidade Categoria. Pela especificação, poderiam ser definidas apenas interfaces remotas (desde que não fosse utilizado o recurso CMR!), ou poderiam ser definidos ambos os tipos de interface. Entretanto, as melhores práticas do desenvolvimento J2EE aconselham que todo o acesso a entidades seja mediado por um ou mais session beans, que fornecem uma visão de “alto nível” da funcionalidade de negócios fornecida. Assim sendo, apenas estes session beans terão interfaces remotas.
O uso de interfaces locais para os entity beans, que nos obriga a definir session beans como intermediárias entre a aplicação cliente e os componentes persistentes, é um exemplo do padrão de projeto “Façade” (fachada). Por este padrão, um conjunto complexo de funcionalidades implementado por várias classes que colaboram entre si é ocultado sob uma interface mais simples, a fachada. Em vez de várias interfaces remotas e home (para cada entidade da aplicação), o cliente manipula apenas um par de interfaces remotas do session bean de fachada. É claro, em sistemas mais complexos, cada módulo terá a sua própria fachada ocultando um conjunto de entidades relacionadas.
...