Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create new or update existing entity at one go with JPA

A have a JPA entity that has timestamp field and is distinguished by a complex identifier field. What I need is to update timestamp in an entity that has already been stored, otherwise create and store new entity with the current timestamp.

As it turns out the task is not as simple as it seems from the first sight. The problem is that in concurrent environment I get nasty "Unique index or primary key violation" exception. Here's my code:

// Load existing entity, if any.
Entity e = entityManager.find(Entity.class, id);
if (e == null) {
  // Could not find entity with the specified id in the database, so create new one.
  e = entityManager.merge(new Entity(id));
}
// Set current time...
e.setTimestamp(new Date());
// ...and finally save entity.
entityManager.flush();

Please note that in this example entity identifier is not generated on insert, it is known in advance.

When two or more of threads run this block of code in parallel, they may simultaneously get null from entityManager.find(Entity.class, id) method call, so they will attempt to save two or more entities at the same time, with the same identifier resulting in error.

I think that there are few solutions to the problem.

  1. Sure I could synchronize this code block with a global lock to prevent concurrent access to the database, but would it be the most efficient way?
  2. Some databases support very handy MERGE statement that updates existing or creates new row if none exists. But I doubt that OpenJPA (JPA implementation of my choice) supports it.
  3. Event if JPA does not support SQL MERGE, I can always fall back to plain old JDBC and do whatever I want with the database. But I don't want to leave comfortable API and mess with hairy JDBC+SQL combination.
  4. There is a magic trick to fix it using standard JPA API only, but I don't know it yet.

Please help.

like image 350
Alex Radzivanovich Avatar asked Feb 25 '10 18:02

Alex Radzivanovich


People also ask

Which method is used to update entity in JPA?

We use the EntityManager. merge() method to update an entity. This method takes the entity to be saved as the parameter and return the merged entity back as the result.

What is @DynamicUpdate in JPA?

@DynamicUpdate is a class-level annotation that can be applied to a JPA entity. It ensures that Hibernate uses only the modified columns in the SQL statement that it generates for the update of an entity. In this article, we'll take a look at the @DynamicUpdate annotation, with the help of a Spring Data JPA example.

What is the difference between Merge and persist in Hibernate?

Persist should be called only on new entities, while merge is meant to reattach detached entities. If you're using the assigned generator, using merge instead of persist can cause a redundant SQL statement.


1 Answers

You are referring to the transaction isolation of JPA transactions. I.e. what is the behaviour of transactions when they access other transactions' resources.

According to this article:

READ_COMMITTED is the expected default Transaction Isolation level for using [..] EJB3 JPA

This means that - yes, you will have problems with the above code.

But JPA doesn't support custom isolation levels.

This thread discusses the topic more extensively. Depending on whether you use Spring or EJB, I think you can make use of the proper transaction strategy.

like image 96
Bozho Avatar answered Nov 06 '22 12:11

Bozho