Что такое каскадность как она используется в hibernate

Cascade Types (пример на Hibernate и Spring Boot)

В этой статье рассмотрим самые популярные каскадные операции на примере отношения OneToMany (хотя их возможно использовать также в OneToOne и ManyToMany).

Модель OneToMany

Рассмотрим пример топика с комментариями. Они находится в отношении OneToMany: у одного топика может быть много комментариев, а каждый комментарий, в свою очередь, относится ровно к одному топику. В базе данных для них создаются две таблицы:

Что такое каскадность как она используется в hibernate. Смотреть фото Что такое каскадность как она используется в hibernate. Смотреть картинку Что такое каскадность как она используется в hibernate. Картинка про Что такое каскадность как она используется в hibernate. Фото Что такое каскадность как она используется в hibernateOneToMany и ManyToOne

Топик является родительской сущностью в том смысле, что комментарий сам по себе не существует, а всегда относится к топику. Его жизненный цикл привязан к топику. При удалении топика надо удалять из базы и его комментарии, а при сохранении топика — сохранять и его комментарии. Тут то и уместны каскадные операции — то есть те, которые распространяются и на дочернюю сущность.

Каскадные операции указываются над ссылкой на дочернее отношение, у нас это comments:

CascadeType.ALL

Есть несколько типов каскадных операций, мы рассмотрим три основные: PERSIST, MERGE и REMOVE. CascadeType.ALL означает, что необходимо выполнять каскадно сразу все операции:

Обратите внимание, что у нас именно это и указано.

CascadeType.PERSIST

Рассмотрим каскадную операцию PERSIST. Поскольку мы используем Spring, то необязательно вызывать напрямую метод EntityManager persist(). Он вызывается, когда мы выполняем метод save() репозитория SimpleJPARepository. Выглядит метод так:

SimpleJPARepository save()

Как видно выше, для новых сущностей вызывается persist(), а для уже существующих сущностей (у которых id!=null) вызывается метод merge().

Но сначала протестируем persist(), который вызывается для новых сущностей. Для этого создадим новый топик с и добавим к нему два комментария:

Числа в Assertions выше приведены с учетом уже существующих данных в базе (см. исходный код либо скрипт ниже).

После добавления комментариев сохраним топик с помощью вышеприведенного метода save() репозитория. Внутри вызовется persist(), и эта операция выполнится каскадно, то есть для комментариев она выполнится тоже.

Генерируются три SQL insert, один для топика и два для комментариев:

В базе будут сохранены комментарии, хотя persist() выполнялся только для топика. В этом и смысл каскада.

CascadeType.MERGE

Теперь выполним каскадно merge(), для этого отредактируем существующий топик и его комментарий. Как уже сказано, некоторые данные в базу мы добавляем при старте приложения и тестов (см. data.sql):

Отредактируем в тесте комментарий с и топик с а merge() выполним только для топика (снова через метод save() репозитория).

Но также отредактирован будет и комментарий, что видно в консоли:

CascadeType.REMOVE

Наконец, удалим топик из репозитория и убедимся, что для комментариев также выполняются SQL команды delete:

orphanRemoval vs CascadeType.REMOVE

О том, чем отличается каскадное удаление от orphanRemoval подробно написано тут. Если кратко, orphanRemoval удаляет комментарий из базы при удалении комментария из топика.

В тесте выше методом removeComment() мы обнулили ссылку на топик из комментария, а также удалили комментарий из коллекции комментариев топика. Несмотря на то, что сам комментарий мы не удаляли (через commentRepository.remove()), благодаря настройке orphanRemoval=true он удаляется из базы. Это происходит в тот момент, когда в конце транзакции (тестового метода) изменения отслеживаемых сущностей (у нас это topic) синхронизируются с базой.

Почему не надо использовать CascadeType.REMOVE в отношениях ManyToMany

Наконец, обратите внимание на опасность, которую таит CascadeType.REMOVE в отношении ManyToMany.

Допустим, у нас есть авторы и книги в отношении ManyToMany, и книга b принадлежит авторам a1 и a2. А над коллекцией книг стоит CascadeType.REMOVE. Тогда при удалении автора a1 из базы книга b будет удалена из базы несмотря на то, что она принадлежит также и a2 (внешний ключ от b к a2 тоже будет предварительно удален).

А если еще и в Book поставить над коллекцией авторов CascadeType.REMOVE, то при удалении нашего a1 будут удалены и другие авторы.

В общем CascadeType.REMOVE в отношениях ManyToMany в большинстве случаев нежелателен и опасен.

Итоги

Мы рассмотрели самые популярные случаи использования каскадных операций. Исходный код есть на GitHub.

Источник

NHibernate — Каскады

В этой главе мы расскажем, как использовать функцию Cascade. Если у вас есть набор или коллекция предметов или отношения между двумя классами, такими как наш клиент и заказ, и у вас есть связь с внешним ключом. Если мы удаляем клиента по умолчанию, NHibernate ничего не делает с дочерними объектами, поэтому те, которые принадлежат этому клиенту, и мы могли бы быть заказами-сиротами.

Мы также могли бы нарушать ограничения внешнего ключа, поэтому мы можем использовать понятие каскадов.

По умолчанию NHibernate не связывает операции с дочерними объектами.

Причина этого заключается в том, что у вас могут быть такие отношения, как клиент, имеющий адрес доставки по умолчанию, и этот адрес доставки используется многими разными клиентами.

Таким образом, вы не хотели бы каскадно связывать эти отношения, потому что другие клиенты все еще ссылаются на них.

Таким образом, вся идея каскадов состоит в том, чтобы рассказать NHibernate, как обращаться с дочерними объектами.

Мы также могли бы нарушать ограничения внешнего ключа, поэтому мы можем использовать понятие каскадов.

По умолчанию NHibernate не связывает операции с дочерними объектами.

Причина этого заключается в том, что у вас могут быть такие отношения, как клиент, имеющий адрес доставки по умолчанию, и этот адрес доставки используется многими разными клиентами.

Таким образом, вы не хотели бы каскадно связывать эти отношения, потому что другие клиенты все еще ссылаются на них.

Таким образом, вся идея каскадов состоит в том, чтобы рассказать NHibernate, как обращаться с дочерними объектами.

Существуют разные варианты каскадирования, а именно:

none — это значение по умолчанию и это означает отсутствие каскадирования.

все, что собирается каскадом, сохраняет, обновляет и удаляет.

save-update — это будет каскад, сохранения и обновления.

удалить — это будет каскадное удаление.

all-delete-orphan — это специальный файл, который довольно часто используется и такой же, как All Except, если он находит строки Delete-orphan, он также удаляет их.

none — это значение по умолчанию и это означает отсутствие каскадирования.

все, что собирается каскадом, сохраняет, обновляет и удаляет.

save-update — это будет каскад, сохранения и обновления.

удалить — это будет каскадное удаление.

all-delete-orphan — это специальный файл, который довольно часто используется и такой же, как All Except, если он находит строки Delete-orphan, он также удаляет их.

Давайте рассмотрим простые примеры каскадов, давайте исправим проблему в программе, где мы должны вручную каскадировать сохранение заказов, как показано в следующем коде.

В приведенном фрагменте кода видно, что мы вручную сохраняем все заказы для клиента. Теперь давайте удалим ручной каскадный код, в котором сохранены все заказы.

Теперь заказы полностью принадлежат заказчику. Таким образом, если клиенты были удалены из базы данных, наше приложение хотело бы удалить все эти заказы, включая любые, которые могли быть осиротевшими.

Это в конечном итоге делает удаление. При этом будет указано удаление из таблицы заказов, где идентификатор клиента равен клиенту, которого вы удаляете.

Теперь заказы полностью принадлежат заказчику. Таким образом, если клиенты были удалены из базы данных, наше приложение хотело бы удалить все эти заказы, включая любые, которые могли быть осиротевшими.

Это в конечном итоге делает удаление. При этом будет указано удаление из таблицы заказов, где идентификатор клиента равен клиенту, которого вы удаляете.

Теперь, когда вы запустите это приложение, вы увидите следующий вывод.

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

Поэтому, в зависимости от ваших отношений, вы можете каскадировать их. Теперь давайте посмотрим на другие каскадные отношения. Давайте перейдем к файлу Order.hbm.xml, и мы сможем связать это соотношение многие-к-одному.

Поэтому, если мы создаем новый заказ, к нему присоединяется новый клиент и мы говорим: сохраните этот заказ, возможно, мы захотим это сделать каскадно. Но одна вещь, которую мы, вероятно, не хотели бы делать, — это удаление заказа для удаления соответствующего клиента.

Итак, здесь мы хотели бы сделать обновление для сохранения, поэтому с помощью сохранения-обновления оно будет каскадно сохранять или сохранять любые обновления для этого клиента. Так что, если мы получим нового клиента или поменяем клиента, это будет каскадным. Если это удаление, оно не удалит это из базы данных.

Итак, запустив наше приложение снова, все по-прежнему работает, как и ожидалось.

Теперь вы должны взглянуть на свое приложение, помните, что по умолчанию установлено значение «Нет», и вам нужно подумать о ваших сущностях и ваших отношениях между ними, чтобы определить соответствующие каскады для каждой из ваших сущностей, а также для каждого из ваших отношений в этой базе данных.

Источник

Русские Блоги

Обучающие каскадные свойства Hibernate Framework

предисловие

Мы используем взаимосвязь между отделом и сотрудником в качестве примера для объяснения сопоставления отношений «один ко многим». При удалении отдела, если у отдела есть связанные сотрудники, а обратный атрибут имеет значение «ложь», то, поскольку отношение ассоциации может поддерживаться, оно сначала связывает отношение сотрудника. Задайте для столбца внешнего ключа нулевое значение, а затем удалите себя. Однако, если вы хотите удалить отдел в этот момент, все сотрудники в отделе будут удалены. В это время вам необходимо ввести атрибут каскада.

Каскад (каскад)

Когда Hibernate сохраняет временный объект, по умолчанию он не сохраняет автоматически другие временные объекты, связанные с ним, но генерирует исключение TransientObjectException. Если вы установите атрибут каскада элемента многие-к-одному для сохранения-обновления, вы сможете добиться автоматического сохранения связанных объектов. Такие как:

Каскадирование относится к тому, выполняет ли связанный объект (пассивная сторона) ту же самую операцию синхронно, когда мастер выполняет операцию.

Каскадный стиль

Основные операции каждого сеанса Hibernate включают в себя persist (), merge (), сохранение OrUpdate (), delete (), lock (), refresh (), evict (), replicate (), эти операции имеют соответствующий каскадный стиль ( каскадный стиль). Эти каскадные стили называются постоянными, объединяются, сохраняются-обновляются, удаляются, блокируются, обновляются, выселяются и реплицируются соответственно.

Каскадный стильМетоды в сеансе
persistpersist()
mergemerge()
save-updatesave()、update()、saveOrUpdate()
deletedelete()
locklock()
refreshrefresh()
evictevict()
replicatereplicate()

Если вы хотите, чтобы операция была связана каскадом по отношению, вы должны указать это в файле отображения.

Укажите каскадный стиль:

Каскадный стиль (каскадный стиль) является составным:

Вы можете использовать cascade = ”all”, чтобы указать, что все операции каскадируются по отношению. Значением по умолчанию является cascade = «none», то есть никакая операция не будет каскадной.

В файле объектного / реляционного отображения элементы, используемые для сопоставления отношений между постоянными классами, такие как 、 и У всех есть каскадный атрибут.

Что такое каскадность как она используется в hibernate. Смотреть фото Что такое каскадность как она используется в hibernate. Смотреть картинку Что такое каскадность как она используется в hibernate. Картинка про Что такое каскадность как она используется в hibernate. Фото Что такое каскадность как она используется в hibernate

Пример каскадных атрибутов

Давайте вернемся к требованию, которое мы упоминали в начале, то есть при удалении отдела все сотрудники в этом отделе удаляются. Чтобы удовлетворить это требование, нам лучше создать общий проект Java, такой как Hibernate_Test, а затем создать новые постоянные классы-Department.java и Employee.java в пакете cn.itcast.f_hbm_oneToManyb.
Код персистентного класса-Department.java выглядит следующим образом:

Поскольку ранее мы проанализировали структуру таблицы, соответствующей каждому постоянному классу в базе данных, мы можем легко написать соответствующий файл конфигурации сопоставления для каждого постоянного класса.
Сначала создайте файл конфигурации сопоставления, соответствующий классу Department-Department.hbm.xml отдела в пакете cn.itcast.f_hbm_oneToMany.

в Установите в элементе cascade = «save-update, delete», что означает, что необходимо сохранить / изменить только отдел, все сотрудники отдела будут сохранены / изменены, или при удалении отдела все сотрудники отдела будут присоединены Удалены.
Затем также создайте файл конфигурации сопоставления, соответствующий классу Employee-Employee.hbm.xml в этом пакете.

Далее мы пишем код для тестирования из следующих двух аспектов:

Поэтому мы должны написать модульный тест class-Application.java в пакете cn.itcast.f_hbm_oneToMany.

Таким образом, при сохранении отдела все сотрудники в отделе будут каскадными, а при удалении отдела все сотрудники в отделе будут каскадными. Мы также можем настроить сотрудников для каскадного удаления отделов, но в бизнесе это крайне неразумно.

резюме

Как правило, каскадирование не используется для «многие к одному» и «многие ко многим». «Один к одному» и «один ко многим» могут использовать каскадирование. В этих двух случаях каскадирование более распространено. Таким образом, этот объект находится под вашим контролем. Может быть каскадно, это не принадлежит вам одному, то вы не каскад.
Еще одна вещь, на которую следует обратить внимание, это то, что обратный атрибут и атрибут каскада вообще не связаны.

Источник

Руководство для начинающих по JPA и Hibernate Cascade Types

Вступление

JPA против Hibernate Каскад Типы

Hibernate поддерживает все каскадные типы JPA и некоторые дополнительные устаревшие каскадные стили. В следующей таблице показана связь между типами JPA Cascade и их эквивалентом в Hibernate:

JPA EntityManager actionJPA CascadeTypeHibernate родной Session actionСпящий родной CascadeTypeСлушатель событий
открепление (юридическое лицо)DETACHвыселить (юридическое лицо)DETACH или

выселять

Прослушиватель событий Evict по умолчанию
Слияние (организация)MERGEСлияние (организация)MERGEПрослушиватель событий слияния по умолчанию
упорствовать (юридическое лицо)PERSISTупорствовать (юридическое лицо)PERSISTПостоянный прослушиватель событий по умолчанию
обновления (юридическое лицо)ОБНОВЛЕНИЕобновления (юридическое лицо)ОБНОВЛЕНИЕОбновление прослушивателя событий по умолчанию
удалить (юридическое лицо)УДАЛЯТЬудаление (юридическое лицо)УДАЛИТЬ или УДАЛИТЬПрослушиватель событий удаления по умолчанию
saveOrUpdate (юридическое лицо)SAVE_UPDATEСохранить или обновить прослушиватель событий по умолчанию
replicate (entity, replicationMode)REPLICATEПрослушиватель событий репликации по умолчанию
блокировка (entity, lockModeType)buildLockRequest (entity, lockOptions)ЗАМОКПрослушиватель событий блокировки по умолчанию
Все вышеперечисленные методы EntityManagerВСЕВсе вышеперечисленные методы Hibernate SessionВСЕ

Из этой таблицы можно сделать вывод, что:

Каскадные лучшие практики

Каскадирование имеет смысл только для родительско- дочерних ассоциаций (переход состояния родительского объекта каскадно связан с его дочерними объектами). Каскадирование от Ребенка к Родителю не очень полезно и, как правило, это запах кода отображения.

Далее я собираюсь проанализировать каскадное поведение всех ассоциаций родительского и дочернего JPA.

Один к одному

Наиболее распространенная двусторонняя ассоциация «один к одному» выглядит следующим образом:

Источник

Hibernate: каскадные обновления, инверсия отношений и прочая и прочая

Вот пришло время и мне написать пару строчек про hibernate. Я попробую сделать небольшой cheatsheet по вопросу двусторонней ассоциации, каскадных обновлений, ленивой загрузки и прочего и прочего. Сразу предупрежу, что я довольно негативно отношусь к hibernate, предпочитаю в практике использовать ibatis. Может, причиной является мой опыт в проектировании БД, и я всегда предпочитаю идти именно от базы к модели классов java, а не наоборот. Большинство проблем, которые возникают у новичков заключается в том, что они забывают, что база данных живет по другим правилам, чем слой объектов. В СУБД нет всех этих двусторонних связей, да и в понятие каскадных обновлений вкладывается немного другой смысл. Естественно, что я не исключаю ситуации, “что фокусник был пьян и фокус не удался”, так что ваши замечания будут для меня полезны. Несмотря на то, что я излагаю пример на базе mysql, я полагаю, что основные идеи и выводы будут применимы для любой СУБД. Одним словом, поехали:

Что такое каскадность как она используется в hibernate. Смотреть фото Что такое каскадность как она используется в hibernate. Смотреть картинку Что такое каскадность как она используется в hibernate. Картинка про Что такое каскадность как она используется в hibernate. Фото Что такое каскадность как она используется в hibernate

Так я создаю таблицу “отделы”:

Обратите внимание, что на уровне базы нет никаких упоминаний, что есть связь между отделом и сотрудниками, и это очень логично, ведь именно сотрудники заинтересованы в том, чтобы быть привязанными к отделам. Всегда зависимая, подчиненная сторона ссылается на главную таблицу. Теперь код создающий таблицу “сотрудники”:

Теперь давайте разберемся с приведенным выше кодом. В целом все просто: две таблицы, в каждой первичный ключ, тестовое поле. В таблицу employee добавлено поле, типа целое число, для этого поля запрещено вводить значение поля равное NULL (т.е. вот оно то ограничение, о котором я говорил, что сотрудников без отдела быть не может, любая попытка очистить поле “номер отдела будет блокироваться на уровне базы данных”). В случае, если правка выполняется в подчиненной таблице (сотрудники), то при выполнении операции вставки или модификации записи mysql проверят то, чтобы в главной таблице была запись с таким номером отдела, на который я пытаюсь сослаться при вставке подчиненно записи. в следующем примере я нарушил это правило, попытавшись добавить сотрудника “Тома” в отдел номер 2 и это вызвало ошибку. Важно, что mysql не делает никаких предположений на предмет того, что “ну и что что отдела номер два нет, я может через секунду его добавлю, честное-честное”. Порядок внесения изменений должен быть таким: сначала добавим запись в главную таблицу и только затем в подчиненную. Когда мы будем писать hibernate код, то это ограничение будет для нас важным.

Так, а что такое “БЛА-БЛА-БЛА”? А это модификаторы, управляющие тем, как mysql будет выполнять операции связанные с модификацией записей в главной таблице. Ведь после правки главной записи, мы не должны допустить того, что в подчиненной таблице возникли потерянные ссылки, указывающие на устаревшую информацию. В mysql есть следующие модификаторы:

CASCADE. Каскадные обновления. В коде sql записываются так:

Значит, любая правка в главной таблице будет приводить к немедленному изменению в подчиненной таблице. Так удаление главной записи будет приводить к автоматическому удалению всех сотрудников отдела. Изменение номера отдела (например, с 2 на 3) приведет к тому, что во все карточки сотрудников также будет внесена правка и значение поля fk_department_id станет равным 3.

SET NULL. При изменении главной записи в подчиненной таблице значение поля fk_department_id для всех затронутых изменением отдела сотрудников станет равным null. Соотвественно, такой режим возможен лишь тогда, когда поле fk_department_id было объявлено с модификатором NULL. Явно не наш случай.

NO ACTION. Запрет на выполнение операции. Фактически если я хочу удалить или перенумеровать отдел, то выполнить это не возможно до тех пор, пока у отдела есть сотрудники. Что же довольно логично: перед расформированием отдела нужно предварительно разобраться с его сотрудниками, например, удалить или перевести в другой отдел.

Такое же поведение, как и NO ACTION вызывает RESTRICT. Точно такое же поведение происходит и тогда, когда я не указываю явно какой-либо из модификаторов.

Последний модификатор: SET DEFAULT. Фактического значения не имеет т.к. mysql игнорирует данное выражение.

Теперь приведем код java классов: отдел и сотрудник.

/**
* Отдел
*/
public class Department <
Integer department_id;
String caption;
Set employies = new HashSet () ;

public Integer getDepartment_id () <
return department_id;
>

public String getCaption () <
return caption;
>

public Set getEmployies () <
return employies;
>

А теперь сотрудник:

/**
* Сотрудник
*/
public class Employee <
Integer employee_id;
String fio;
Department department;

public Integer getEmployee_id () <
return employee_id;
>

public String getFio () <
return fio;
>

public Department getDepartment () <
return department;
>

Как видите ничего сложного: в классе сотрудника есть поле Department играющее роль ссылки на отдел в котором трудится наш герой, в таблице Employee есть ссылка на список (set) сотрудников зачисленных в отдел.

Теперь переходим к написанию правил отображений “реляция-классы-и-обратно”:

Сначала отображение для отдела:

Теперь отображение для сотрудника:

Как внешние ключи были объявлены сразу две колонки: id и department и это не хорошо. Правильный вариант должен быть таким:

И тут самое время задуматься: а где здесь модификаторы ON DELETE CASCADE или ON DELETE SET NULL – ничего нет. Может быть, нужно написать какое-то ключевое слово где-нибудь в конфигурационном файле? Не нужно, да и нет такого секретного места. Не забывайте что hibernate это инструмент универсальный и умеющий работать не только с mysql, но postgres, mssql … И подобные on delete … могут быть не поддерживаемыми и не иметь какого-то встроенного аналога для какой-то СУБД. Тот же mysql поддерживает внешние ключи только для движка innodb, а Microsoft sql server 7.0 (это было еще до 2000), каскадные операции также делать не умел, и приходилось их делать с помощью триггеров (вот такие были темные времена).

Так значит, что мы должны будем заботиться об удалении и изменении подчиненных записей сами? Нет не должны: hibernate сделает это и многое другое за нас. Надо только правильно настроить каскадные операции (и как вы уже поняли, что эти каскадные операции с каскадами в СУБД не имеют ничего общего). Но сначала пример:

ses.saveOrUpdate ( managers ) ;
ses.saveOrUpdate ( jim ) ;

По крайней мере, именно, так рекомендуют поступать в разных книжках: создали два объекта – сотрудник и отдел, привязали их друг к другу (два раза: и отдел к сотруднику и сотрудника к отделу). После сохранения проверили таблицы в СУБД: да новые записи появились и выглядят просто прекрасно.

Как видите, значение поля fk_department_id в таблице сотрудников равно 1 (отдел менеджеров).

Теперь попробуем другой сценарий: все как раньше но при сохранении я укажу другой порядок сохранения объектов: сначала сотрудника, затем отдел. Тут я надеюсь, что будет ошибка: ведь на уровне базы стоит запрет, что нельзя сохранить сначала сотрудника (ссылающегося на еще не существующий отдел), и только затем сам отдел:

Правда, первая часть ошибки не про нас: ведь значение поля department у нашего Джима ну никак не может быть равным null, а вот что значит слово transient? А значит, что при сохранении Джима, hibernate не смог этого выполнить т.к. объект department еще не был сохранен – все как и ожидалось. Хоть и не приятно, но понятно. Теперь такой эксперимент:

А вот это странно: как это hibernate не смог сохранить отдел из-за того, что в нем есть сотрудник которого мы не сохранили. Ну ладно, неприятно, но ведь можно запомнить, что сохранять объекты нужно всегда парами и всегда в нужно порядке. Считаем, что как-будто проблему мы решили и смело идем дальше.

А как насчет удаления отдела или сотрудника?

После завершения первой транзакции я снова начинаю ее, загружаю отдел под номером 1 и пытаюсь удалить его:

И снова получаю ошибку:

Да вспоминаю, действительно я поставил ограничение на то, что значение поля fk_department_id не может быть null, но причем здесь именно такая ошибка? Ведь я хотел удалить отдел … Стоп, от отдела зависит сотрудник. Нельзя удалить отдел, не сделав что-то предварительное с сотрудником, ведь иначе будет нарушена целостность СУБД (те самые внешние ключи, про которые я рассказывал в начале статьи). А теперь главный вопрос: как вы думаете, что должен сделать hibernate с сотрудником, когда его отдел удаляется? Похоже, он решил, что нужно отчислить сотрудника из отдела, но с работы не увольнять. Отлично, давайте немного поиграемся и сделаем два варианта: перед удалением отдела мы переведем всех его сотрудников из одного отдела в другой, а во второй раз, удалим сотрудников перед уничтожением отдела:

В следующем примере я для полной красоты добавил два отдела (дизайнеры и менеджеры) и трех человек персонала, первоначально зачисленных в отдел менеджмента, затем они должны быть переведены к дизайнерам:

jim.setDepartment ( managers ) ;
tom.setDepartment ( managers ) ;
ron.setDepartment ( managers ) ;

ses.saveOrUpdate ( managers ) ;
ses.saveOrUpdate ( designers ) ;
ses.saveOrUpdate ( jim ) ;
ses.saveOrUpdate ( tom ) ;
ses.saveOrUpdate ( ron ) ;

Смотрится довольно ужасно (супер-цикл удаляющий людей из одного отдела и переносящий их в другой), но, тем не менее, все работает. Точно также, циклом, я могу выполнить и предварительное удаление сотрудников перед удалением отдела.

А-а-а. не получилось. Снова это гадкое исключение:

Ну, как же мне от тебя избавиться?!

Удалить отдел вместе с сотрудниками я не смог, только переместить в другой отдел. Более того, непонятно зачем столько городить проблем с этим hibernate, если только на старом добром sql перемещение сотрудников из одного отдела в другой занимало одну строчку кода. А может быть все не так уж и просто? И hibernate может взять на себя эти рутинные действия? В одной замечательной книжке есть следующая фраза:

Ну, по поводу десяти возможных вариантов каскадных операций это они отстали от жизни (правда не намного, в моей версии hibernate (3.2) операций 13, вместе с none). Но в целом, да, действительно я могу пометить некоторую ассоциацию как cascade, и операции над родительским объектом будут распространяться и на связанную сущность. Пробуем:

В файле department.hbm.xml я заменил декларацию set-а на следующую:

Запись cascade=”all” означает, что при сохранении, обновлении и удалении родительского объекта изменения будут распространены и на дочерний объект. Каскадные операции установлены у меня с обоих сторон, поэтому для сохранения графа объектов достаточно будет сохранить хотя бы одну сущность:

Session ses = factory.openSession () ;
ses.beginTransaction () ;

Employee jim = new Employee ( «jim» ) ;
Employee tom = new Employee ( «tom» ) ;
Employee ron = new Employee ( «ron» ) ;

jim.setDepartment ( managers ) ;
tom.setDepartment ( managers ) ;
ron.setDepartment ( managers ) ;

В примере я сохранаю одного Джима, но раз он включен в отдел (и есть модификатор связи cascade=all), то будет и сохранен отдел менеджеров, а раз внутри отдела менеджеров есть некоторый набор сотрудников (tom, ron), то будут сохранены и они. Если пометка cascade=all будет одна, то поведение меняется:

Поведение, которое будет, если убрать каскад от сотрудника к отделу.

И поведение, когда наоборот, убран каскад от отдела к сотруднику.

jim.setDepartment ( managers ) ;
tom.setDepartment ( managers ) ;
ron.setDepartment ( managers ) ;

