JPA orphanRemoval = true แตกต่างจากส่วนคำสั่ง ON DELETE CASCADE DML อย่างไร


184

ฉันสับสนเล็กน้อยเกี่ยวกับorphanRemovalแอตทริบิวต์JPA 2.0

ฉันคิดว่าฉันสามารถเห็นมันเป็นสิ่งจำเป็นเมื่อฉันใช้เครื่องมือการสร้างฐานข้อมูล JPA ของผู้ให้บริการเพื่อสร้าง DDL ฐานข้อมูลพื้นฐานเพื่อให้มีON DELETE CASCADEความสัมพันธ์เฉพาะ

อย่างไรก็ตามถ้าฐานข้อมูลมีอยู่และมีON DELETE CASCADEความสัมพันธ์กันอยู่แล้วสิ่งนี้ไม่เพียงพอที่จะลดการลบอย่างเหมาะสมหรือไม่? สิ่งที่orphanRemovalนอกเหนือไปจากนี้?

ไชโย

คำตอบ:


292

orphanRemovalON DELETE CASCADEมีอะไรจะทำอย่างไรกับ

orphanRemovalทั้งหมดเป็นสิ่งที่ออมเฉพาะ มันทำเครื่องหมายเอนทิตี "child" ที่จะถูกลบเมื่อไม่มีการอ้างอิงจากเอนทิตี "parent" อีกต่อไปเช่นเมื่อคุณลบเอนทิตีลูกออกจากคอลเลกชันที่สอดคล้องกันของเอนทิตีหลัก

ON DELETE CASCADEเป็นสิ่งที่ระบุเฉพาะฐานข้อมูลจะลบแถว "ลูก" ในฐานข้อมูลเมื่อแถว "แม่" ถูกลบ


3
สิ่งนี้หมายความว่าพวกเขามีผลที่ปลอดภัย แต่ระบบที่แตกต่างมีหน้าที่รับผิดชอบในการทำให้มันเกิดขึ้น?
Anonymoose

101
อานนท์มันไม่ได้มีผลเช่นเดียวกัน ON DELETE CASCADE บอกให้ DB ลบระเบียนลูกทั้งหมดเมื่อลบพาเรนต์ นั่นคือถ้าฉันลบ INVOICE แล้วลบรายการทั้งหมดใน INVOICE นั้น OrphanRemoval บอก ORM ว่าถ้าฉันลบวัตถุรายการออกจากการรวบรวมรายการที่เป็นของวัตถุใบแจ้งหนี้ (ในการทำงานของหน่วยความจำ) แล้ว "บันทึก" ใบแจ้งหนี้รายการที่ลบออกควรถูกลบออกจากฐานข้อมูลพื้นฐาน
garyKeorkunian

2
หากคุณใช้ความสัมพันธ์แบบทิศทางเดียวดังนั้นเด็กกำพร้าจะถูกลบโดยอัตโนมัติแม้ว่าคุณจะไม่ได้ตั้ง orphanRemoval = จริง
ทิม

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/ …' เพียงแค่เพิ่มความคิดเห็นของฉันเพื่อเชื่อมโยงคำถามทั้งสอง
Narendra Verma

@ สำหรับโปรดไปยังคำถามstackoverflow.com/questions/58185249/…
GokulRaj KN

46

ช่วงเวลาที่คุณลบเอนทิตีลูกออกจากคอลเล็กชันคุณจะลบเอนทิตีลูกนั้นออกจากฐานข้อมูลด้วย orphanRemoval ก็หมายความว่าคุณไม่สามารถเปลี่ยนผู้ปกครองได้ หากมีแผนกที่มีพนักงานเมื่อคุณลบพนักงานคนนั้นเพื่อนำไปไว้ในแผนกอื่นคุณจะต้องลบพนักงานคนนั้นออกจากฐานข้อมูลโดยไม่ตั้งใจที่ flush / commit (ซึ่งมาก่อน) ขวัญกำลังใจคือการกำหนด orphanRemoval ให้เป็นจริงตราบใดที่คุณมั่นใจว่าลูก ๆ ของพ่อแม่นั้นจะไม่ย้ายไปอยู่กับพ่อแม่คนอื่นตลอดชีวิตของพวกเขา การเปิด orphanRemoval ยังเพิ่ม REMOVE ไปยังรายการเรียงซ้อนโดยอัตโนมัติ


3
ถูกต้องแน่นอน ... เรียกอีกอย่างว่าความสัมพันธ์ "ส่วนตัว" ของผู้ปกครอง / เด็ก
บันทึก

ซึ่งหมายความว่าทันทีที่ฉันโทรหาdepartment.remove(emp);พนักงานจะถูกลบออกจากตาราง EMP โดยไม่ต้องโทรแม้แต่commit()
JavaTechnical

18

การทำแผนที่ JPA เทียบเท่าสำหรับ DDL คือON DELETE CASCADE cascade=CascadeType.REMOVEการกำจัดเด็กกำพร้าหมายความว่าเอนทิตีที่พึ่งพาได้จะถูกลบออกเมื่อความสัมพันธ์กับเอนทิตี "แม่" ของพวกเขาถูกทำลาย ตัวอย่างเช่นถ้าเด็กถูกลบออกจาก@OneToManyความสัมพันธ์โดยไม่ต้องลบมันอย่างชัดเจนในการจัดการกิจการ


1
cascade=CascadeType.REMOVEON DELETE CASCADEไม่เทียบเท่า จะลบในรหัสแอปพลิเคชันและจะไม่ส่งผลกระทบต่อ DDL ดำเนินการอื่น ๆ ในฐานข้อมูล ดูstackoverflow.com/a/19696859/548473
Grigory Kislin

9

ความแตกต่างคือ:
- orphanRemoval = true: เอนทิตี "เด็ก" จะถูกลบออกเมื่อไม่มีการอ้างอิงอีกต่อไป (พาเรนต์อาจไม่ถูกลบออก)
- CascadeType.REMOVE: เอนทิตี "เด็ก" จะถูกลบออกเมื่อ "พาเรนต์" ถูกลบออก


6

เนื่องจากนี่เป็นคำถามที่พบบ่อยมากฉันจึงเขียนบทความนี้ขึ้นมาซึ่งคำตอบนี้ขึ้นอยู่กับ

การเปลี่ยนสถานะเอนทิตี

JPA แปลการเปลี่ยนสถานะนิติบุคคลเป็นคำสั่ง SQL เช่น INSERT, UPDATE หรือ DELETE

การเปลี่ยนสถานะนิติบุคคล JPA

เมื่อpersistเอนทิตีของคุณคุณกำลังกำหนดเวลาคำสั่ง INSERT ที่จะดำเนินการเมื่อEntityManagerถูกฟลัชไม่ว่าจะโดยอัตโนมัติหรือด้วยตนเอง

เมื่อremoveเอนทิตีของคุณคุณกำลังจัดตารางเวลาคำสั่ง DELETE ซึ่งจะถูกดำเนินการเมื่อ Persistence Context ถูกล้างออก

การเปลี่ยนสถานะเอนทิตีแบบเรียงซ้อน

เพื่อความสะดวก JPA อนุญาตให้คุณเผยแพร่การเปลี่ยนสถานะของเอนทิตีจากเอนทิตีหลักไปยังระดับย่อย

ดังนั้นหากคุณมีPostเอนทิตีหลักที่มีการ@OneToManyเชื่อมโยงกับPostCommentเอนทิตีรอง:

โพสต์และหน่วยงาน 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

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ซึ่งอาจเป็นการดำเนินการที่อันตรายมากหากคุณลบเอนทิตีรูทที่มีเอนทิตีลูกหลายอันในหลายระดับ

สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับหัวข้อนี้ตรวจสอบบทความนี้


ดังนั้นในการกำจัด Orphan ส่วนหนึ่งของคำตอบของคุณ: post.getComments (). ลบ (postComment); จะทำงานในการแมปสองทิศทาง OneToMany เพียงเพราะมีอยู่ต่อ หากไม่มีการเรียงซ้อนและไม่มีการลบด้าน ManyToOne เช่นในตัวอย่างของคุณการลบการเชื่อมต่อระหว่าง 2 เอนทิตีจะไม่คงอยู่ใน DB หรือไม่
aurelije

CascadeTypeกำจัดเด็กกำพร้าไม่ได้รับผลกระทบจาก มันเป็นกลไกเสริม ตอนนี้คุณกำลังกำจัดข้อผิดพลาดอย่างต่อเนื่อง การลบเด็กกำพร้าเป็นเรื่องเกี่ยวกับการลบการเชื่อมโยงที่ไม่ได้อ้างถึงในขณะที่การยืนยันจะเป็นการบันทึกเอนทิตีใหม่ คุณต้องทำตามลิงก์ที่ให้ไว้ในคำตอบเพื่อทำความเข้าใจแนวคิดเหล่านี้ให้ดีขึ้น
Vlad Mihalcea

ฉันไม่เข้าใจสิ่งหนึ่ง: การเอาเด็กกำพร้าออกจะเตะในการทำแผนที่สองทิศทางได้อย่างไรถ้าเราไม่ลบการเชื่อมต่อทางด้าน M ฉันคิดว่าการลบ PostComment ออกจากรายการโพสต์โดยไม่ตั้งค่า PostComment.post เป็น null จะไม่ส่งผลให้มีการลบการเชื่อมต่อระหว่าง 2 เอนทิตีใน DB นั่นคือเหตุผลที่ฉันคิดว่าการกำจัดเด็กกำพร้าจะไม่เริ่มขึ้นในโลกแห่งความสัมพันธ์มี PostComment ไม่ใช่เด็กกำพร้า ฉันจะทดสอบเมื่อฉันมีเวลาว่าง
aurelije

1
ฉันได้เพิ่มสองตัวอย่างเหล่านี้ในที่เก็บ GitHub Persistence Java ที่มีประสิทธิภาพสูงของฉันซึ่งแสดงให้เห็นว่ามันทำงานอย่างไร คุณไม่จำเป็นต้องซิงโครไนซ์ด้านข้างลูกโดยปกติคุณต้องทำเพื่อลบเอนทิตีโดยตรง อย่างไรก็ตามการลบเด็กกำพร้าทำงานได้เฉพาะเมื่อมีการเพิ่มเรียงซ้อน แต่ดูเหมือนว่าจะเป็นข้อ จำกัด ไฮเบอร์เนตไม่ใช่ข้อกำหนด JPA
Vlad Mihalcea

5

@GaryK คำตอบคือดีอย่างฉันได้ใช้เวลาหนึ่งชั่วโมงมองหาคำอธิบายorphanRemoval = trueVS 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
คุณมีการพิมพ์ผิดในวรรคที่สองของคุณ: ไม่มีวิธีการเช่น entityManager.delete (obj); มันเป็น entityManager.remove (obj)
JL_SO

3

การลบเด็กกำพร้ามีผลเช่นเดียวกับ ON DELETE CASCADE ในสถานการณ์ต่อไปนี้: - สมมติว่าเรามีความสัมพันธ์แบบหนึ่งต่อหนึ่งที่ง่ายระหว่างหน่วยงานนักเรียนและหน่วยงานแนะนำซึ่งนักเรียนจำนวนมากสามารถแมปกับคำแนะนำเดียวกันและในฐานข้อมูลที่เรามี ความสัมพันธ์ระหว่างประเทศกับคีย์นักเรียนต่างชาติและตาราง 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 ชื่อฝ่าย Id_Guide

1 Roy ECE 1

2 Nick ECE 1

ตารางคู่มือ: -

ID NAME เงินเดือน

1 จอห์น $ 1,500

และตอนนี้ถ้าฉันต้องการที่จะลบหนึ่งในนักเรียนโดยใช้

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

และเมื่อลบบันทึกของนักเรียนแล้วควรลบบันทึกคู่มือที่เกี่ยวข้องนั่นคือที่มาของแอตทริบิวต์ CASCADE.REMOVE ในเอนทิตีของนักเรียนเข้ามาในรูปภาพและสิ่งที่ทำคือมันลบนักเรียนที่มีตัวระบุ 1 รวมทั้งวัตถุคู่มือที่เกี่ยวข้อง (ตัวระบุ 1) แต่ในตัวอย่างนี้มีอีกหนึ่งวัตถุนักเรียนที่แมปไปยังระเบียนคำแนะนำเดียวกันและถ้าเราใช้แอตทริบิวต์ orphanRemoval = trueใน Guide Entity รหัสลบด้านบนจะไม่ทำงาน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.