Cascade Types (пример на Hibernate и Spring Boot)
В этой статье рассмотрим самые популярные каскадные операции на примере отношения OneToMany (хотя их возможно использовать также в OneToOne и ManyToMany).
Модель OneToMany
Рассмотрим пример топика с комментариями. Они находится в отношении OneToMany: у одного топика может быть много комментариев, а каждый комментарий, в свою очередь, относится ровно к одному топику. В базе данных для них создаются две таблицы:

Топик является родительской сущностью в том смысле, что комментарий сам по себе не существует, а всегда относится к топику. Его жизненный цикл привязан к топику. При удалении топика надо удалять из базы и его комментарии, а при сохранении топика — сохранять и его комментарии. Тут то и уместны каскадные операции — то есть те, которые распространяются и на дочернюю сущность.
Каскадные операции указываются над ссылкой на дочернее отношение, у нас это 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.
Обзор типов каскадов JPA/Hibernate
Краткий и практический обзор типов каскадов JPA/Hibernate.
1. введение
В этом уроке мы обсудим, что такое каскадирование в JPA/Hibernate. Затем мы рассмотрим различные доступные типы каскадов, а также их семантику.
Дальнейшее чтение:
Введение в весенние данные JPA
Сопоставление имен классов сущностей с именами таблиц SQL с помощью JPA
2. Что Такое Каскадирование?
Отношения сущностей часто зависят от существования другой сущности — например, отношения Person – |/Address . Без Person , Адрес сущность не имеет никакого собственного значения. Когда мы удаляем Лицо сущность или Адрес сущность также должны быть удалены.
Каскадирование-это способ достичь этого. Когда мы выполняем какое-либо действие над целевым объектом, то же самое действие будет применено к связанному объекту.
2.1. Каскадный тип JPA
Все каскадные операции, специфичные для JPA, представлены javax.persistence.CascadeType Перечисление, содержащее записи:
2.2. Каскадный тип гибернации
Hibernate поддерживает три дополнительных типа каскадов, а также те, которые указаны в JPA. Эти типы каскадов для гибернации доступны в org.hibernate.annotations.Каскадный тип :
ЗАМОК
3.1. Каскадный тип.ВСЕ
Каскад.ALL распространяет все операции, включая операции, связанные с гибернацией, от родительского объекта к дочернему.
Давайте рассмотрим это на примере:
Обратите внимание, что в OneToMany ассоциации, мы упомянули каскадный тип в аннотации.
Теперь давайте посмотрим на связанную сущность Адрес :
3.2. Каскадный тип.НАСТАИВАТЬ
Давайте рассмотрим тестовый случай для постоянной операции:
Когда мы запустим приведенный выше тестовый случай, мы увидим следующий SQL:
3.3. Каскадный тип.ПОГЛОЩАТЬ
Давайте протестируем операцию слияния:
Когда мы запускаем приведенный выше тестовый случай, операция слияния генерирует следующий SQL:
3.4. Каскадный тип.УДАЛИТЬ
Как следует из названия, операция удаления удаляет строку, соответствующую сущности, из базы данных, а также из постоянного контекста.
Теперь пришло время проверить CascadeType.Remove :
Когда мы запустим приведенный выше тестовый случай, мы увидим следующий SQL:
3.5. Каскадный тип.ОТСОЕДИНИТЬ
Давайте посмотрим на это в действии:
Давайте посмотрим тестовый пример, чтобы понять CascadeType.ЗАМОК :
3.7. Каскадный тип.ОБНОВЛЕНИЕ
В таком сценарии это может быть полезно. Когда мы используем эту операцию с CascadeType REFRESH , дочерняя сущность также перезагружается из базы данных всякий раз, когда обновляется родительская сущность.
Для лучшего понимания давайте рассмотрим тестовый пример для CascadeType.REFRESH :
Здесь мы внесли некоторые изменения в сохраненные сущности person и
Здесь мы внесли некоторые изменения в сохраненные сущности || person || и
Теперь давайте проверим CascadeType. РЕПЛИЦИРОВАТЬ :
Из-за CascadeType |/REPLICATE , когда мы реплицируем person entity, его связанный адрес также реплицируется с заданным нами идентификатором.
3.9. CascadeType.SAVE_UPDATE
CascadeType.SAVE_UPDATE распространяет ту же операцию на связанную дочернюю сущность. Это полезно, когда мы используем операции Hibernate, такие как save, update, и saveOrUpdate .
Давайте посмотрим CascadeType. SAVE_UPDATE в действии:
4. Заключение
В этой статье мы обсудили каскадирование и различные варианты каскадного типа, доступные в JPA и Hibernate.
Руководство для начинающих по JPA и Hibernate Cascade Types
Вступление
JPA против Hibernate Каскад Типы
Hibernate поддерживает все каскадные типы JPA и некоторые дополнительные устаревшие каскадные стили. В следующей таблице показана связь между типами JPA Cascade и их эквивалентом в Hibernate:
| JPA EntityManager action | JPA CascadeType | Hibernate родной 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 JPA Cascade Types
Last Updated: December 26, 2020
We learned about mapping associated entities in hibernate already in previous tutorials such as one-to-one mapping and one-to-many mappings. There we wanted to save the mapped entity whenever relationship owner entity got saved. To enable this we had use “CascadeType” attribute. In this JPA Cascade Types tutorial, we will learn about various type of available options for cascading via CascadeType.
How JPA Cascade Types Work?
Before moving forward, let’s look at how this cascade type attribute is defined in your code. Let’s have an example for more clear understanding. Take a scenario where an Employee can have multiple Accounts; but one account must be associated with only one employee. Let’s create entities with minimum information for sake of clarity.
EmployeeEntity.java
AccountEntity.java
But what if we only want to cascade only save operations but not delete operation. Then we need to clearly specify it using below code.
Now only when save() or persist() methods are called using employee instance then only accounts will be persisted. If any other method is called on session, it’s effect will not affect/cascade to accounts.
JPA Cascade Types
The cascade types supported by the Java Persistence Architecture are as below:
There is no default cascade type in JPA. By default no operations are cascaded.
The cascade configuration option accepts an array of CascadeTypes; thus, to include only refreshes and merges in the cascade operation for a One-to-Many relationship as in our example, you might see the following:
Above cascading will cause accounts collection to be only merged and refreshed.
Hibernate Cascade Types
Now lets understand what is cascade in hibernate in which scenario we use it.
Apart from JPA provided cascade types, there is one more cascading operation in hibernate which is not part of the normal set above discussed, called “orphan removal“. This removes an owned object from the database when it’s removed from its owning relationship.
Let’s understand with an example. In our Employee and Account entity example, I have updated them as below and have mentioned “orphanRemoval = true” on accounts. It essentially means that whenever I will remove an ‘account from accounts set’ (which means I am removing the relationship between that account and Employee); the account entity which is not associated with any other Employee on database (i.e. orphan) should also be deleted.
EmployeeEntity.java
AccountEntity.java
TestOrphanRemovalCascade.java
It’s a very good way of removing the matching/mismatching items from a collection (i.e. many-to-one or one-to-many relationships). You just remove the item from collection and hibernate take care of rest of the things for you. It will check whether entity is referenced from any place or not; If it is not then it will delete the entity from database itself.
Let me know of your thoughts and questions on hibernate 5 cascade types or JPA cascade types, if any.
Руководство для начинающих по типам каскадов JPA и гибернации
JPA переводит переходы состояния сущности в базу данных DML операторы. Поскольку обычно используется для работы с графами сущностей, JPA позволяет нам распространять изменения состояния сущностей от Родителей к Дочерним сущностям.
Hibernate поддерживает все типы каскадов JPA и некоторые дополнительные устаревшие стили каскадирования. В следующей таблице показана связь между типами JPA Каскад и их Гибернацией собственным API эквивалентом:
| Прослушиватель событий выселения по умолчанию | ОТСОЕДИНИТЬ | отсоединить(сущность) | ОТСОЕДИНИТЬ или ВЫСЕЛИТЬ | выселить(юридическое лицо) |
| Прослушиватель событий слияния по умолчанию | ПОГЛОЩАТЬ | слияние(сущность) | ПОГЛОЩАТЬ | слияние(сущность) |
| Прослушиватель постоянных событий по умолчанию | НАСТАИВАТЬ | сохраниться(сущность) | НАСТАИВАТЬ | сохраниться(сущность) |
| Прослушиватель событий Обновления по умолчанию | ОСВЕЖИТЬ | обновить(сущность) | ОСВЕЖИТЬ | обновить(сущность) |
| Прослушиватель событий Удаления по умолчанию | УДАЛИТЬ | удалить(сущность) | УДАЛИТЬ или УДАЛИТЬ | удалить(сущность) |
| Прослушиватель Событий Сохранения Или Обновления По Умолчанию | СОХРАНИТЬ ОБНОВЛЕНИЕ | Сохранить обновление(сущность) | ||
| Прослушиватель событий Репликации по Умолчанию | КОПИРОВАТЬ | репликация(сущность, режим репликации) | ||
| Прослушиватель событий Блокировки по умолчанию | блокировка(сущность, тип блокировки) | ЗАМОК | buildLockRequest(сущность, блокировки) | |
| ВСЕ | Все вышеперечисленные методы EntityManager | ВСЕ | Все вышеперечисленные методы сеанса гибернации |
Из этой таблицы мы можем сделать вывод, что:
для ПЕРЕХОДНЫХ сущностей и объединять для ОТДЕЛЕННЫХ сущностей. Недостатки saveOrUpdate
Спящий режим конкретные события. Например, вы можете каскадировать операцию блокировки JPA (хотя она ведет себя как повторное подключение, а не как фактическое распространение запроса на блокировку), даже если JPA не определяет тип каскада.ЗАМОК .
Каскадирование имеет смысл только для Родительских – Дочерних ассоциаций (переход состояния Родительской сущности каскадируется в дочерние сущности). Каскадирование из Дочернего в Родительский не очень полезно, и обычно это запах кода сопоставления.
Далее я собираюсь проанализировать каскадное поведение всех JPA | Родительских – Дочерних ассоциаций.
Один К Одному
Наиболее распространенная Взаимно однозначная двунаправленная ассоциация выглядит следующим образом:
В данном конкретном случае тип каскадный.ВСЕ и orphanremoval имеют смысл, потому что Сведения о публикации жизненный цикл привязан к Публикации Родительской сущности.
Каскадирование операции сохранения один к одному
Создание следующего вывода:
Каскадирование операции слияния один к одному
Операция слияния генерирует следующие выходные данные:
Каскадирование операции удаления один к одному
CascadeType.REMOVE также наследуется от CascadeType.ВСЕ конфигурация, поэтому удаление Записи сущности также вызывает удаление записей сущности:
Создание следующего вывода:
Каскадная операция удаления сирот один к одному
Удаление сироты генерирует этот вывод:
Однонаправленная связь “один к одному”
Чаще всего Родительская сущность является обратной стороной (например, mappedBy ), поскольку Дочерняя сущность контролирует ассоциацию через свой Внешний ключ. Но каскад не ограничивается двунаправленными ассоциациями, мы также можем использовать его для однонаправленных отношений:
Каскадирование заключается в распространении перехода состояния Родительской сущности на одну или несколько Дочерних сущностей и может использоваться как для однонаправленных, так и для двунаправленных ассоциаций.
Один Ко Многим
Наиболее распространенная Родительская – Дочерняя ассоциация состоит из один ко многим и многие к одному отношения, где каскад полезен только для один ко многим стороны:
Каскадирование операции сохранения “один ко многим”
Нам нужно только сохранить Запись сущность, и все связанные Комментарии сущности также сохраняются:
Операция сохранения генерирует следующий вывод:
Каскадирование операции слияния “один ко многим”
Слияние Записи сущности приведет к объединению всех Комментариев сущностей, а также:
Создание следующего вывода:
Каскадирование операции удаления “один ко многим”
Когда Запись сущность удаляется, связанные Комментарий сущности также удаляются:
Создание следующего вывода:
Каскадная операция “один ко многим” по удалению сироты
orphanremoval позволяет нам удалять Дочернюю сущность всякий раз, когда на нее больше не ссылается ее Родитель :
Комментарий удален, как мы видим в следующем выводе:
Многие Ко Многим
Связь многие ко многим сложна, потому что на этот раз связь отображается на родительских сторонах ассоциации, в то время как дочерняя сторона (таблица соединений) скрыта. Если связь является двунаправленной, обе стороны могут распространять изменения состояния сущности.
Мы не должны использовать по умолчанию CascadeType.ВСЕ потому что CascadeType.REMOVE может привести к удалению большего количества, чем мы ожидаем (как вы скоро узнаете):
Каскадирование операции сохранения “многие ко многим”
Сохранение Автора сущностей также сохранит Книги :
Книга и Book_Author строки вставляются вместе с Авторами :
Разъединение одной стороны ассоциации “многие ко многим”
Этот вариант использования генерирует следующие выходные данные:
Ассоциация многие ко многим генерирует слишком много избыточных SQL операторов, и часто их очень трудно настроить. Далее я собираюсь продемонстрировать много-ко-многим |/Каскадный тип.УДАЛИТЕ скрытые опасности.
Каскадный тип “многие ко многим”. УДАЛИТЕ ошибки
Каскадный тип многие ко многим |/.ВСЕ – это еще один запах кода, с которым я часто сталкиваюсь при просмотре кода. CascadeType.REMOVE автоматически наследуется при использовании CascadeType.ВСЕ , но удаление сущности применяется не только к таблице ссылок, но и к другой стороне ассоциации.
Давайте изменим ассоциацию Автор сущность книги |/многие ко многим , чтобы использовать Каскадный тип.ВСЕ вместо:
При удалении одного Автора :
Чаще всего такое поведение не соответствует ожиданиям бизнес-логики и обнаруживается только при первом удалении сущности.
Мы можем продвинуть эту проблему еще дальше, если установим Каскадный тип.ВСЕ на сторону Книги сущности, а также:
Этот вариант использования неверен во многих отношениях. Существует множество ненужных SELECT утверждений, и в конечном итоге мы удаляем всех авторов и все их книги. Вот почему Каскадный тип.ВСЕ должны поднимать брови всякий раз, когда вы замечаете это в ассоциации “многие ко многим”//.
Когда дело доходит до Спящий режим сопоставления, вы всегда должны стремиться к простоте. Документация Hibernate также подтверждает это предположение:



