I have a Java EE application that has REST web services attached to it. Each REST call is given its own stateless bean to operate on. This bean calls a helper bean to create new items and persist them in the database. When the the REST bean gets back the item, manipulates it slightly, and then merges it into the database. Here’s the general flow
// Bean A
void someRESTCall()
{
Item i = beanB.getItem(...);
// Possible race condition here
if(i == null)
{
i = beanB.buildItem();
if(i == null)
{
// Assume race condition
i = beanB.getItemForce(...); // This has transactional attribute REQUIRES_NEW
}
}
// Do some stuff to the item and merge it
i.setColor("blue");
entityManager.merge(i);
}
// Bean B
Item buildItem(...)
{
Item i = new Item();
i.setName(name); // Name is the primary key, cannot be changed.
try
{
entityManager.persist(i);
}
catch(PersistenceException ex)
{
// Assume threading issue / race condition
i = null;
}
return i;
}
The original problem we were having as that, if two rest calls get into a race condition, you could have duplicates in the database (which result in a constraint exception because the primary key is not autogenerated.)
So, to get around that I added the return null to buildMachine. The problem is that the transaction rolls back, and although A continues to execute, it can’t update the database properly after the rollback.
So to get around that problem, I tried adding a REQUIRES_NEW transaction attribute to the buildMachine method. When I do that, I get a constraint exception when beanA goes to merge it’s changes back in.
So, how can I get around both the race condition and the constraint exception? Perhaps more importantly, why does the call to merge result in a constraint exception? It should be reusing the existing item database row returned by buildItem Changing the database to allow autogenerated primary keys is not an option.
Some people have suggested removing the call to entityManager.merge() in beanA, saying that it is/was redudant. If I do that, none of the changes made by A are merged in.
You have several problems.
First problem: persist() doesn’t insert in database. It only makes a transient entity attached. The insert is only executed when flush(à is called, explicitely of implicitely before the commit of the transaction. Catching an exception thrown by persist won’t work
Second problem: since
A.someRESTCallandB.buildItemrun in the same transaction, if any exception is thrown by JPA, the only thing you can do is rollback the transaction and discard the session. The Hibernate session is in an unstable state after any exception is thrown, and you can’t recover from any exception it throws.So, what I would do is: