Чем JPA orphanRemoval = true отличается от предложения ON DELETE CASCADE DML


184

Я немного запутался в orphanRemovalатрибуте JPA 2.0 .

Я думаю, что вижу, что это необходимо, когда я использую инструменты генерации БД моего провайдера JPA для создания базовой базы данных DDL, чтобы иметь отношение ON DELETE CASCADEк конкретному отношению.

Однако, если БД существует и уже имеет отношение ON DELETE CASCADEon, этого недостаточно для надлежащего каскадного удаления? Что делает orphanRemovalв дополнение?

ура

Ответы:


292

orphanRemovalне имеет ничего общего с ON DELETE CASCADE.

orphanRemovalэто совершенно специфичная для ORM вещь . Он помечает «дочернюю» сущность, которая будет удалена, когда на нее больше нет ссылок из «родительской» сущности, например, когда вы удаляете дочернюю сущность из соответствующей коллекции родительской сущности.

ON DELETE CASCADEэто специфичная для базы данных вещь , она удаляет «дочернюю» строку в базе данных, когда «родительская» строка удаляется.


3
Означает ли это, что они имеют безопасный эффект, но другая система отвечает за это?
Anonymoose

101
Анон, это не имеет того же эффекта. ON DELETE CASCADE указывает БД удалить все дочерние записи при удалении родителя. То есть, если я удаляю СЧЕТ, затем удаляю все ПУНКТЫ на этом СЧЕТЕ. OrphanRemoval сообщает ORM, что если я удалю объект Item из коллекции Предметов, принадлежащих объекту Invoice (в операции памяти), а затем "сохраню" Invoice, удаленный Item должен быть удален из базовой БД.
garyKeorkunian

2
Если вы используете однонаправленные отношения, тогда сирота будет удалена автоматически, даже если вы не установите orphanRemoval = true
Тим

98

Пример взятой формы здесь :

Когда Employeeобъект сущности удаляется, операция удаления каскадно относится к Addressобъекту ссылочной сущности. В связи с этим orphanRemoval=trueи cascade=CascadeType.REMOVEидентичны, и, если orphanRemoval=trueуказано, CascadeType.REMOVEявляется излишним.

Разница между этими двумя настройками заключается в ответе на разрыв отношения. Например, например, при установке поля адреса на nullили на другой Addressобъект.

  • Если orphanRemoval=trueуказано, отключенный Addressэкземпляр автоматически удаляется. Это полезно для очистки зависимых объектов (например Address), которые не должны существовать без ссылки от объекта-владельца (например Employee).

  • Если cascade=CascadeType.REMOVEуказано только , автоматическое действие не выполняется, так как отключение отношения не является операцией удаления.

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

Я надеюсь, что это делает это более ясным.


Прочитав ваш ответ, я понимаю, что между ними и моей проблемой возникла точная разница. Я застрял в удалении дочерних сущностей из базы данных, если они отключены от определенной коллекции в родительской сущности. Для этого же я задал вопрос « stackoverflow.com/questions/15526440/… ». Просто добавив мой комментарий, чтобы связать оба вопроса.
Нарендра Верма

@forhas, пожалуйста, пройдите вопрос stackoverflow.com/questions/58185249/…
GokulRaj KN

46

В тот момент, когда вы удаляете дочернюю сущность из коллекции, вы также удаляете эту дочернюю сущность из БД. orphanRemoval также подразумевает, что вы не можете сменить родителей; если есть отдел, в котором есть сотрудники, то после того, как вы удалите этого сотрудника, чтобы перевести его в другой отдел, вы по неосторожности удалите этого сотрудника из БД с помощью команды flush / commit (что произойдет раньше). Мораль заключается в том, чтобы установить для orphanRemoval значение true, если вы уверены, что дети этого родителя не будут мигрировать к другому родителю на протяжении всего своего существования. Включение orphanRemoval также автоматически добавляет REMOVE в каскадный список.


3
Точно правильно ... также называется "частными" родительскими / дочерними отношениями.
HDave

Это означает, что как только я позвоню, department.remove(emp);этот сотрудник будет удален из таблицы emp, даже не позвонивcommit()
JavaTechnical

18

Эквивалентное отображение JPA для DDL ON DELETE CASCADEесть cascade=CascadeType.REMOVE. Удаление без учета означает, что зависимые объекты удаляются, когда связь с их «родительским» объектом уничтожается. Например, если ребенок удален из @OneToManyотношения без явного удаления его в менеджере сущностей.


