Um dos recursos da extensão demoiselle-jpa é fornecer um produtor de instâncias de EntityManager e cuidar do ciclo de vida dessas instâncias,providenciando que permaneçam consistentes durante uma chamada de método de negócio e que sejam devidamente fechadas ao final dessa chamada. Até o momento qualquer injeção de EntityManager ficava armazenada no escopo Request, o que significa que durante uma requisição web qualquer ponto de injeção receberá a mesma instância de EntityManager.
Isso é interessante para a maior parte dos casos, mas não permite flexibilidade para contornar situações onde esse comportamento não é desejável. Exemplos de tais situações incluem:
- Sistemas não web
- Chamadas onde o escopo de requisição não está ativo
- Situações que requerem controlar o tempo de vida do EntityManager, para evitar a famosa exceção LazyInitializationException
Começando na versão 2.4 a extensão demoiselle-jpa permitirá determinar o escopo do EntityManager. Além disso ela permitirá desligar o controle de escopo, entregando ao desenvolvedor a tarefa de controlar o ciclo de vida manualmente.
Para demonstrar essa nova funcionalidade, elaboramos um pequeno exemplo do recurso em ação. O aplicativo de exemplo está disponível no repositório do projeto no Github.
Software necessário
Para criar esse exemplo utilizamos os seguintes aplicativos:
- Eclipse IDE (Kepler, versão 4.3.1) + Plugin Jboss Tools
- Servidor de Aplicação JBoss AS 7.1
- JDK da Oracle, versão 6 update 45
Continue lendo nosso passo-a-passo desse novo recurso.
Nosso exemplo
Criaremos um simples exemplo que nos permite evitar o LazyInitializationException ao usar JSF 2.0 e chamadas AJAX. Nosso exemplo permitirá exibir uma lista do tipo master-detail. Cada elemento da lista master possui uma coleção com seus itens detail mas essa lista é anotada para ser carregada do banco de dados apenas quando acessada (a estratégia lazy do JPA). Normalmente ao fazer uma solicitação via AJAX para detalhar um item uma exceção é lançada, pois o EntityManager que gerenciava essas entidades foi destruído com o fim doRequest. Nosso exemplo vai definir o escopo do EntityManager para o escopo de visão (ViewScoped do Demoiselle), de forma que enquanto permanecermos na mesma página, oEntityManager permanecerá ativo.
O primeiro passo é criar uma nova aplicação usando o arquétipo Bookmark. Vamos utilizar a versão 2.4.0-RC1 do framework (após o lançamento da versão 2.4.0-FINAL essa poderá ser usada normalmente). O arquétipo escolhido deve ser o demoiselle-jsf-jpa. Use o groupId “com.jpa” e o artifactId “entitymanager-scope-sample” para essa aplicação. Use o pacote java com.jpa.emscopesample como base da aplicação. Se houver dúvida em como criar um aplicativo usando o arquétipo demoiselle-jsf-jpa, consulte nossa página quickstart.

Após a criação do projeto, você já deve ter um aplicativo totalmente funcional. Teste o aplicativo em um servidor de aplicação antes de continuar. Você deve ver a tela abaixo ao acessar seu aplicativo.

Vamos agora alterar o projeto para configurar o escopo do EntityManager. O primeiro passo é abrir a classe BookmarkBC e remover a anotação @Startup.
Porque devemos fazer isso? Bem, essa anotação executa qualquer método anotado no momento da carga da aplicação. Esse método em particular usa o EntityManager e cria nossa carga inicial de dados. O problema é que vamos alterar o escopo do EntityManager para visão, um escopo que depende da existência de uma chamada à telas JSF para existir. Se deixarmos essa anotação ativa em métodos que usam um EntityManager veremos uma exceção no momento da carga e a aplicação não será iniciada.
Ao invés disso vamos abrir a classe BookmarkListMB e criar um novo método que será responsável pela nossa carga inicial. Abra essa classe e crie o método loadData.
O projeto Bookmark tem apenas uma entidade: a bookmark. Para testarmos o problema de listar dados no formato master-detail precisaremos criar uma nova entidade: a category. Faça as seguintes alterações em seu projeto:
Crie a classe Category no pacote com.jpa.emscopesample.domain.
Edite seu arquivo persistence.xml para declarar a classe Category como uma entidade JPA.
Edite a classe Bookmark para conter uma lista de categorias.
Abra a classe BookmarkBC e edite o método load para incluir pelo menos uma categoria para cada bookmark durante a carga.
Por último edite a tela bookmark_list.xhtml para transforma-la em uma lista master-detail.
Tudo pronto. Agora inicie o JBoss, acesse a aplicação e vá na lista de Bookmarks. Pressione a seta e veja o detalhe de cada bookmark. Tudo parece estar bem. Agora saia dessa tela, acesse-a novamente e tente listar os detalhes. Agora a lista de detalhes está em branco, o que aconteceu?

O primeiro acesso fez a carga inicial de nossos dados e por isso a lista de bookmarks já estava completamente preenchida, mas acessos posteriores ativam a estratégia lazy para carregar a coleção de categorias de cada bookmark. Como o EntityManager é fechado ao concluir a requisição, no momento que o componente datatable tenta usar AJAX para carregar a lista de detalhes, o provedor JPA acusa erro devido ao EntityManager estar fechado. De fato se o console do JBoss for consultado nesse momento é possível ver no log a exceçãoorg.hibernate.LazyInitializationException disparada pelo container.
Esse é o problema que tentamos resolver e a solução está a apenas uma configuração de distância. Abra o arquivo demoiselle.properties e edite/crie a propriedadeframeworkdemoiselle.persistence.entitymanager.scope com o valor view.
1 | frameworkdemoiselle.persistence.entitymanager.scope=view |
Basta agora reiniciar o servidor de aplicação. Acesse agora a lista de bookmarks. Não importa quantas vezes a tela for acessada, sempre será possível detalhar um bookmark individual, pois apenas após trocar de visão (de tela) os EntityManager‘s associados àquela visão serão fechados.
Considerações importantes
A seleção de escopo para o EntityManager é um recurso experimental – é importante tomar cuidados especiais ao usar qualquer escopo diferente de request. Seguem os cuidados mais comuns a serem tomados ao usar esse recurso. Lembre-se que na dúvida é melhor deixar o comportamento padrão ativado.
Escopos serializáveis
Os escopos Session, View e Conversation são “passiváveis”, o que significa que a qualquer momento o container pode decidir serializar todos os beans armazenados nesses escopos em disco para liberar memória. De acordo com a especificação um provedor JPA não é obrigado a permitir a serialização do EntityManager e o comportamento dessa operação não é definido.
Para tentar minimizar o risco de problemas ao armazenar o EntityManager nesses escopos, tome as seguintes precauções:
- Verifique a documentação de seu container para saber quando e como ele serializa esses escopos: Cheque se é possível controlar o tempo de serialização e se existe alguma forma de detectar essa situação, lhe permitindo encerrar qualquer EntityManager ainda aberto.
- Não use trava em suas entidades, ou use a trava OPTIMISTIC_FORCE_INCREMENT: Ao obter uma trava pessimista (uma trava real no banco de dados) essa trava requer uma conexão aberta e acesso direto ao banco para solicitar sua abertura. No momento em que um EntityManager é serializado essa trava é perdida. Por isso se for necessário travar entidades em um sistema cujo EntityManager possa ser serializado, use a trava otimista com versão (OPTIMISTIC_FORCE_INCREMENT) pois essa trava é lógica e a trava só acontece realmente quando a entidade for ser modificada em banco. Leia a documentação da classe javax.persistence.LockModeType para entender a diferença entre as várias travas.
O escopo especial “noscope”
É possível definir o valor da propriedade frameworkdemoiselle.persistence.entitymanager.scope para noscope. Isso significa que o controle do ciclo de vida do EntityManager, uma vez injetado, é de inteira responsabilidade do desenvolvedor. Esse EntityManager não vai participar de transações iniciadas a partir da anotação @Transactional (a não ser que a estratégia JTA seja usada) nem será fechado ao fim do request. Mais: se o desenvolvedor injetar um EntityManager em mais de um ponto de injeção durante um request, cada ponto de injeção gerará uma instância diferente de EntityManager. Use esse recurso com cuidado, seu objetivo é dar maior controle ao ciclo de vida de um EntityManager e seu uso deve ser considerado.
Conclusão
Alterar o escopo do EntityManager é um recurso poderoso – é necessário planejar bem a arquitetura da aplicação que se fará dele. Verifique se um dos problemas listados nesse artigo aparecem em sua aplicação e realize testes para escolher o melhor escopo para configurar seu EntityManager. Consulte a documentação da API e teste o recurso em seu aplicativo. Não esqueça de reportar qualquer dúvida ou problema na lista de discussão e no tracker oficial do projeto.
Autor: Dancovich