Monday, May 21, 2007

Bug in Hibernate implementation of JPA: Persisting an entity

I've found a bug in the Hibernate implementation of JPA when persisting an entity.

I'm working with JPA using two different JPA implementations: Hibernate EM and Toplink Essential, and I've found certain interesting conditions (I'm to describe) upon which Hibernate throws an unexpected exception that I think it shouldn't.

The observation

The exception I obtained was this:

javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist

And it is thrown when persisting an entity for the second time when the first time hadn't succeeded. The whole scenario is as follows:

Suppose a user is going to add a new entity such as a customer to the database but he/she enters some data invalid for the database (it violates some constraints, for instance a unique constraint) and therefore when the user clicks on the "add button" to add the customer, within the business logic, the persist method of the EntityManager throws a PersistenceException. In fact, with Hibernate, this exception is thrown in a lazy way when the transaction tries to commit.

Whatever the case, for the business logic to be robust, when the exception happens, it is caught and the user is informed, so he/she is allowed to modify the wrong data and to try again to add this same but modified entity.
So, when the user, after modifying some values of the customer proceeds again to click on the "add button", the business logic executes em.persist(entity) for the second time (with the same object entity). This time, although no constraint is violated in the database (the user worked it out), Hibernate throws the exception with the message: detached entity passed to persist.

The code of the business logic I was referring above is this:
public void addCustomer(Customer cust) throws ValidateException, CustomerException {

validate(cust);
EntityManagerFactory emf = PersistenceManager.getInstance().getEntityManagerFactory();
EntityManager em = emf.createEntityManager();

try {
EntityTransaction t = em.getTransaction();
try {
t.begin();
cust.setStartedDate(new Date());
em.persist(cust);
t.commit();
} finally {
if (t.isActive())
em.getTransaction().rollback();
}
} catch (PersistenceException ex) {
Throwable lastCause = ex;
while (lastCause.getCause() != null)
lastCause = lastCause.getCause();
if (lastCause.getMessage().startsWith("ORA-00001"))
throw new CustomerException("The Customer Id already exists.");
else
throw ex;
} finally {
em.close();
}
}
Note that I'm assuring both EntityManager em and transaction is closed before leaving this method!

My Customer Entity class is using a database sequence to feed its primary key:
@Entity
@Table(name = "CUSTOMER")
@SequenceGenerator(name="customerGen", sequenceName="SEQ_CUSTOMER", initialValue=1, allocationSize=1)
public class Customer implements Serializable {

@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="customerGen")
private Integer id;

...
}
And my client code (snippet from a JSF Backing bean) where the business logic is invoked is:
public String addCustomerAction() {

try {
model.addCustomer(customer);
return "done";
} catch (ValidateException ex) {
FacesContext.getCurrentInstance().getExternalContext().getRequestMap()
.put("validationErrors", ex.getMessages());
return "error";
} catch (CustomerException ex) {
FacesContext.getCurrentInstance().getExternalContext().getRequestMap()
.put("errors", ex.getMessages());
return "error";
}
}
Just after invoking the addCustomer() method for the second time the unexpected PersistenceException is thrown!
No exception should be thrown here, because the entity hasn't persisted yet (although we tried once but there was a rollback due to database constraint).
As I have checked, this works fine using Toplink Essential as the JPA engine.

The explanation

So, what is happening? Why am I obtaining this error if this method is invoked upon these conditions?

After some little research, I've discovered what should be happening with Hibernate and how to workaround with this problem.
Once you have tried to persist an entity and obtained an exception, the primary key of the entity is remembered and marked as a sort of "already used" state and therefore you shoudn't try to persist an entity with this primary key again: the workaround is based on assigning another primary key to the entity! This way you won't obtain the unexpected exception with the message: detached entity passed to persist.

My workaround client code (snippet from a JSF Managed bean)
public String addCustomerAction() {

try {
model.addCustomer(customer);
return "done";
} catch (ValidateException ex) {
FacesContext.getCurrentInstance().getExternalContext().getRequestMap()
.put("validationErrors", ex.getMessages());
return "error";
} catch (CustomerException ex) {
FacesContext.getCurrentInstance().getExternalContext().getRequestMap()
.put("errors", ex.getMessages());
customer.setId(null); // This is the line we need to workaround the problem with Hibernate!
return "error";
}
}
The actual value of the setId method doesn't matter as far as it is not the same. In this snippet it's assigned to null because this way the sequence will be use again to get the next value when persisting next time.

In My Humble Opinion, I think this is a bug, because if the transaction is rolled back, you should be able to work with the same primary key (as far as it isn't in the database yet).

I would like to know what is your opinion on this bug, so I encourage you to post any comments below!

I'm using the following version products:

  • Hibernate Core 3.2
  • Hibernate EntityManager 3.2.1 GA

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();
}