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:
- A Criação de um Listener que verifica se a aplicação foi iniciada ou finalizada;
- A Criação de um singleton responsável pela factory;
- 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:
- 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)
- 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: