问题
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.
回答1:
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... ;
回答2:
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. :)
回答3:
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();
来源:https://stackoverflow.com/questions/33972564/is-it-possible-to-turn-off-hibernate-version-increment-for-particular-update