ses.saveOrUpdate ( jim ) ; // сохранение сотрудника заставляет сохранить и его отдел

Теперь перейдем от сохранения объекта к его удалению и посмотрим нету ли там подводных камней (для полного удобства я включил каскадные отношения на обоих сторонах ассоциации):

Запускаем и снова получаем исключение:

Ну никак у нас не получается начать работать с hibernate и отойти в сторону от sql. Пора начать разбираться какую последовательность запросов посылает на сервер hibernate и в каком месте он решил выполнить установку значения поля ‘fk_department_id’ на null.

Для этого я в корень своего classpath поместил файли log4j.properties (не забудьте подключить и библиотеку log4j). В нем я включаю журналирование:

Запускаем приложение еще раз и наблюдаем странную картину:

Линией я разделил код который вставляет записи и который пытается их удалить. С самого первой строки начинают расти подозрения, что что-то не так, вот например операция сохранения отделов и сотрудников:

Вызвала следующие шаги:

Операция удаления также очень странная: первым шагом идет обновление таблицы сотрудников, а не удаление из нее Джима (как на то мог бы я надеяться, поставив cascade=all).

Может быть, нужно удалить Джима явно, перед удалением отдела?

Department managers_2 = ( Department ) ses.load ( Department.class, 1 ) ;
Employee jim_2= ( Employee ) ses.load ( Employee.class, 1 ) ;
ses.delete ( jim_2 ) ;
ses.delete ( managers_2 ) ;

Нет, снова то же самое исключение во время ненужного обновления таблицы сотрудников. Ах да, я ведь забыл, что у нас есть связи между Джимом и отделом, давайте избавимся от них.

Запустили, получили исключение, правда, уже другое:

Ну, это еще понятно: ведь я пытаюсь установить значение поля department для Джима равным null. Надо сказать, что созданное в файле маппинга ограничение проверяется при любой операции синхронизации сессии с базой данных, и если значение поля depertment равно null, то операция отменяется, несмотря на то, что возможно на Джима есть ссылка из отдела – это не имеет значения. Собственно говоря, такое поведение ожидаемое и понятно. Вот только зачем hibernate пытается выполнить это действие, ведь через секунду нашего Джима вместе с отделом не станет. Может выполнить явно удаление Джима?

Собственно, все эти проблемы из-за того, что при создании файла маппинга, я задал ограничение на значение поля fk_department_id, чтобы оно было не равно null. Если я уберу этот запрет, то все примеры заработают, пусть не идеально, пусть будет выполняться обновление таблицы сотрудников с установкой значения поля fk_department_id в null, чтобы через секунду удалить этого сотрудника, но все будет работать. И если бы я мог спроектировать с нуля приложение и базу для него, то так бы и сделал, плюнул на ограничения not-null, надеясь, что с базой будут работать только через мою программу и никак иначе. Но нельзя, особенно, если база есть, она была унаследована, есть зоопарк софта работающий с данными и ограничения на уровне базы данных – это последний бастион защиты, и ломать его в угоду тому, что новому “hibernate-танку” неудобно выезжать через ворота – глупо.

Как вывод: нам нужно подстроить hibernate под правила Б.Д. Начнем с того, что поймем, что в базах данных нет двусторонних связей, таких как я спроектировал ранее. Связь реализуется от подчиненного к главному и точка. Например, когда я выполняю типовое назначение сотрудника в отдел, то делаю так:

То hibernate считает, что у нас есть две связи, а база данных считает, что связь одна. Именно из-за этого различия в логах sql-команд посылаемых на сервер присутствуют дубляжи: сначала вставим сотрудника в отдел X, затем обновим значение номера отдела (уже правильное) на его же самого.

Мы должны сообщить hibernate-у, что эти две связи есть единое целое и одна сторона является отражением другой. Для этого используют модификатор inverse. Им помечается ассоциация находящаяся на строне Один. Если у нас связь не один ко многим, а многие ко многим, то помечать можно любую из сторон, но об этом я расскажу подрбнее попозже. Итак, новый вид файла маппинга для отдела будет выглядеть так:

Пробуем, сначала сохранение:

Или так, от перестановки слагаемых сумма не изменилась (ведь у сотрудника, равно как и у отдела стоит пометка cascade=”all” и связанные объекты будут сохранены).

Более того, пропали дублирующиеся sql-запросы, теперь журнал выглядит следующим образом:

Однако перед тем как мы восхитимся и перейдем к рассмотрению операции удаления, давайте сделаем еще один эксперимент:

Employee jim = new Employee ( «jim» ) ;
Employee tom = new Employee ( «tom» ) ;
Employee ron = new Employee ( «ron» ) ;
Department managers = new Department ( «managers» ) ;

// я не могу не назначить сотруднику отдел т.к. у меня стоит ограничение not-null
// и оно проверяется еще до отправки данных в СУБД
jim.setDepartment ( managers ) ;
tom.setDepartment ( managers ) ;
ron.setDepartment ( managers ) ;

Смотрим журнал сообщений:

Employee jim = new Employee ( «jim» ) ;
Employee tom = new Employee ( «tom» ) ;
Employee ron = new Employee ( «ron» ) ;

Department managers = new Department ( «managers» ) ;
Department designers = new Department ( «designers» ) ;

jim.setDepartment ( managers ) ;
tom.setDepartment ( managers ) ;
ron.setDepartment ( designers ) ;

Обратите внимание: в отдел менеджеров через set был добавлен ron, но у него самого связь идет на отдел дизайнеров. Смотрим журнал запросов:

Ха, как и ожидалось. При сохранении Джима, мы начали сохранять его отдел (менеджеров). В отделе менеджеры мы нашли Тома (с ним все просто) и Рона. А вот сохраняя Рона, мы начали с того, что сохранили отдел дизайнеров, в который включен Рон, и затем сохраняя самого Рона, установили ему связь на отдел дизайнеров. Тот факт что Рон входит в список сотрудников отдела менеджмент был проигнорирован (спасибо inverse=»true»).

На этом с сохранением все, переходим к удалению.

Думаете на этом все, как бы не так – неаккуратных и невнимательных везде ждут сюрпризы. Давайте удалим одного единственного Джима.

Те же действия, что и в прошлый раз. Дело в том, что мы не осмотрительно поставили каскадные действия на все операции (в том числе и удаление) от сотрудника к отделу. И если сохранение было удобным и полезным, то с удалением мы попали в просак. Удаление Джима инициировало удаление отдела. Удаление отдела привело к тому, что были удалены все его сотрудники. Решением будет изменить отображение многие-к-одному на следующее:

Проверяем: все работает. На сегодня хватит, спасибо за внимание.

Небольшое дополнение: не забывайте, что при подтверждении транзакции все объекты, которые прошли через сессию (были загружены или созданы) будут проверяться на предмет «истинной духовной чистоты», т.е. были или не были ли они изменены. В последнем примере я удаляю Джима загружая его ОДНОГО из сессии (сессия у меня новая) и ссылка на отдел через сессию не проходит (помним, что по-умолчанию в hibernate свойства many-to-one равно как и коллекции загружаются «лениво»). Поэтому при удалении все получилось отлично. Но вот если сделать так:

То возникнет ошибка:

Т.к. у меня установлено каскадное сохранение всех подчиненных к отделу сотрудников, то hibernate не может удалить сотрудника, ведь на него есть ссылка из отдела (в раннем примере о том, что есть отдел и связь от него к сотрудникам hibernate не догадывался). А раз есть ссылка на сотрудника, то он попросит сотрудника об cascade-сохранении и сотрудник попадет в базу данных. Так что при удалении сотрудника, не забывайте очистить ссылку на его из коллекции сотрудников отдела.

Написав эту строчку я задумался, что фактически можно так настроить hibernate, чтобы он удалил сотрудника из отдела только на основании того факта, что его нет в перечне employee. В начале я планировал опустить вопрос удаления «сирот» и подробнее рассказать о нем в теме посвященной коллекциям, но раз начал, так начал. Среди режимов каскадного распространения «операции» от родительского объекта к ассоциированным с ним (ко всем элементам коллекции или объекту-владельцу), режим cascade=»all» не является самым «большим». Есть еще и режим cascade=»all-delete-orphan». В докуметации про него говорят так: все что было у cascade=»all», плюс, автоматическое удаление всех тех, кто не принадлежит коллекции. Фактически записав ассоциацию так:

Я могу удалить сотрудника Джима, следующим образом (обратите внимание, что явного обращения session.delete нет):

Employee jim = new Employee ( «jim» ) ;
Employee tom = new Employee ( «tom» ) ;
Employee ron = new Employee ( «ron» ) ;

Department managers = new Department ( «managers» ) ;

jim.setDepartment ( managers ) ;
tom.setDepartment ( managers ) ;
ron.setDepartment ( managers ) ;

Причина в общем-то дурацкая: когда я сказал сохранить Джима, то hibernate определелил, что нужно сохранить отдел, в котором числится наш Джим. Сохранив отдел, он тут же решил проверить его на «моральную целостность», найти может быть какие-то Сотрудники уже были отчислены из этого отдела (ага, неуспели отдел создать, а сотрудники из него так и бегут). Ну и в ходе этой проверки обнаружил что в отделе числится Джим, который еще не успели сохранить, такого наш hibernate не смог переварить и выбросил исключение.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *