I have 2 concurrent threads that at the same time enter a (Spring) transaction service.
Using Hibernate, the service method loads some entities, processes those, finds one and deletes it from the DB. The pseudo code is as following:
@Transactional
public MyEntity getAndDelete(String prop) {
List<MyEntity> list = (List<MyEntity>)sessionFactory
.getCurrentSession()
.createCriteria(MyEntity.class)
.add( Restrictions.eq("prop", prop) )
.list();
// process the list, and find one entity
MyEntity entity = findEntity(list);
if (entity != null) {
sessionFactory.getCurrentSession().delete(entity);
}
return entity;
}
In case two threads at the same time pass the same parameter, both will "find" the same entity an both will call delete
. One of them will fail throwing an org.hibernate.StaleObjectStateException
when the session is close.
I would like that both threads would return the entity, with no exception being thrown. In order to achieve this, I tried to lock (with "select... for update") the entity before deleting it, as following:
@Transactional
public MyEntity getAndDelete(String prop) {
List<MyEntity> list = (List<MyEntity>)sessionFactory
.getCurrentSession()
.createCriteria(MyEntity.class)
.add( Restrictions.eq("prop", prop) )
.list();
// process the list, and find one entity
MyEntity entity = findEntity(list);
if (entity != null) {
// reload the entity with "select ...for update"
// to ensure the exception is not thrown
MyEntity locked = (MyEntity)sessionFactory
.getCurrentSession()
.load(MyEntity.class, entity.getId(), new LockOptions(LockMode.PESSIMISTIC_WRITE));
if (locked != null) {
sessionFactory.getCurrentSession().delete(locked);
}
}
return entity;
}
I use load()
instead of get()
since according to the hibernate APIs, get would return the entity if already in the session, while load should re-read it.
If two threads enter at the same time the method above, one of them blocks a the locking stage, and when the first thread closes the transaction, the second is awoken throwing an org.hibernate.StaleObjectStateException
. Why?
Why does the locked load not just return null? How could I achieve this?