JPA: การลบแบบหลายทิศทางแบบหลายทิศทางและแบบเรียงซ้อน


98

สมมติว่าฉันมีความสัมพันธ์แบบทิศทางเดียว @ManyToOneดังต่อไปนี้:

@Entity
public class Parent implements Serializable {

    @Id
    @GeneratedValue
    private long id;
}

@Entity
public class Child implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @ManyToOne
    @JoinColumn
    private Parent parent;  
}

ถ้าฉันมีพ่อแม่ P และลูก C 1 ... C nอ้างอิงกลับไปที่ P มีวิธีที่สะอาดและสวยงามใน JPA เพื่อเอาเด็กออกโดยอัตโนมัติ C 1 ... C nเมื่อ P ถูกลบ (เช่นentityManager.remove(P))?

สิ่งที่ฉันกำลังมองหาคือฟังก์ชันที่คล้ายกับON DELETE CASCADEใน SQL


1
แม้ว่าจะมีเพียง "Child" เท่านั้นที่มีการอ้างอิงถึง "Parent" (ด้วยวิธีนี้การอ้างอิงเป็นแบบทิศทางเดียว) ก็เป็นปัญหาสำหรับคุณที่จะเพิ่มรายการของ "Child" ด้วยการแมป "@OneToMany" และแอตทริบิวต์ "Cascade = ALL" ใน 'ผู้ปกครอง'? ฉันคิดว่า JPA ควรแก้ปัญหาว่าแม้จะมีเพียงด้านเดียวเท่านั้นที่มีข้อมูลอ้างอิง
kvDennis

1
@kvDennis มีหลายกรณีที่คุณไม่ต้องการจับคู่หลาย ๆ ด้านเข้ากับด้านเดียวอย่างแน่นหนา เช่นในการตั้งค่าแบบ ACL ที่สิทธิ์ด้านความปลอดภัยเป็น "ส่วนเสริม" ที่โปร่งใส
Bachi

คำตอบ:


73

ความสัมพันธ์ใน JPA เป็นแบบทิศทางเดียวเสมอเว้นแต่คุณจะเชื่อมโยงผู้ปกครองกับเด็กในทั้งสองทิศทาง การดำเนินการ Cascading REMOVE จากพาเรนต์ไปยังเด็กจะต้องใช้ความสัมพันธ์จากพาเรนต์ถึงลูก (ไม่ใช่แค่ตรงกันข้าม)

คุณจะต้องทำสิ่งนี้:

  • ไม่ว่าจะเปลี่ยนทิศทาง@ManyToOneความสัมพันธ์กับสองทิศทางหรือทิศทางเดียว@ManyToOne @OneToManyจากนั้นคุณสามารถเรียงลำดับการดำเนินการ REMOVE เพื่อที่EntityManager.removeจะลบพาเรนต์และลูก ๆ คุณยังสามารถระบุว่าorphanRemovalเป็นจริงเพื่อลบเด็กที่ถูกทอดทิ้งเมื่อเอนทิตีลูกในคอลเลกชันพาเรนต์ถูกตั้งค่าเป็นโมฆะกล่าวคือลบเด็กเมื่อไม่มีอยู่ในคอลเลกชันของพาเรนต์
  • หรือระบุข้อ จำกัด ของคีย์ต่างประเทศในตารางลูกเป็นON DELETE CASCADE. คุณจะต้องเรียกใช้EntityManager.clear()หลังจากเรียกEntityManager.remove(parent)เนื่องจากต้องมีการรีเฟรชบริบทการคงอยู่ - เอนทิตีลูกไม่ควรมีอยู่ในบริบทการคงอยู่หลังจากที่ถูกลบในฐานข้อมูลแล้ว

7
มีวิธีทำ No2 ด้วยคำอธิบายประกอบ JPA หรือไม่?
user2573153

3
ฉันจะทำ No2 ด้วยการแม็พ Hibernate xml ได้อย่างไร
arg20

96

หากคุณใช้ไฮเบอร์เนตเป็นผู้ให้บริการ JPA ของคุณคุณสามารถใช้คำอธิบายประกอบ @OnDelete คำอธิบายประกอบนี้จะเพิ่มความสัมพันธ์กับทริกเกอร์ON DELETE CASCADEซึ่งมอบหมายการลบลูกไปยังฐานข้อมูล

ตัวอย่าง:

public class Parent {

        @Id
        private long id;

}


public class Child {

        @Id
        private long id;

        @ManyToOne
        @OnDelete(action = OnDeleteAction.CASCADE)
        private Parent parent;
}

ด้วยวิธีนี้ความสัมพันธ์แบบทิศทางเดียวจากเด็กกับผู้ปกครองก็เพียงพอที่จะลบเด็กทั้งหมดโดยอัตโนมัติ โซลูชันนี้ไม่จำเป็นต้องมีผู้ฟังใด ๆ เป็นต้นนอกจากนี้การสืบค้นเช่นDELETE FROM Parent WHERE id = 1จะลบเด็กออก


4
ฉันไม่สามารถทำให้มันใช้งานได้มีไฮเบอร์เนตเวอร์ชันเฉพาะหรือตัวอย่างอื่น ๆ ที่ละเอียดกว่านี้ไหม
Mardari

3
ยากที่จะบอกว่าทำไมมันถึงไม่เหมาะกับคุณ เพื่อให้ได้ผลคุณอาจต้องสร้างสคีมาใหม่หรือคุณต้องเพิ่มการลบน้ำตกด้วยตนเอง คำอธิบายประกอบ @OnDelete ดูเหมือนจะอยู่ในช่วงเวลาหนึ่งดังนั้นฉันจึงเดาไม่ออกว่าเวอร์ชันนี้เป็นปัญหา
Thomas Hunziker

12
ขอบคุณสำหรับคำตอบ. หมายเหตุด่วน: ทริกเกอร์น้ำตกฐานข้อมูลจะถูกสร้างขึ้นก็ต่อเมื่อคุณเปิดใช้งานการสร้าง DDL ผ่านโหมดไฮเบอร์เนต มิฉะนั้นคุณจะต้องเพิ่มวิธีอื่น (เช่นลิควิดเบส) เพื่อให้การสืบค้นเฉพาะกิจทำงานโดยตรงกับฐานข้อมูลเช่น 'DELETE FROM Parent WHERE id = 1' ทำการลบแบบเรียงซ้อน
mjj1409

1
สิ่งนี้ใช้ไม่ได้เมื่อสมาคมมี@OneToOneความคิดอย่างไรในการแก้ปัญหาด้วย@OneToOne?
stakowerflol

1
@ThomasHunziker นี้จะไม่ทำงานสำหรับ orphanRemoval ใช่มั้ย?
oxyt

14

สร้างความสัมพันธ์แบบสองทิศทางดังนี้:

@Entity
public class Parent implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
    private Set<Child> children;
}

9
คำตอบที่ไม่ดีความสัมพันธ์แบบสองทิศทางแย่มากใน JPA เพราะการดำเนินการกับเด็กชุดใหญ่ต้องใช้เวลามากอย่างไม่น่าเชื่อ
Enerccio

1
มีข้อพิสูจน์ว่าความสัมพันธ์แบบสองทิศทางช้าหรือไม่?
shalama

@enerccio จะเกิดอะไรขึ้นถ้าความสัมพันธ์แบบสองทิศทางเป็นแบบหนึ่งต่อหนึ่ง? นอกจากนี้โปรดแสดงบทความที่ระบุว่าความสัมพันธ์แบบสองทิศทางช้าหรือไม่? ช้าอะไร กำลังดึงข้อมูล? กำลังลบ? อัพเดท?
saran3h

@ saran3h ทุกการดำเนินการ (เพิ่มลบ) จะโหลดลูกทั้งหมดดังนั้นจึงเป็นการโหลดข้อมูลขนาดใหญ่ที่ไร้ประโยชน์ (เช่นการเพิ่มค่าไม่จำเป็นต้องโหลดลูกทั้งหมดจากฐานข้อมูลซึ่งเป็นสิ่งที่การทำแผนที่นี้ทำ)
Enerccio

@Enerccio ฉันคิดว่าทุกคนใช้การโหลดที่ขี้เกียจในการรวม แล้วมันยังคงเป็นปัญหาด้านประสิทธิภาพได้อย่างไร?
saran3h

2

ฉันเคยเห็นใน @ManytoOne ทิศทางเดียวการลบไม่ทำงานตามที่คาดไว้ เมื่อลบพาเรนต์ควรลบเด็กด้วย แต่ลบเฉพาะพาเรนต์และเด็กจะไม่ถูกลบและถูกปล่อยให้เป็นเด็กกำพร้า

เทคโนโลยีที่ใช้ ได้แก่ Spring Boot / Spring Data JPA / Hibernate

Sprint Boot: 2.1.2 รีลีส

Spring Data JPA / Hibernate ใช้เพื่อลบแถว

parentRepository.delete(parent)

ParentRepository ขยายที่เก็บ CRUD มาตรฐานดังที่แสดงด้านล่าง ParentRepository extends CrudRepository<T, ID>

ต่อไปนี้เป็นคลาสเอนทิตีของฉัน

@Entity(name = “child”)
public class Child  {

    @Id
    @GeneratedValue
    private long id;

    @ManyToOne( fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = “parent_id", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Parent parent;
}

@Entity(name = “parent”)
public class Parent {

    @Id
    @GeneratedValue
    private long id;

    @Column(nullable = false, length = 50)
    private String firstName;


}

ฉันพบวิธีแก้ปัญหาว่าทำไมการลบไม่ทำงาน เห็นได้ชัดว่า hibernate ไม่ได้ใช้ mysql Engine -INNODB คุณต้องมี Engine INNODB สำหรับ mysql เพื่อสร้างข้อ จำกัด ของคีย์ต่างประเทศ การใช้คุณสมบัติต่อไปนี้ใน application.properties ทำให้ spring boot / hibernate ใช้ mysql engine INNODB ข้อ จำกัด ของคีย์ต่างประเทศจึงใช้งานได้และด้วยเหตุนี้จึงลบน้ำตกด้วย
ranjesh

คุณสมบัติที่ไม่ได้ใช้ในความคิดเห็นก่อนหน้านี้ ต่อไปนี้เป็นคุณสมบัติของสปริงที่ใช้spring.jpa.hibernate.use-new-id-generator-mappings=true spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
ranjesh

FYI คุณ"ใส่รหัสผิด ดูname= "parent"
alexander

0

ใช้วิธีนี้เพื่อลบเพียงด้านเดียว

    @ManyToOne(cascade=CascadeType.PERSIST, fetch = FetchType.LAZY)
//  @JoinColumn(name = "qid")
    @JoinColumn(name = "qid", referencedColumnName = "qid", foreignKey = @ForeignKey(name = "qid"), nullable = false)
    // @JsonIgnore
    @JsonBackReference
    private QueueGroup queueGroup;

-1

@ Cascade (org.hibernate.annotations.CascadeType.DELETE_ORPHAN)

คำอธิบายประกอบใช้ได้ผลสำหรับฉัน สามารถลอง

ตัวอย่างเช่น :-

     public class Parent{
            @Id
            @GeneratedValue(strategy=GenerationType.AUTO)
            @Column(name="cct_id")
            private Integer cct_id;
            @OneToMany(cascade=CascadeType.REMOVE, fetch=FetchType.EAGER,mappedBy="clinicalCareTeam", orphanRemoval=true)
            @Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
            private List<Child> childs;
        }
            public class Child{
            @ManyToOne(fetch=FetchType.EAGER)
            @JoinColumn(name="cct_id")
            private Parent parent;
    }

-1

คุณไม่จำเป็นต้องใช้การเชื่อมโยงแบบสองทิศทางแทนรหัสของคุณคุณเพียงแค่เพิ่ม CascaType ลบเป็นคุณสมบัติของคำอธิบายประกอบ ManyToOne จากนั้นใช้ @OnDelete (action = OnDeleteAction.CASCADE) ก็ใช้ได้ดีสำหรับฉัน

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