3

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)

  • have you tried em.persist instead of merge? The merge method keep tracking the object after the update. Depending how your configuration is it is possible that the update already went to the DB. – fhofmann May 20 '16 at 13:29
  • Post some more code. Relevant is how you load and modify the entity and how you call the code you have posted. – Alan Hay May 20 '16 at 13:35
  • @AlanHay I edited the original post with the some more code that represents my situation – Cátia Azevedo May 20 '16 at 14:46
  • @fhofmann I edited the "merge code" that answer your question. – Cátia Azevedo May 20 '16 at 14:52

3 Answers3

1

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

Community
  • 1
  • 1
K.Nicholas
  • 10,956
  • 4
  • 46
  • 66
0

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);
Ihor Patsian
  • 1,288
  • 2
  • 15
  • 25
Eduardo Meneses
  • 504
  • 3
  • 18
0

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;
Pedro Borges
  • 1,568
  • 1
  • 14
  • 25