Удаление дочернего элемента в отношении «один ко многим» вызывает исключение ObjectDeletedException

Я просто не понимаю, почему Hibernate выдает исключение, упомянутое в заголовке. Я, вероятно, не понимаю идею управления состоянием, лежащую в основе Hibernate.

У меня следующая ситуация:

Отношение "один ко многим" между организацией и сотрудником

Организация.hmb.xml

<set name="employees" inverse="true" cascade="save-update">  
    <key column="organization_id"/>  
    <one-to-many class="Employee"/>  
</set>

Сотрудник.hbm.xml

<many-to-one name="organization" class="Organization"  column="organization_id" />

Я использую стандартную архитектуру приложения Spring/Hibernate со службами и DAO, где DAO расширяют класс HibernateDaoSupport и используют службы класса HibernateTemplate для управления сеансом.

Когда я пытаюсь удалить сотрудника в этом сценарии...

Employee e=employeeService.read(1); 

//EDIT: Important! delete operation in EmployeeService is (@)transactional  
employeeService.delete(e); //this call just delegate execution to employeeDao.delete

EDIT: сначала я не упомянул, что операция удаления на уровне службы является транзакционной, что кажется важной информацией (продолжайте читать)!

Спящий режим бросает...

 ObjectDeletedException: deleted object would be re-saved by cascade...

Операция удаления в EmployeeService выглядит так...

  @Transactional public void delete(Employee emp){  
    Employee e=employeeDao.read(emp.getId());  
    if(e==null)  
        throw NoSuchOrganizationException();  

    /*...several while-s to delete relations where Employee is  
    not owner of relation... */

    employeeDao.delete(e);  
}

Сценарии (они не связаны):
1. Когда я удаляю cascade="save-update" из сопоставления отношения с сотрудником(ами) в Organization.hbm. xml, все работает нормально.
2. Когда я удаляю аннотацию @Transactional из метода удаления, все работает нормально.
3. Когда я удаляю дочерний элемент (Сотрудник) из родительского (Организация) списка дочерних элементов, а затем выполните удаление, все работает нормально.

Вопрос:

Почему Hibernate вообще заботится о каскаде в родительском классе?
Где находится точка выполнения, в которой он рассматривает каскад на объекте Organization? Почему он просто не может удалить Employee(Child) с помощью DELETE FROM... и все. Кроме того, Сотрудник является владельцем отношения, и операции, выполняемые над ним, должны управлять самим отношением. Когда он вообще думал вызывать какую-либо операцию над объектом Organization в упомянутом сценарии? Я просто не понимаю.


person slomir    schedule 12.05.2011    source источник


Ответы (1)


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

Учитывая ваш сценарий, если вы загрузили Organization и его набор employees и теперь пытаетесь удалить одного из этих сотрудников, Hibernate сообщает вам (выбрасывая ObjectDeletedException), что он повторно сохранит удаленного сотрудника, когда он сохранит Organization, потому что вы объявил, что сохранение или обновление должно быть каскадным от организации до сотрудников в ее наборе.

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

Изменить (на основе обновленного вопроса):

Organization владеет коллекцией сотрудников. Каскад всегда следует за ассоциацией — вы объявили его на стороне Organization, поэтому изменения (сохранение/обновление) распространяются на уровень Employee. Пока конкретный экземпляр Employee является членом коллекции employees любого объекта Organization, который живет в сеансе, он будет сохранен (или пересохранен, поэтому будет выдано исключение) при сбросе/закрытии сеанса.

Тот факт, что ассоциация отображается со стороны Employee (например, столбец organization_id находится в таблице Employee), здесь не имеет значения; его можно было бы сопоставить через таблицу соединений с теми же результатами. Hibernate поддерживает «состояние» через сеанс, и когда вы пытаетесь удалить Employee, не удаляя его из Organization.employees, вы даете противоречивые инструкции Hibernate (поскольку он все еще жив — и его нужно сохранить — в одном месте, но удаляется в другом) , отсюда и исключение.

person ChssPly76    schedule 12.05.2011
comment
Спасибо за ответ. Вы были правы, так оно и работает, но раньше я не загружал никаких объектов Organization. Я до сих пор не понимаю, когда Hibernate будет рассматривать любую операцию над объектом Organization, чтобы что-то каскадировать. Сначала я дал недостаточно информации, поэтому я отредактировал свой вопрос, указав больше информации, которая может быть важна. - person slomir; 12.05.2011
comment
Пожалуйста, извините, если я беспокою вас своими вопросами. Я провел еще несколько экспериментов и кое-что прояснил. У меня только одно замешательство: сохраняется ли Организация автоматически, когда сеанс закрывается/сбрасывается, независимо от того, что нет какой-либо явной операции над Организацией или в ее списке сотрудников? Другими словами, когда экземпляр организации обновляется/сохраняется? - person slomir; 12.05.2011
comment
@slomir - все объекты, которые были загружены (непосредственно вами или с помощью Hibernate за кулисами) в сеансе, будут сохраняться при сбросе сеанса. Это не означает, что для каждой сущности в базу данных выдается фактический оператор UPDATE — сохраняются только фактические изменения. Если вы не загрузили свою организацию напрямую, она была загружена Hibernate через ассоциацию с сотрудником (или, возможно, с каким-либо другим объектом) - person ChssPly76; 12.05.2011
comment
Большое спасибо за ответы. Благодаря вам, теперь я понял :) - person slomir; 12.05.2011
comment
1 вопрос...неважно это не слово. Вы буквально только что сказали без-безразличия.... :-| - person dsutherland; 18.06.2015