Чим JPA orphanRemoval = true відрізняється від пункту ON DELETE CASCADE DML


184

Я трохи заплутався в orphanRemovalатрибуті JPA 2.0 .

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

Однак, якщо БД існує і вона вже має ON DELETE CASCADEвідношення, чи цього недостатньо для каскадування видалення належним чином? Що робить orphanRemovalдодатково?

Ура

Відповіді:


292

orphanRemovalне має нічого спільного ON DELETE CASCADE.

orphanRemoval- цілком специфічна для ORM річ . Він позначає "дочірнє" об'єкт, яке потрібно видалити, коли на нього більше не посилається "батьківський" об'єкт, наприклад, коли ви вилучаєте дочірнє об'єкт із відповідної колекції батьківського об'єкта.

ON DELETE CASCADE- специфічна для бази даних річ , вона видаляє "дочірню" рядок у базі даних, коли "батьківський" рядок видалено.


3
Чи означає це, що вони мають безпечний ефект, але інша система відповідає за те, щоб це сталося?
Anonymousmoose

101
Анон, це не має однакового ефекту. ON DELETE CASCADE вказує БД видалити всі дочірні записи при видаленні батьків. Тобто, якщо я видаляю РАБОТУ, то видаляю всі ПІДТВОРЕННЯ цієї фактури. OrphanRemoval повідомляє ORM, що якщо я видаляю об'єкт 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, також автоматично додається REMOVE до списку каскадів.


3
Точно правильно ... також називають "приватними" відносинами батьків / дитини.
HDave

Це означає, що як тільки я зателефоную, department.remove(emp);що цей працівник буде видалений з таблиці emp, навіть не зателефонувавшиcommit()
JavaTechNI

18

Еквівалентне відображення JPA для DDL ON DELETE CASCADEє cascade=CascadeType.REMOVE. Видалення сиріт означає, що залежні особи видаляються, коли знищуються відносини до їх "батьківського" об'єкта. Наприклад, якщо дитину відсторонили від @OneToManyвідносин, не видаляючи її чітко у менеджера юридичних осіб.


1
cascade=CascadeType.REMOVEНЕ еквівалентно ON DELETE CASCADE. Увімкніть видалення в коді програми та не впливає на 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дочірнім об'єктом:

Суб'єкти пошти та пошти

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 (). Видалити (postComment); буде працювати в двонаправленому відображенні OneToMany лише через каскад Персист. Без каскадування та не видалення з боку ManyToOne, як у вашому прикладі, видалення з'єднання між двома об'єктами не було б збереженим у БД?
aurelije

На видалення сиріт не впливає CascadeType. Це взаємодоповнюючий механізм. Тепер ви помиляєтеся видаленням із збереженням. Видалення сиріт - це видалення нереференційованих асоціацій, а збереження - це збереження нових об'єктів. Щоб краще зрозуміти ці поняття, вам потрібно перейти за посиланнями, наданими у відповіді.
Влад Михальча

Я не розумію одного: як вилучення сиріт розпочнеться в двонаправленому картографуванні, якщо ми ніколи не знімемо з'єднання з боку М? Я думаю, що видалення PostComment зі списку Пост без встановлення значення PostComment.post на null не призведе до видалення зв'язку між цими двома об'єктами в БД. Ось чому я думаю, що видалення сиріт не вступить, у реляційному світі PostComment не є сиротою. Я перевірю це, коли отримаю трохи вільного часу.
aurelije

1
Ці два приклади я додав у своє високоефективне сховище Java Persistence GitHub, яке демонструє, як це все працює. Вам не потрібно синхронізувати дочірню сторону, як це потрібно робити безпосередньо для видалення об'єктів. Однак видалення сиріт працює лише в тому випадку, якщо додано каскад, але, здається, це обмеження в сплячому режимі, а не специфікація JPA.
Влад Михальча

5

Відповідь @GaryK абсолютно чудова, я витратив годину на пошуки пояснення orphanRemoval = trueпроти, CascadeType.REMOVEі це допомогло мені зрозуміти.

Підбиття підсумків: orphanRemoval = trueпрацює ідентично, CascadeType.REMOVE ТІЛЬКО ЯКЩО ми видаляємо об'єкт ( entityManager.delete(object)), і ми хочемо, щоб дочірні об’єкти також були видалені.

При зовсім іншому оцінюванні, коли ми отримуємо деякі дані, як-от, List<Child> childs = object.getChilds()а потім видаляємо дочірню ( entityManager.remove(childs.get(0)), використовуючи, це orphanRemoval=trueпризведе до того, що це об'єкт childs.get(0)буде видалено з бази даних.


1
У вашому другому абзаці є помилка друку: Не існує такого методу як entitManager.delete (obj); це entitManager.remove (obj).
JL_SO

3

Вилучення сиріт має той же ефект, що і НА ВИДАЛЕННЯ КАСКАДУ за наступним сценарієм: - Скажімо, у нас є прості відносини між студентом та керівництвом, що мають багато простих до одного, коли багато студентів можуть бути відображені в одному керівництві, а в базі даних у нас є зовнішнє ключове відношення між таблицею Student і Guide таким чином, що таблиця студента має 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_Guide

1 Рой ЄЕК 1

2 Нік ECE 1

Таблиця довідника: -

ID NAME Зарплата

1 Джон 1500 доларів США

і тепер, якщо я хочу видалити одного з учнів, використовуючи

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

і коли видаляється запис студента, відповідний запис керівництва також повинен бути видалений, саме тут атрибут CASCADE.REMOVE в об'єкті Student входить у зображення і що він робить, він видаляє студента з ідентифікатором 1, а також відповідний керівний об'єкт (ідентифікатор 1). Але в цьому прикладі є ще один об'єкт для студентів, який відображається у тому самому записі керівництва, і, якщо ми не використаємо атрибут orphanRemoval = true в Entity Guide, код видалення вище не буде працювати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.