Избегайте 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 при закрытии сеанса.

Я хотел бы, чтобы оба потока возвращали объект без исключения. Чтобы добиться этого, я попытался заблокировать (с помощью «выбрать... для обновления») объект перед его удалением, как показано ниже:

@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(), так как, согласно API-интерфейсам гибернации, get вернет объект, если он уже находится в сеансе, а load должен перечитать его.

Если два потока одновременно входят в метод, описанный выше, один из них блокирует стадию блокировки, и когда первый поток закрывает транзакцию, второй пробуждается, выдавая org.hibernate.StaleObjectStateException. Почему?

Почему заблокированная загрузка не просто возвращает ноль? Как я мог этого добиться?


person Ciaccia    schedule 19.10.2011    source источник


Ответы (1)


Я потратил некоторое время на изучение этой проблемы и наконец понял, что происходит.

Блокировка PESSIMISTIC_WRITE пытается "заблокировать" Entity, которая уже загружена в сеансе, она не перечитывает объект из БД. При отладке вызова я увидел, что 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.

person Ciaccia    schedule 20.10.2011
comment
Получение сеанса теперь устарело. Вы знаете, как перезагрузить без него? - person Alison; 28.12.2012