1
cascade=CascadeType.REMOVEНЕ является эквивалентом ON DELETE CASCADE. On делает удаление в коде приложения и не влияет на DDL, другие выполняются в БД. См. Stackoverflow.com/a/19696859/548473
Григорий Кислин

9

Разница в следующем:
- orphanRemoval = true: дочерняя сущность удаляется, когда на нее больше нет ссылок (ее родительская часть не может быть удалена).
- CascadeType.REMOVE: «Дочерняя» сущность удаляется только тогда, когда ее «Родитель» удаляется.


6

Поскольку это очень распространенный вопрос, я написал эту статью , на которой основан этот ответ.

Переходы состояния объекта

JPA переводит переходы состояния сущности в операторы SQL, такие как INSERT, UPDATE или DELETE.

Переходы состояния объекта JPA

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

когда вы являетесь removeсущностью, вы планируете инструкцию DELETE, которая будет выполняться, когда сбрасывается контекст постоянства.

Каскадные переходы состояний объекта

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

Итак, если у вас есть родительский Postобъект, который @OneToManyсвязан с PostCommentдочерним объектом:

Post и PostComment сущности

commentsКоллекция в Postсущности, отображается следующим образом :

@OneToMany(
    mappedBy = "post", 
    cascade = CascadeType.ALL,
    orphanRemoval = true
)
private List<Comment> comments = new ArrayList<>();

CascadeType.ALL

cascadeАтрибут сообщает поставщик JPA передать объект состояние перехода от родительского Postобъекта для всех PostCommentлиц , содержащихся в commentsколлекции.

Итак, если вы удалите Postсущность:

Post post = entityManager.find(Post.class, 1L);
assertEquals(2, post.getComments().size());

entityManager.remove(post);

Поставщик JPA собирается PostCommentсначала удалить объект, а когда все дочерние объекты будут удалены, он также удалит Postобъект:

DELETE FROM post_comment WHERE id = 1
DELETE FROM post_comment WHERE id = 2

DELETE FROM post WHERE id = 1

Удаление сирот

Когда вы устанавливаете orphanRemovalатрибут в true, поставщик JPA собирается планировать removeоперацию, когда дочерняя сущность удаляется из коллекции.

Итак, в нашем случае,

Post post = entityManager.find(Post.class, 1L);
assertEquals(2, post.getComments().size());

PostComment postComment = post.getComments().get(0);
assertEquals(1L, postComment.getId());

post.getComments().remove(postComment);

Поставщик JPA собирается удалить связанную post_commentзапись, поскольку на PostCommentобъект больше не ссылаются в commentsколлекции:

DELETE FROM post_comment WHERE id = 1

НА УДАЛЕННОМ КАСКАДЕ

ON DELETE CASCADEОпределяется на уровне FK:

ALTER TABLE post_comment 
ADD CONSTRAINT fk_post_comment_post_id 
FOREIGN KEY (post_id) REFERENCES post 
ON DELETE CASCADE;

Как только вы это сделаете, если вы удалите postстроку:

DELETE FROM post WHERE id = 1

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

Вывод

Преимущество JPA cascadeи orphanRemovalопций заключается в том, что вы также можете воспользоваться оптимистичной блокировкой для предотвращения потери обновлений .

Если вы используете механизм каскадирования JPA, вам не нужно использовать уровень DDL ON DELETE CASCADE, что может быть очень опасной операцией, если вы удалите корневую сущность, которая имеет много дочерних сущностей на нескольких уровнях.

Для более подробной информации по этой теме, проверьте эту статью .


Итак, в разделе «Удаление сирот» часть вашего ответа: post.getComments (). Remove (postComment); будет работать в двунаправленном отображении OneToMany только из-за постоянного каскада. Без каскадирования и отсутствия удаления на стороне ManyToOne, как в вашем примере, удаление соединения между двумя объектами не будет сохраняться в БД?
Aurelije

Удаление сирот не затрагивается CascadeType. Это дополнительный механизм. Теперь вы ошибаетесь с удалением с сохранением. Удаленное удаление - это удаление не связанных ссылок, а сохранение - сохранение новых объектов. Вам нужно перейти по ссылкам, приведенным в ответе, чтобы лучше понять эти понятия.
Влад Михальча

