У вас есть два способа справиться с этой ситуацией: либо с блокировкой pessimist , либо с блокировкой optimist . Но вы, кажется, не используете ни то, ни другое, что, вероятно, объясняет неправильное поведение.
С оптимистической блокировкой Hibernate проверит, что учетная запись пользователя не изменялась между временем, когда она была прочитана и сохранена. Параллельная транзакция может затем потерпеть неудачу и откатиться.
При пессимистической блокировке вы блокируете строку, когда читаете ее, и она разблокируется только после завершения транзакции. Это предотвращает одновременную транзакцию для чтения данных, которые могут устареть.
Обновление объекта может считывать новые данные или нет, в зависимости от того, была ли текущая транзакция уже зафиксирована или нет, но также не является решением. Поскольку вы, кажется, также создаете учетную запись пользователя, если она не существует, вы не можете так легко применить пессимистическую блокировку. Я бы посоветовал вам использовать оптимистическую блокировку (и, например, использовать временную метку для обнаружения одновременных изменений).
Прочтите другой вопрос на SO о блокировке пессимиста и оптимиста. Также обратите внимание на разделы "1021 * транзакции и параллелизм " и " аннотации гибернации ".
Это должно быть так же просто, как добавить @Version
в соответствующее поле, optimisticLockStrategy
значение по умолчанию равно VERSION
(используется отдельный столбец).
- ОБНОВЛЕНИЕ -
Вы можете проверить, работает ли он в тестовом примере. Я создал простую сущность Counter
с полями ID
, value
и version
.
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;
...
}
Если вы обновляете одну сущность последовательно , она работает:
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();
Я получаю одну сущность с value=2
и version=2
.
Если я имитирую два одновременных обновления:
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();
, тогда 2-ой сброс завершается неудачей:
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)
Это так, потому что фактические параметры в операторах SQL:
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