Friday, May 4, 2007

JPA EntityManagerFactory in web applications

Coding Java Persistence web applications that run outside a Java EE Server (e.g. Apache Tomcat) is slightly different from JPA applications inside an Application Server. The main difference has to do with the responsibility to manage the EntityManagerFactory lifecycle. Because there is no Dependence Injection outside the Java EE Server, this responsibility fall directly on the programmer. So, we are about to discuss the way to deal with this easily.


As it's already mentioned in Design Choices in a Web-only Application Using Java Persistence, the EntityManagerFactory class is thread-safe, therefore it's very convenient to create it once with application scope and to destroy it when the web application eventually ends (i. e. during a server shutdown).


Closing an EntityManagerFactory must not be forgotten. It's very important to keep this fact in mind. Otherwise it would be possible to achieve unexpected side effects. These effects depend on the type of JPA provider; i. e. By using Toplink Essentials the problems, as I've realized, may arise in subsequents deployments (if closing the EntityMangerFactory is forgotten).

So, it's a common practice to create only one EntityManagerFactory at the beginning of a deployed web application shared with application scope and ensure its destruction at the end of the web application's lifecycle.

A practical design for managing an EntityManagerFactory this way is based on a web application listener (ServletContextListener) as well as on the ServiceLocator design pattern. This latter to gain access to the EntityManagerFactory from wherever it is needed. The only purpose of the application listener is to ensure that the shared EntityManagerFactory is always closed (if it was created).

Note we'll use a lazy strategy for the creation of the EntityManagerFactory.

The following UML class diagram shows us this approach:



Managing the EntityManagerFactory's lifecycle using an application listener

The following class could be a sample of this listener.

public class PersistenceAppListener implements ServletContextListener {

public void contextInitialized(ServletContextEvent evt) {
}

public void contextDestroyed(ServletContextEvent evt) {

PersistenceManager.getInstance().closeEntityManagerFactory();
}
}
We also need to define this listener in the web application descriptor file web.xml.
<web-app ...>
...
<description>ServletContextListener</description>
<listener>
<listener-class>tmpl.web.PersistenceAppListener</listener-class>
</listener>
...
</web-app>

Getting the EntityManagerFactory from a singleton in the PersistenceManager class

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("n*** Persistence finished at " + new java.util.Date());
}
}

protected void createEntityManagerFactory() {

this.emf = Persistence.createEntityManagerFactory("OrderPU");
if (DEBUG)
System.out.println("n*** Persistence started at " + new java.util.Date());
}
}

Using the EntityManagerFactory class from a business class

The client code that use this pattern is straight forward.

The following snippet of code is when your are using the EntityManager in a transaction:
EntityManagerFactory emf = PersistenceManager.getInstance().getEntityManagerFactory();
EntityManager em = emf.createEntityManager();

try {
EntityTransaction t = em.getTransaction();
try {
t.begin();
...
t.commit();
} finally {
if (t.isActive()) t.rollback();
}
} finally {
em.close();
}
When no transaction is required the client code is as follows:
EntityManagerFactory emf = PersistenceManager.getInstance().getEntityManagerFactory();
EntityManager em = emf.createEntityManager();

try {
...
} finally {
em.close();
}