10

Is it possible update entity in database without modifying version of entity using hibernate?

Using my web application users can create or update entities. And where is another asynchronous process which “processes” these entities after any user operation. If user opens entity for update before entity is “processed”, but tries to save it after it is “processed”, user will get “OptimisticLockException” and all his entered data will be lost. But I would like to overwrite data updated in asynchronous process with user provided data.

code snipet to demonstrate why I need such behaviour (JPA + Hibernate):

//user creates entity by filling form in web application
Entity entity = new Entity ();
entity.setValue("some value");
entity.setProcessed (false);
em.persist(entity);
em.flush();
em.clear();

//but after short period of time user changes his mind and again opens entity for update
entity = em.find(Entity.class, entity.getId());
em.clear(); //em.clear just for test purposes

//another application asynchronously updates entities 
List entities = em.createQuery(
                "select e from Entity e where e.processed = false")
                .getResultList();
for (Object o: entities){
    Entity entityDb = (Entity)o;
    someTimeConsumingProcessingOfEntityFields(entityDb); //update lots of diferent entity fields
    entityDb.setProcessed(true);
    em.persist(entityDb);
}        
em.flush(); //version of all processed entities are incremented.       
//Is it possible to prevent version increment?
em.clear();  

//user modifies entity in web application and again press "save" button
em.merge(entity); //em.merge just for test purposes
entity.setValue("some other value");
entity.setProcessed (false);
em.persist(entityDb);
em.flush(); //OptimisticLockException will occur.        
//Is it possible to prevent this exception from happening?
//I would like to overwrite data updated in asynchronous process 
//with user provided data.

And my entity:

@Entity
@Table(name = "enities")
public class Entity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Version
    private int versionNum;
    @Column
    private String value
    @Column
    private boolean processed;
    //… and so on (lots other properties)
}

In reality I have much more classes with similar problem - so I am looking for some elegant non intrusive solution.

It seems to me this is quite usual scenario. But I could not find any information how to achieve such functionality.

Palladium
  • 280
  • 2
  • 15

3 Answers3

2

Hibernate Optimistic Locking can be bypassed using hibernate Session (http://docs.jboss.org/hibernate/orm/5.0/javadocs/org/hibernate/Session.html) replicate(...) method.

Example of code which does not increment version:

//Detaching to prevent hibernate to spot dirty fields. 
//Otherwise if entity exists in hibernate session replication will be skipped 
//and on flush entity will be updated with version increment.
em.detach(entityDb); 
someTimeConsumingProcessingOfEntityFields(entityDb);  

//Telling hibernate to save without any version modifications.
//Update hapends only if no newer version exists.
//Executes additional query DB to get current version of entity.
hibernateSession.replicate(entity, ReplicationMode.LATEST_VERSION);

I think this solution is better than native SQL update, because:

  • ORM mappings (anotations or *.hbm.xml) are uses (no need to duplicate Java objects <-> DB Tables mapping in native queries);
  • no need to manually execute flush (performance);
  • db and hibernate caches are in same state, no need to evict entities form cache (perfomance);
  • and you still have all ORM provided features like optimistic locking, and so on... ;
Palladium
  • 280
  • 2
  • 15
  • How can the same be achieved using eclipselink? Any answers [here](https://stackoverflow.com/questions/64171942/is-it-possible-to-turn-off-eclipselink-version-increment-for-particular-update) please – Alex K. Oct 02 '20 at 12:57
0

The version will always be incremented (unless you use JPQL). The idea of a version is to track changes and in case of JPA to also enable optimistic locking so applications can work concurrently with the same entities.

But to get around this issue you could read the latest version from the database and apply all values from the entity version the user has worked on.

Something like this:

void updateWithUserInput(Entity userVersion) {
    Entity dbVersion = entityManager.find(Entity.class, userVersion.getId);
    // apply changes...
    dbVersion.setX(userVersion.getX());
    // ...and so on
    // Also: We're dealing with an attached entity so a merge call should not be necessary
}

I assume that you do realize that this discards any changes performed by the async call, thus rendering it obsolete so you could also not do it and wouldn't need to override any changes. :)

Brian
  • 872
  • 8
  • 16
  • This solution will completely turn off optimistic lock check then multiple users edit same entity using web interface, but I do not want that to happen. The problem is not then user updates the entity, but then asynchronous process updates the entity. So I think I need somehow modify update of entity in asynchronous process. – Palladium Nov 28 '15 at 16:45
  • 1
    You could also map the entity to a different class without the version attribute and use it solely in your async call. – Brian Nov 28 '15 at 17:13
  • Yes, that would work, but unfortunately that would require quite a lot of extra refactoring (completely redesign my domain classes). In my application all domain classes (more then 100 classes) extends super class with field versionNum and significant part of them are updated with some asynchronous processes. So changing domain for this particular reason would be very “intrusive” solution. In that case I think it would be easier to overwrite hibernate DefaultFlushEntityEventListener class with possiblilty to turn of version increment in some cases (but that means "branching" hibernate). – Palladium Nov 28 '15 at 17:37
  • @Palladium, at this point I can only encourage you to see optimistic locking as something your application has to deal with if concurrent modifications occur. Maybe you can abort your async call, if a user wants to work on the same entity. After all there can be only one. – Brian Nov 28 '15 at 19:31
0

You can bypass Hibernate Optimistic Locking mechanism by issuing a native query update for asynchronous processing:

em
    .createNativeQuery("update entities set processed = :1 where id =:2")
    .setParameter(1, true)
    .setParameter(2, entityDb.getId())
    .executeUpdate();
Ori Dar
  • 18,687
  • 5
  • 58
  • 72
  • method someTimeConsumingProcessingOfEntityFields(entityDb) updates multiple complex fields of class Entity so it would be quite difficult to duplicate all this code using native queries. (In reality I have much more classes with similar problem - so I am looking for some elegant non intrusive solution). And second problem with using native queries with hibernate it it leaves domain object in "inconsistent" state. – Palladium Nov 28 '15 at 17:45
  • And second problem with using native queries with hibernate: it leaves domain object in "inconsistent" state (after native query updates domain objects directly in DB hibernate session will contain different objects values). This can be problem for code reusability (you will not be able to pass current objects to other method - firstly you need to update them from DB) – Palladium Nov 28 '15 at 17:53