You have two ways to handle this situation: either with pessimist locking or with optimist locking. But you seem to use neither of both, which explain probably the incorrect behaviour.
With optimistic locking, Hibernate will check that the user account wasn't altered between the time it was read and saved. A concurrent transaction may then fail and be rolled back.
With pessimistic locking, you lock the row when you read it and it's unlocked only when transaction completes. This prevent a concurrent transaction to read data that would become stale.
Refreshing the entity may read new data or not depending whether the current transaction has already been committed or not, but is not a solution neither. Because you seem to also create the user account if it doesn't exist, you can't apply pessimist locking so easily. I would suggest you use optimistic locking then (and use for instance a timestamp to detect concurrent modifications).
Read this other question on SO about pessimist and optimist locking. Have also a look at hibernate chapter "transaction and concurrency" and "hibernate annotations".
It should be as simple as adding @Version
on the corresponding field, the optimisticLockStrategy
default value is VERSION
(a separate column is used).
-- UPDATE --
You can test whether it works in a test case. I've created a simple entity Counter
with an ID
, value
, and version
fields.
public class Counter implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Basic(optional = false)
@Column(name = "ID")
private Integer id;
@Column(name = "VALUE")
private Integer value;
@Column(name = "VERSION")
@Version
private Integer version;
...
}
If you update one entity sequentially it works:
id = insertEntity( ... );
em1.getTransaction().begin();
Counter c1 = em1.find( Counter.class, id );
c1.setValue( c1.getValue() + 1 );
em1.flush();
em1.getTransaction().commit();
em2.getTransaction().begin();
Counter c2 = em2.find( Counter.class, id );
c2.setValue( c2.getValue() + 1 );
em2.flush(); // OK
em2.getTransaction().commit();
I get one entity with value=2
and version=2
.
If I simulate two concurrent updates:
id = insertEntity( ... );
em1.getTransaction().begin();
em2.getTransaction().begin();
Counter c1 = em1.find( Counter.class, id );
Counter c2 = em2.find( Counter.class, id );
c1.setValue( c1.getValue() + 1 );
em1.flush();
em1.getTransaction().commit();
c2.setValue( c2.getValue() + 1 );
em2.flush(); // fail
em2.getTransaction().commit();
then the 2nd flush fails:
Hibernate: update COUNTER set VALUE=?, VERSION=? where ID=? and VERSION=?
Hibernate: update COUNTER set VALUE=?, VERSION=? where ID=? and VERSION=?
Dec 23, 2009 11:08:46 AM org.hibernate.event.def.AbstractFlushingEventListener performExecutions
SEVERE: Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [org.ewe.Counter#15]
at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1765)
This is so because the actual parameters in the SQL statements are:
update COUNTER set VALUE=1, VERSION=1 where ID=xxx and VERSION=0
--> 1 row updated
update COUNTER set VALUE=1, VERSION=1 where ID=xxx and VERSION=0
--> 0 row updated, because version has been changed in between