简体   繁体   中英

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)

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

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

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;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM