Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to manually set the @Version fields with Hibernate 4?

Environment:

I have that User entity :

@Entity
public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer userId;

    @Version
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "VERSION", length = 19)
    private Date version;

    @Column(nullable = false, length = 20)
    private String login;

    // Getters and Setters
}

I have a search page which lists users, then I click on a user to edit it (giving its userId in the URL).

In the edit form, I store on the server the fields of that entity and when I save my User I do this :

User user = entityManager.find(User.class, userId)
user.setLogin(form.getLogin());
user.setVersion(form.getVersion());
user.setUserId(form.getUserId());
entityManager.merge(user);

Question:

So if I correctly understood optimistic locking with Hibernate, if I open 2 tabs in my browser to edit the same user, then update the login on the first tab, and then the login on the second tab, I should have an OptimisticLockException shouldn't I ?

Actually, this is not the case on my application... I verified, the form.getVersion() return the same value in both case, even if that in the second update, the user.version has been updated by the first edit.

Am I missing something ?

The EntityManager is produced @RequestScoped (so I'm on two different EntityManagers when I try to merge...).

I've tried to do a entityManager.lock(user, LockModeType.OPTIMISTIC_FORCE_INCREMENT) before entityManager.merge(...) (as said here), but it didn't help.

I'm using Seam 3 with JBoss 7.0.2.Final (which uses Hibernate 4).

like image 843
Anthony O. Avatar asked Feb 06 '12 15:02

Anthony O.


3 Answers

Found another way of triggering optimistic locking manually. We can compare the cached Hibernate version with the version in the entity. I'm using spring data JPA, and added the ff. to the repository save implementation:

EntityEntry entityEntry = entityManager
  .unwrap(SessionImplementor.class)
  .getPersistenceContext()
  .getEntry(entity);

//checked if a cached entry is present
if (entityEntry != null) {

  //Compare cached version with the one in the entity
  if (!Objects.equals(entityEntry.getVersion(), classMetadata.getVersion(entity))) {
    throw new ObjectOptimisticLockingFailureException();
  }
}
like image 83
David M Avatar answered Sep 30 '22 00:09

David M


Actually I've found a way to do that... but I think it's not really efficient (because there is 1 more SELECT request).

User user = entityManager.find(User.class, userId)
user.setLogin(form.getLogin());
user.setVersion(form.getVersion());
user.setUserId(form.getUserId());

entityManager.detach(user);
entityManager.merge(user);

With entityManager.detach(user), hibernate now uses my setted version value instead of its own copied somewhere value...

like image 38
Anthony O. Avatar answered Sep 30 '22 01:09

Anthony O.


I just did some more research on this.

It is not allowed to modify the version field according to the JPA spec:

JPA 2.0 Final Spec, Section 3.4.2:

An entity may access the state of its version field or property or export a method for use by the application to access the version, but must not modify the version value. With the exception noted in section 4.10, only the persistence provider is permitted to set or update the value of the version attribute in the object.

Note: Section 4.10 refers to batch updates which ignore the version attribute.

like image 36
chkal Avatar answered Sep 30 '22 02:09

chkal