Автор оригинала: Vlad Mihalcea.
Вступление
В этой статье мы рассмотрим, как механизм JPA и Hibernate orphanRemoval позволяет нам запускать операцию дочернего объекта удалить
при отсоединении ссылки на дочернюю сущность от дочерней коллекции на родительской стороне.
Модель предметной области
Мы собираемся использовать Сообщение
и Комментарий к сообщению
сущность, которая образует отношение таблицы “один ко многим” :
@ManyToOne
аннотация в Комментарии к сообщению
сущности отображает столбец post_id
внешнего ключа, который образует отношение таблицы “один ко многим” между родительской публикацией
и post_comment
дочерними таблицами:
@Entity(name = "PostComment") @Table(name = "post_comment") public class PostComment { @Id @GeneratedValue private Long id; @ManyToOne(fetch = FetchType.LAZY) private Post post; private String review; //Getters and setters omitted for brevity }
И объект Post
отображается следующим образом:
@Entity(name = "Post") @Table(name = "post") public class Post { @Id @GeneratedValue private Long id; private String title; @OneToMany( mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true ) private Listcomments = new ArrayList<>(); //Getters and setters omitted for brevity public Post addComment(PostComment comment) { comments.add(comment); comment.setPost(this); return this; } public Post removeComment(PostComment comment) { comments.remove(comment); comment.setPost(null); return this; } }
Коллекция комментариев
сопоставляется с помощью аннотации @OneToMany
, а атрибут mappedBy
указывает поставщику JPA, что свойство post
в дочерней сущности PostComment
управляет базовым столбцом внешнего ключа.
Атрибут cascade
имеет значение Тип каскада.ВСЕ
, что означает, что все переходы состояния JPA и спящего режима сущности (например, сохранение
, слияние
, удаление
) передаются от родительской Последующей
сущности к последующей
дочерним сущностям.
Атрибут orphanRemoval
даст указание поставщику JPA инициировать переход в состояние удаления
сущности, когда PostComment
сущность больше не ссылается на свою родительскую Post
сущность.
Поскольку у нас есть двунаправленная связь “один ко многим”, нам нужно убедиться, что обе стороны связи синхронизированы, и по этой причине мы создали методы AddComment
и removeComment
для синхронизации обоих концов при добавлении или удалении новой дочерней сущности. Ознакомьтесь с этой статьей для получения более подробной информации.
JPA и каскадный тип спящего режима.Механизм СОХРАНЕНИЯ
Давайте создадим Сообщение
сущность с двумя Комментарием к сообщению
дочерними сущностями:
Post post = new Post() .setTitle("High-Performance Java Persistence") .addComment( new PostComment() .setReview("Best book on JPA and Hibernate!") ) .addComment( new PostComment() .setReview("A must-read for every Java developer!") ); entityManager.persist(post);
Потому что каскадный тип.ВСЯ стратегия
включает в себя каскадный тип.Опция PERSIST
при вызове persist
и сущности post
Hibernate сохранит как Post
, так и две дочерние сущности
PostComment//, как показано в сгенерированных инструкциях INSERT:
INSERT INTO post ( title, id ) VALUES ( 'High-Performance Java Persistence', 1 ) INSERT INTO post_comment ( post_id, review, id ) VALUES ( 1, 'Best book on JPA and Hibernate!', 2 ) INSERT INTO post_comment ( post_id, review, id ) VALUES ( 1, 'A must-read for every Java developer!', 3 )
JPA и механизм временного отключения в спящем режиме
Если мы загрузим Сообщение
сущность вместе с ее двумя последующими
дочерними сущностями и удалим первый Комментарий к сообщению
:
Post post = entityManager.createQuery(""" select p from Post p join fetch p.comments c where p.id = :id order by p.id, c.id """, Post.class) .setParameter("id", postId) .getSingleResult(); post.removeComment(post.getComments().get(0));
Hibernate выполнит следующие инструкции SQL:
SELECT p.id as id1_0_0_, c.id as id1_1_1_, p.title as title2_0_0_, c.post_id as post_id3_1_1_, c.review as review2_1_1_ FROM post p INNER JOIN post_comment c ON p.id = c.post_id WHERE p.id = 1 ORDER BY p.id, c.id DELETE FROM post_comment WHERE id = 2
Поскольку метод удалить комментарий
удаляет ссылку Комментарий к сообщению
из коллекции комментариев
, механизм orphanRemoval запустит удаление
для объекта PostComment
, и будет выполнена инструкция DELETE.
Если мы установим для атрибута orphanRemoval
значение false
:
@OneToMany( mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = false ) private Listcomments = new ArrayList<>();
И повторите предыдущий тестовый случай, в котором вызывался метод удалить комментарий
, Hibernate выполнил следующие инструкции SQL:
SELECT p.id as id1_0_0_, c.id as id1_1_1_, p.title as title2_0_0_, c.post_id as post_id3_1_1_, c.review as review2_1_1_ FROM post p INNER JOIN post_comment c ON p.id = c.post_id WHERE p.id = 1 ORDER BY p.id, c.id UPDATE post_comment SET post_id = NULL, review = 'Best book on JPA and Hibernate!' WHERE id = 2
Таким образом, вместо инструкции DELETE вместо нее выполняется инструкция UPDATE, устанавливающая в столбце post_id
значение NULL
. Такое поведение вызвано следующей строкой в методе удалить комментарий
:
comment.setPost(null);
Итак, если вы хотите удалить базовую дочернюю запись после удаления связанной сущности из дочерней коллекции в родительской сущности, вам следует установить для атрибута orphanRemoval
значение true
.
JPA и гибернация сиротского времени по сравнению с каскадным типом.УДАЛИТЬ
Очень распространенный вопрос заключается в том, чем механизм orphanRemoval отличается от стратегии CascadeType.REMOVE
.
Если механизм orphanRemoval позволяет нам запустить операцию удаления
для несвязанной дочерней сущности, стратегия CascadeType.REMOVE
распространяет операцию удаления
от родительской сущности на все дочерние сущности.
Потому что в коллекции комментарии
используется Каскадный тип.ВСЕ
, это означает, что он также наследует стратегию CascadeType.REMOVE
.
Поэтому, если мы выполним следующий тестовый случай:
Post post = entityManager.createQuery(""" select p from Post p join fetch p.comments where p.id = :id """, Post.class) .setParameter("id", postId) .getSingleResult(); entityManager.remove(post);
Hibernate выполнит три инструкции УДАЛЕНИЯ:
DELETE FROM post_comment WHERE id = 2 DELETE FROM post_comment WHERE id = 3 DELETE FROM post WHERE id = 1
Во-первых, дочерние строки удаляются, потому что, если мы сначала удалим строку post
, будет вызвано исключение Нарушения ограничений
, поскольку все еще будут post_comment
строки, связанные с записью post
, которую хотели удалить.
Не используйте CascadeType.УДАЛИТЕ с помощью @ManyToMany ассоциаций
Стратегия CascadeType.REMOVE
полезна только для ассоциаций @OneToMany
и @OneToOne
. Если вы используете ассоциацию @ManyToMany
, вам никогда не следует устанавливать тип каскада.ВСЕ значение атрибута
, так как вы также унаследуете стратегию CascadeType.REMOVE
.
Каскадирование имеет смысл от родительской сущности к дочерней. Поскольку коллекция, аннотированная @ManyToMany
, связывает две родительские сущности через объединенную таблицу, мы не хотим распространять удаление от одного родителя к другому. Вместо этого мы хотим распространить операцию удаления из родительской таблицы на дочерние записи таблицы объединения.
При удалении элемента из коллекции @ManyToMany
Hibernate создает инструкцию DELETE для записи таблицы объединения. Таким образом, он работает как orphanRemoval, но вместо распространения удаления на фактическую сущность, удаляемую из коллекции, он запускает инструкцию DELETE для дочерней строки в таблице объединения.
Для получения более подробной информации об этой теме ознакомьтесь с этой статьей .
Вывод
Стратегия orphanRemoval упрощает управление состоянием дочерней сущности, так как нам нужно только удалить дочернюю сущность из дочерней коллекции, и связанная дочерняя запись также удаляется.
В отличие от стратегии orphanRemoval, CascadeType.REMOVE
распространяет операцию remove
от родительской к дочерним сущностям, как если бы мы вручную вызывали remove
для каждой дочерней сущности.