วิธีลบเอนทิตีที่มีความสัมพันธ์ ManyToMany ใน JPA (และแถวตารางการเข้าร่วมที่เกี่ยวข้อง)


92

สมมติว่าฉันมีสองเอนทิตี: กลุ่มและผู้ใช้ ผู้ใช้ทุกคนสามารถเป็นสมาชิกได้หลายกลุ่มและทุกกลุ่มสามารถมีผู้ใช้ได้หลายคน

@Entity
public class User {
    @ManyToMany
    Set<Group> groups;
    //...
}

@Entity
public class Group {
    @ManyToMany(mappedBy="groups")
    Set<User> users;
    //...
}

ตอนนี้ฉันต้องการลบกลุ่มออก (สมมติว่ามีสมาชิกหลายคน)

ปัญหาคือเมื่อฉันเรียก EntityManager.remove () ในบางกลุ่มผู้ให้บริการ JPA (ในกรณีของฉันคือ Hibernate) ไม่ลบแถวออกจากตารางรวมและการดำเนินการลบล้มเหลวเนื่องจากข้อ จำกัด ของคีย์ต่างประเทศ การเรียกลบ () บน User ใช้งานได้ดี (ฉันเดาว่าสิ่งนี้เกี่ยวข้องกับความสัมพันธ์ที่เป็นเจ้าของ)

ดังนั้นฉันจะลบกลุ่มในกรณีนี้ได้อย่างไร

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

คำตอบ:


87
  • ความเป็นเจ้าของความสัมพันธ์จะถูกกำหนดโดยตำแหน่งที่คุณวางแอตทริบิวต์ "mappedBy" ให้กับคำอธิบายประกอบ เอนทิตีที่คุณใส่ 'mappedBy' คือเอนทิตีที่ไม่ใช่เจ้าของ ทั้งสองฝ่ายไม่มีโอกาสเป็นเจ้าของ หากคุณไม่มีกรณีการใช้งาน "ลบผู้ใช้" คุณสามารถย้ายการเป็นเจ้าของไปยังGroupเอนทิตีดังเช่นในปัจจุบันUserเป็นเจ้าของ
  • ในทางกลับกันคุณไม่ได้ถามเกี่ยวกับเรื่องนี้ แต่สิ่งหนึ่งที่ควรรู้ groupsและusersยังไม่ได้รวมเข้าด้วยกัน ฉันหมายถึงหลังจากลบอินสแตนซ์ User1 จาก Group1.users คอลเล็กชัน User1.groups จะไม่เปลี่ยนแปลงโดยอัตโนมัติ (ซึ่งค่อนข้างน่าแปลกใจสำหรับฉัน)
  • โดยรวมแล้วฉันขอแนะนำให้คุณตัดสินใจว่าใครเป็นเจ้าของ ให้บอกว่าUserเป็นเจ้าของ จากนั้นเมื่อลบผู้ใช้กลุ่มผู้ใช้ที่เกี่ยวข้องจะได้รับการอัปเดตโดยอัตโนมัติ แต่เมื่อลบกลุ่มคุณต้องดูแลลบความสัมพันธ์ด้วยตัวเองดังนี้:

entityManager.remove(group)
for (User user : group.users) {
     user.groups.remove(group);
}
...
// then merge() and flush()

ขอบคุณ! ฉันมีปัญหาเดียวกันและวิธีแก้ปัญหาของคุณแก้ไขได้ แต่ฉันต้องรู้ว่ามีวิธีอื่นในการแก้ปัญหานี้หรือไม่ มันสร้างรหัสที่น่ากลัว ทำไมถึงไม่เป็นem ลบ (เอนทิตี)และนั่นล่ะ
Royi Freifeld

2
สิ่งนี้ได้รับการปรับให้เหมาะสมที่สุดเบื้องหลังหรือไม่? เพราะฉันไม่ต้องการค้นหาชุดข้อมูลทั้งหมด
Ced

1
นี่เป็นแนวทางที่แย่มากถ้าคุณมีผู้ใช้หลายพันคนในกลุ่มนั้นล่ะ?
Sergiy Sokolenko

2
สิ่งนี้ไม่ได้อัปเดตตารางความสัมพันธ์ในตารางความสัมพันธ์แถวเกี่ยวกับความสัมพันธ์นี้ยังคงอยู่ ฉันไม่ได้ทดสอบ แต่อาจทำให้เกิดปัญหาได้
SaygınDoğu

@SergiySokolenko ไม่มีการดำเนินการสืบค้นฐานข้อมูลสำหรับการวนซ้ำ ทั้งหมดจะถูกรวมเป็นกลุ่มหลังจากที่ฟังก์ชันธุรกรรมถูกทิ้งไว้
Kilves

43

ต่อไปนี้ใช้ได้ผลสำหรับฉัน เพิ่มวิธีการต่อไปนี้ในเอนทิตีที่ไม่ใช่เจ้าของความสัมพันธ์ (กลุ่ม)

@PreRemove
private void removeGroupsFromUsers() {
    for (User u : users) {
        u.getGroups().remove(this);
    }
}

โปรดทราบว่าเพื่อให้สามารถใช้งานได้กลุ่มต้องมีรายชื่อผู้ใช้ที่อัปเดต (ซึ่งไม่ได้ดำเนินการโดยอัตโนมัติ) ดังนั้นทุกครั้งที่คุณเพิ่มกลุ่มในรายการกลุ่มในเอนทิตีผู้ใช้คุณควรเพิ่มผู้ใช้ในรายการผู้ใช้ในเอนทิตีกลุ่ม


1
cascade = CascadeType ไม่ควรตั้งค่าทั้งหมดเมื่อคุณต้องการใช้โซลูชันนี้ มิฉะนั้นจะทำงานได้สมบูรณ์แบบ!
ltsstar

@damian สวัสดีฉันใช้โซลูชันของคุณแล้ว แต่ฉันมีข้อผิดพลาดพร้อมกัน ModficationException ฉันคิดว่าขณะที่คุณชี้ให้เห็นเหตุผลอาจเป็นเพราะกลุ่มต้องมีรายชื่อผู้ใช้ที่อัปเดต เพราะถ้าฉันเพิ่มมากกว่าหนึ่งกลุ่มให้กับผู้ใช้ฉันจะได้รับข้อยกเว้นนี้ ...
Adnan Abdul Khaliq

@Damian โปรดทราบว่าเพื่อให้สามารถใช้งานได้กลุ่มต้องมีรายชื่อผู้ใช้ที่อัปเดต (ซึ่งไม่ได้ดำเนินการโดยอัตโนมัติ) ดังนั้นทุกครั้งที่คุณเพิ่มกลุ่มในรายการกลุ่มในเอนทิตีผู้ใช้คุณควรเพิ่มผู้ใช้ในรายการผู้ใช้ในเอนทิตีกลุ่ม คุณช่วยอธิบายรายละเอียดเกี่ยวกับเรื่องนี้ให้ฉันเป็นตัวอย่างได้ไหม, ขอบคุณ
Adnan Abdul Khaliq

27

ฉันพบทางออกที่เป็นไปได้ แต่ ... ฉันไม่รู้ว่ามันเป็นทางออกที่ดีหรือเปล่า

@Entity
public class Role extends Identifiable {

