Cátia Azevedo
Cátia Azevedo

Reputation: 105

JPA Version Entity merge

I know that there are some questions about this subject already but I think that this one is different.

Let's say I have this class:

@Entity
public class foo{
  @Id
  @GeneratedValue
  private long id;

  @Version
  private long version;

  private String description;
  ...
}

They I create some objects and persist them to a DB using JPA add().

Later, I get all from the repository using JPA all(); From that list I select one object and change the description.

Then I want to update that object in the repository using JPA merge() (see code).

The problem here is that it works the first time I try to change the description (Version value is now 2). The second time, a OptimisticLockException is raised saying that that object was changed meanwhile.

I'm using H2 has DB in embedded mode.

MERGE CODE:

//First: persist is tried, if the object already exists, an exception is raised and then this code is executed
try {
     tx = em.getTransaction();
     tx.begin();
     entity = em.merge(entity);
     tx.commit();
   } catch (PersistenceException pex) {
              //Do stuff
            }

What can be wrong where?

Thank you.

EDIT (more code)

//Foo b is obtained by getting all objects from db using JPA all() and then one object is selected from that list

b.changeDescription("Something new!");

//Call update method (Merge code already posted)

Upvotes: 3

Views: 1832

Answers (3)

Eduardo Meneses
Eduardo Meneses

Reputation: 524

Here are a post which explains perfectly when OptimisticLockException is thrown.

Also, just for future reference, you can make JPA avoid this in-memory validation of entities when you are updating them but want to change in DB side just in the end of this transaction using detach method on EntityManager:

em.detach(employee);

Upvotes: 0

Pedro Borges
Pedro Borges

Reputation: 1719

Are you properly initialising the version field?

If not, it is not supposed to work with null, try adding a default value to it:

@Version
private Long version = 0L;

Upvotes: 0

K.Nicholas
K.Nicholas

Reputation: 11551

I would assume that you are changing elements in the list from different clients or different threads. This is what causes an OptimisticLockException.

One thread, in it's own EntityManager, reads the Foo object and gets a @Version at the time of the read.

// select and update AnyEntity
EntityManager em1 = emf.createEntityManager();
EntityTransaction tx1 = em1.getTransaction();
tx1.begin();
AnyEntity firstEntity = em1.createQuery("select a from AnyEntity a", AnyEntity.class).getSingleResult();
firstEntity.setName("name1");
em1.merge(firstEntity);

Another client reads and updates the Foo object at the same time, before the first client has committed its changes to the database:

// select and update AnyEntity from a different EntityManager from a different thread or client
EntityManager em2 = emf.createEntityManager();
EntityTransaction tx2 = em2.getTransaction();
tx2.begin();
AnyEntity secondEntity = em2.createQuery("select a from AnyEntity a", AnyEntity.class).getSingleResult();
secondEntity.setName("name2");
em2.merge(secondEntity);

Now the first client commits its changes to the database:

// commit first change while second change still pending
tx1.commit();
em1.close();

And the second client gets an OptimisticLockException when it updates its changes:

// OptimisticLockException thrown here means that a change happened while AnyEntity was still "checked out"
try {
    tx2.commit();
    em2.close();
} catch (RollbackException ex ) {
    Throwable cause = ex.getCause();
    if (cause != null && cause instanceof OptimisticLockException) {
        System.out.println("Someone already changed AnyEntity.");
    } else {
        throw ex;
    }
}

Reference: Java - JPA - @Version annotation

Upvotes: 1

Related Questions