3

I would like to use @Version for optimistic concurrency control with JPA & Hibernate.

I know how it works in the typical scenario of two parallel transactions. I also know that if I have a CRUD with 1:1 mapping between the form and entity, I can just pass version along as a hidden field and use this to prevent concurrent modifications by users.

What about more interesting cases, which use DTOs or change command patterns? Is it possible to use @Version in this scenario as well, and how?

Let me give you an example.

@Entity
public class MyEntity {
    @Id private int id;
    @Version private int version;
    private String someField;
    private String someOtherField;
    // ...
}

Now let's say two users open the GUI for this, make some modifications and save changes (not at the same time, so the transactions don't overlap).

If I pass the entire entity around, the second transaction will fail:

@Transactional
public void updateMyEntity(MyEntity newState) {
    entityManager.merge(newState);
}

That's good, but I don't like the idea of passing entities everywhere and sometimes would use DTOs, change commands etc.

For simplicity change command is a map, eventually used in a call like this on some service:

@Transactional
public void updateMyEntity(int entityId, int version, Map<String, Object> changes) {
    MyEntity instance = loadEntity(entityId);
    for(String field : changes.keySey()) {
        setWithReflection(instance, field, changes.get(field));
    }
    // version is unused - can I use it somehow?
}

Obviously, if two users open my GUI, both make a change, and execute it one after another, in this case both changes will be applied, and the last one will "win". I would like this scenario to detect concurrent modification as well (the second user should get an exception).

How can I achieve it?

Konrad Garus
  • 53,145
  • 43
  • 157
  • 230

1 Answers1

0

If I understand your question correctly, all you need is a setter for your private int version field and when you update the entity, you set it in your entity. Of course your DTO must always transport version data. Eventually, you would do also something like:

MyEntity instance = loadEntity(entityId);
entityManager.detach(instance);
for(String field : changes.keySey()) {
    setWithReflection(instance, field, changes.get(field));
}
//set also the version field, if the loop above does not set it
entityManager.merge(instance);
V G
  • 18,822
  • 6
  • 51
  • 89