Я не понимаю одну вещь: как удаление сирот вступит в силу при двунаправленном отображении, если мы никогда не удалим соединение на стороне M? Я думаю, что удаление PostComment из списка Post без установки значения PostComment.post в null не приведет к удалению связи между этими двумя объектами в БД. Вот почему я думаю, что удаление сирот не вступит в силу, в реляционном мире PostComment не является сиротой. Я проверю это, когда у меня будет немного свободного времени.
Aurelije

1
Я добавил эти два примера в мой высокопроизводительный репозиторий Java Persistence GitHub, которые демонстрируют, как все это работает. Вам не нужно синхронизировать дочернюю сторону, как это обычно делается для непосредственного удаления сущностей. Однако удаление потерянных объектов работает только при добавлении каскадирования, но это, похоже, ограничение Hibernate, а не спецификация JPA.
Влад Михальча

5

@GaryK ответ абсолютно отличный, я потратил час на поиски объяснения orphanRemoval = trueпротив, CascadeType.REMOVEи это помогло мне понять.

Подводя итог: orphanRemoval = trueработает идентично CascadeType.REMOVE ТОЛЬКО ЕСЛИ мы удаляем object ( entityManager.delete(object)) и хотим, чтобы дочерние объекты также были удалены.

В совершенно ином случае, когда мы выбираем некоторые данные, такие как, List<Child> childs = object.getChilds()а затем удаляем child ( entityManager.remove(childs.get(0)), orphanRemoval=trueэто приводит к тому, что соответствующий объект childs.get(0)будет удален из базы данных.


1
У вас есть опечатка во втором абзаце: нет такого метода, как entityManager.delete (obj); это entityManager.remove (obj).
JL_SO

3

Удаление сирот имеет тот же эффект, что и ON DELETE CASCADE в следующем сценарии: - Допустим, у нас простое отношение «многие к одному» между сущностью студента и сущностью-гидом, где многие ученики могут быть сопоставлены с одним и тем же руководством, и в базе данных есть отношение внешнего ключа между таблицей Student и Guide так, что для таблицы Student есть id_guide в виде FK.

    @Entity
    @Table(name = "student", catalog = "helloworld")
    public class Student implements java.io.Serializable {
     @Id
     @GeneratedValue(strategy = IDENTITY)
     @Column(name = "id")
     private Integer id;

    @ManyToOne(cascade={CascadeType.PERSIST,CascadeType.REMOVE})
    @JoinColumn(name = "id_guide")
    private Guide guide;

// родительский объект

    @Entity
    @Table(name = "guide", catalog = "helloworld")
    public class Guide implements java.io.Serializable {

/**
 * 
 */
private static final long serialVersionUID = 9017118664546491038L;

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Integer id;

@Column(name = "name", length = 45)
private String name;

@Column(name = "salary", length = 45)
private String salary;


 @OneToMany(mappedBy = "guide", orphanRemoval=true) 
 private Set<Student> students = new  HashSet<Student>(0);

В этом сценарии отношение таково, что объект учащегося является владельцем отношения, и поэтому нам необходимо сохранить объект учащегося, чтобы сохранить весь граф объекта, например

    Guide guide = new Guide("John", "$1500");
    Student s1 = new Student(guide, "Roy","ECE");
    Student s2 = new Student(guide, "Nick", "ECE");
    em.persist(s1);
    em.persist(s2);

Здесь мы отображаем одно и то же руководство с двумя разными объектами учеников, и поскольку используется CASCADE.PERSIST, граф объектов будет сохранен, как показано ниже в таблице базы данных (MySql в моем случае)

СТУДЕНЧЕСКИЙ стол: -

ID Имя Dept Id_Guide

1 Рой ЕЭК 1

2 ник ECE 1

Таблица руководств: -

ID NAME Зарплата

1 Джон 1500 долларов

и теперь, если я хочу удалить одного из студентов, используя

      Student student1 = em.find(Student.class,1);
      em.remove(student1);

и когда запись о студенте удаляется, соответствующая запись о руководстве также должна быть удалена, вот где атрибут CASCADE.REMOVE в сущности «Студент» входит в изображение и что он делает, он удаляет студента с идентификатором 1, а также соответствующий объект-указатель (идентификатор) 1). Но в этом примере есть еще один объект ученика, который сопоставлен с той же направляющей записью, и если мы не используем атрибут orphanRemoval = true в объекте Guide, приведенный выше код удаления не будет работать.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.