setembro 25, 2010

Trabalhando com JPA, fora de um container Java EE.

Resolvi escrever este post, para compartilhar a minha experiência com a utilização da API de Persistência do JAVA (JPA), fora de um container Java EE (ex.: TOMCAT). No meu caso, o ambiente em questão é composto por: Tomcat, Hibernate e PostgreSQL.

Ao utilizar a JPA em um ambiente "não Java EE", o desenvolvedor não pode contar com o recurso de "Dependency Injection" (pelo menos não, na camada de modelo) e deve, por conta própria, gerenciar o ciclo de vida dos Entity Managers no domain model. Não realizar esta tarefa de forma eficiente, pode resultar em situações desagradáveis, como o travamento do banco de dados por conexões abertas em excesso.

A interface EntityManager, faz a "ponte" entre o mundo orientado a objetos e o mundo relacional,  ela oferece serviços de persistência para objetos, como não é possível contar com a Dependency Injection,  é necessário utilizar a interface EntityManagerFactory, que também faz parte da API, para obter um EntityManager

Entity Managers obtidos através de uma Entity Manager Factory, são conhecidos como Applicaton-Managed Entity Managers, é de responsabilidade do desenvolvedor gerenciar seu ciclo de vida, os que são gerenciados pelo container, são conhecidos como Container-managed Entity Managers.

Como EntityManagerFactory é um objeto de carga pesada, uma idéia básica é, criar uma única factory, através de um Singleton e permitir acesso à ela de forma global. Assim, as classes responsáveis por realizar a comunicação com o banco de dados, podem obter objetos do tipo EntityManager, através desta factory compartilhada, sempre que necessário.

Uma abordagem para alcançar este objetivo envolve: 
  1. A Criação de um Listener que verifica se a aplicação foi iniciada ou finalizada;
  2. A Criação de um singleton responsável pela factory
  3. Gerenciar ciclo de vida de Entity Managers no escopo dos métodos que fazem acesso à banco de dados.
1. O Listener

public class PersistenceAppListener implements ServletContextListener {
  
     public void contextInitialized(ServletContextEvent evt) {
     }


     public void contextDestroyed(ServletContextEvent evt) {
          PersistenceManager.getInstance().closeEntityManagerFactory();
     }
}


precisamos adicioná-lo ao web.xml:

<description>ServletContextListener</description>
<listener>
     <listener-class>my.package.PersistenceAppListener</listener-class>
</listener>

A idéia do listener é observar se a aplicação foi finalizada, para liberar os recursos.


2. O Singleton

public class PersistenceManager {
 
 public static final boolean DEBUG = true;
   
   private static final PersistenceManager singleton = 
                                             new PersistenceManager();
   
   protected EntityManagerFactory emf;
   
   public static PersistenceManager getInstance() {
     
     return singleton;
   }
   
   private PersistenceManager() {
   }
  
   public EntityManagerFactory getEntityManagerFactory() {
     
     if (emf == null)
       createEntityManagerFactory();
     return emf;
   }
   
   public void closeEntityManagerFactory() {
     
     if (emf != null) {
       emf.close();
       emf = null;
       if (DEBUG)
         System.out.println("Persistence finished at " + 
                                       new java.util.Date());
     }
   }
   
   protected void createEntityManagerFactory() {
     
     this.emf = Persistence.createEntityManagerFactory("MyUnit");
     if (DEBUG)
       System.out.println("Persistence started at " + 
                                 new java.util.Date());
   }
}

O Singleton é responsável por fornecer a factory global através do método getEntityManagerFactory().

3. Utilizando Entity Managers

Segue o exemplo de código cliente, que utiliza a factory para obter um EntityManager

public class MyRepository  {
     
     // variável de instância 
     private EntityManagerFactory emf = PersistenceManager.getInstance().getEntityManagerFactory();
     
     // Insere objeto no banco
     public void add(myObject object) {
      
          EntityManager em = this.emf.createEntityManager();
          try{
               EntityTransaction et = em.getTransaction();
               try {
                    et.begin();
                    em.persist(object);
                    et.commit();
               } finally {
                    if (et.isActive()) et.rollback();
               }
          } finally {
               em.close();
          }  
     }
}

No geral, esta abordagem atendeu bem às minhas necessidades, mas eu enxerguei duas desvantagens neste modelo:
  1. A dificuldade de utilizar LAZY LOAD de objetos, pois o ciclo de vida dos Entity Managers têm escopo de método. Os relacionamentos @OneToMany e @ManyToMany, utilizam lazy load por default. Uma forma de resolver este problema é anotar forçando EAGER LOADING, ex.: @OneToMany(fetch=FetchType.EAGER)
  2. A quantidade de código extra (try's aninhados), necessário para manter a gerência de ciclo de vida dos Entity Managers, de forma eficiente.
Para saber mais: