Избегайте StaleObjectStateException при удалении объекта
У меня есть 2 параллельных потока, которые одновременно входят в (Spring) сервис транзакций.
Используя Hibernate, сервисный метод загружает некоторые объекты, обрабатывает их, находит и удаляет их из БД. Псевдокод выглядит следующим образом:
@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;
}
Если два потока одновременно передают один и тот же параметр, оба "найдут" один и тот же объект, и оба вызовут delete
, Один из них не сможет бросить org.hibernate.StaleObjectStateException
когда сессия близка
Мне бы хотелось, чтобы оба потока возвращали объект, без исключения. Чтобы добиться этого, я попытался заблокировать (с помощью "select... for update") объект перед его удалением, как показано ниже:
@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;
}
я использую load()
вместо get()
поскольку в соответствии с hibernate API, get возвращает объект, если он уже находится в сеансе, а load должен перечитать его.
Если два потока входят одновременно в описанный выше метод, один из них блокирует этап блокировки, и когда первый поток закрывает транзакцию, второй активируется org.hibernate.StaleObjectStateException
, Зачем?
Почему заблокированная загрузка не просто возвращает ноль? Как я мог этого добиться?
1 ответ
Я потратил некоторое время на изучение этой проблемы и наконец понял, что происходит.
Блокировка PESSIMISTIC_WRITE пытается "заблокировать" сущность, которая уже загружена в сеансе, она не перечитывает объект из БД. Отлаживая звонок, я увидел, что entity == locked
возвращенный true
(в терминах Java). Обе переменные указывали на один и тот же экземпляр.
Чтобы заставить hibernate перезагружать сущность, ее необходимо сначала удалить из сеанса.
Следующий код делает свое дело:
@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) {
// Remove the entity from the session.
sessionFactory.getCurrentSession().evict(entity);
// reload the entity with "select ...for update"
MyEntity locked = (MyEntity)sessionFactory
.getCurrentSession()
.get(MyEntity.class, entity.getId(), new LockOptions(LockMode.PESSIMISTIC_WRITE));
if (locked != null) {
sessionFactory.getCurrentSession().delete(locked);
}
}
return entity;
}
PESSIMISTIC_WRITE должен использоваться с get
вместо load
потому что иначе org.hibernate.ObjectNotFoundException
будет брошен.