    @ManyToMany(cascade ={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinTable(name="Role_Permission",
            joinColumns=@JoinColumn(name="Role_id"),
            inverseJoinColumns=@JoinColumn(name="Permission_id")
        )
    public List<Permission> getPermissions() {
        return permissions;
    }

    public void setPermissions(List<Permission> permissions) {
        this.permissions = permissions;
    }
}

@Entity
public class Permission extends Identifiable {

    @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinTable(name="Role_Permission",
            joinColumns=@JoinColumn(name="Permission_id"),
            inverseJoinColumns=@JoinColumn(name="Role_id")
        )
    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

ฉันได้ลองแล้วและได้ผล เมื่อคุณลบบทบาทความสัมพันธ์จะถูกลบด้วย (แต่ไม่ใช่เอนทิตีสิทธิ์) และเมื่อคุณลบสิทธิ์ความสัมพันธ์กับบทบาทจะถูกลบไปด้วย (แต่ไม่ใช่อินสแตนซ์บทบาท) แต่เรากำลังทำแผนที่ความสัมพันธ์แบบทิศทางเดียวสองครั้งและทั้งสองเอนทิตีเป็นเจ้าของความสัมพันธ์ สิ่งนี้อาจทำให้เกิดปัญหาในการไฮเบอร์เนตหรือไม่? ปัญหาประเภทไหน

ขอบคุณ!

โค้ดด้านบนมาจากโพสต์อื่นที่เกี่ยวข้อง


ปัญหาการรีเฟรชคอลเลกชัน?
jelies

12
สิ่งนี้ไม่ถูกต้อง ManyToMany แบบสองทิศทางควรมีความสัมพันธ์ด้านเจ้าของเพียงด้านเดียว โซลูชันนี้ทำให้ทั้งสองฝ่ายเป็นเจ้าของและในที่สุดก็จะส่งผลให้เกิดการบันทึกซ้ำ เพื่อหลีกเลี่ยงระเบียนที่ซ้ำกันต้องใช้ชุดแทนรายการ แต่การใช้ Set เป็นเพียงวิธีแก้ปัญหาสำหรับการทำสิ่งที่ไม่แนะนำ
L. Holanda

4
แล้วแนวทางปฏิบัติที่ดีที่สุดสำหรับสถานการณ์ทั่วไปเช่นนี้คืออะไร?
SalutonMondo

8

เป็นอีกทางเลือกหนึ่งสำหรับโซลูชัน JPA / Hibernate: คุณสามารถใช้ประโยค CASCADE DELETE ในนิยามฐานข้อมูลของคีย์ foregin บนตารางการรวมของคุณเช่น (ไวยากรณ์ Oracle):

CONSTRAINT fk_to_group
     FOREIGN KEY (group_id)
     REFERENCES group (id)
     ON DELETE CASCADE

ด้วยวิธีนี้ DBMS จะลบแถวที่ชี้ไปที่กลุ่มโดยอัตโนมัติเมื่อคุณลบกลุ่ม และใช้งานได้ไม่ว่าจะเป็นการลบจาก Hibernate / JPA, JDBC ด้วยตนเองใน DB หรือวิธีอื่น ๆ

คุณลักษณะการลบแบบเรียงซ้อนได้รับการสนับสนุนโดย DBMS หลักทั้งหมด (Oracle, MySQL, SQL Server, PostgreSQL)


3

สำหรับสิ่งที่คุ้มค่าฉันใช้ EclipseLink 2.3.2.v20111125-r10461 และถ้าฉันมี @ManyToMany ความสัมพันธ์แบบทิศทางเดียวฉันสังเกตเห็นปัญหาที่คุณอธิบาย อย่างไรก็ตามถ้าฉันเปลี่ยนเป็นความสัมพันธ์ @ManyToMany แบบสองทิศทางฉันสามารถลบเอนทิตีออกจากฝั่งที่ไม่ได้เป็นเจ้าของและตาราง JOIN จะได้รับการอัปเดตอย่างเหมาะสม ทั้งหมดนี้ไม่มีการใช้คุณลักษณะแบบเรียงซ้อนใด ๆ


2

สิ่งนี้ใช้ได้กับฉัน:

@Transactional
public void remove(Integer groupId) {
    Group group = groupRepository.findOne(groupId);
    group.getUsers().removeAll(group.getUsers());

    // Other business logic

    groupRepository.delete(group);
}

นอกจากนี้ให้ทำเครื่องหมายวิธีการ @Transactional (org.springframework.transaction.annotation.Transactional) ซึ่งจะทำกระบวนการทั้งหมดในเซสชันเดียวช่วยประหยัดเวลา


1

นี่เป็นทางออกที่ดี ส่วนที่ดีที่สุดคือด้าน SQL - การปรับจูนระดับใดก็ได้เป็นเรื่องง่าย

ฉันใช้ MySql และ MySql Workbench เพื่อ Cascade เมื่อทำการลบสำหรับ Foreign KEY ที่จำเป็น

ALTER TABLE schema.joined_table 
ADD CONSTRAINT UniqueKey
FOREIGN KEY (key2)
REFERENCES schema.table1 (id)
ON DELETE CASCADE;

ยินดีต้อนรับสู่ SO โปรดใช้เวลาสักครู่และตรวจสอบสิ่งนี้เพื่อปรับปรุงการจัดรูปแบบและการอ่านหลักฐานของคุณ: stackoverflow.com/help/how-to-ask
petezurich

0

สำหรับกรณีของฉันฉันลบ mappedBy และเข้าร่วมตารางดังนี้:

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "user_group", joinColumns = {
        @JoinColumn(name = "user", referencedColumnName = "user_id")
}, inverseJoinColumns = {
        @JoinColumn(name = "group", referencedColumnName = "group_id")
})
private List<User> users;

@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JsonIgnore
private List<Group> groups;

3
อย่าใช้ cascadeType.all แบบหลายต่อหลายคน ในการลบจะทำให้ระเบียน A ลบระเบียน B ที่เกี่ยวข้องทั้งหมด และระเบียน B จะลบระเบียน A ที่เกี่ยวข้องทั้งหมด และอื่น ๆ ไม่ใหญ่
Josiah

0

สิ่งนี้ใช้ได้กับฉันในปัญหาที่คล้ายกันซึ่งฉันไม่สามารถลบผู้ใช้เนื่องจากข้อมูลอ้างอิง ขอขอบคุณ

@ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST,CascadeType.REFRESH})

0

นี่คือสิ่งที่ฉันทำ หวังว่าจะมีคนเห็นว่ามีประโยชน์

@Transactional
public void deleteGroup(Long groupId) {
    Group group = groupRepository.findById(groupId).orElseThrow();
    group.getUsers().forEach(u -> u.getGroups().remove(group));
    userRepository.saveAll(group.getUsers());
    groupRepository.delete(group);
}

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