JPA में ManyToMany संबंध के साथ इकाई कैसे निकालें (और इसी तालिका पंक्तियों में शामिल हों)?


91

मान लीजिए कि मेरी दो इकाइयाँ हैं: समूह और उपयोगकर्ता। हर उपयोगकर्ता कई समूहों का सदस्य हो सकता है और हर समूह में कई उपयोगकर्ता हो सकते हैं।

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

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

अब मैं एक समूह को हटाना चाहता हूं (मान लें कि इसके कई सदस्य हैं)।

समस्या यह है कि जब मैं कुछ ग्रुप पर EntityManager.remove () कॉल करता हूं, तो JPA प्रदाता (मेरे मामले में हाइबरनेट) पंक्तियों को ज्वाइन टेबल से नहीं हटाता है और विदेशी मुख्य बाधाओं के कारण ऑपरेशन विफल हो जाता है। उपयोगकर्ता को हटाने का आह्वान () ठीक काम करता है (मुझे लगता है कि इसका संबंध संबंधों के साथ कुछ करना है)।

तो मैं इस मामले में एक समूह को कैसे हटा सकता हूं?

जिस तरह से मैं साथ आ सकता था, वह है समूह के सभी उपयोगकर्ताओं को लोड करना, फिर प्रत्येक उपयोगकर्ता अपने समूहों से वर्तमान समूह को हटा दें और उपयोगकर्ता को अपडेट करें। लेकिन समूह से हर उपयोगकर्ता पर अपडेट () कॉल करने के लिए इस समूह को हटाने में सक्षम होना मेरे लिए हास्यास्पद लगता है।

जवाबों:


86
  • संबंध के स्वामित्व को निर्धारित किया जाता है जहां आप एनोटेशन के लिए 'मैप्डबी' विशेषता रखते हैं। 'MappedBy' लगाई जाने वाली इकाई वह है जो स्वामी नहीं है। मालिकों के लिए दोनों पक्षों के लिए कोई मौका नहीं है। यदि आपके पास 'उपयोगकर्ता को हटाएं' उपयोग-मामला नहीं है, तो आप बस स्वामित्व को Groupइकाई में स्थानांतरित कर सकते हैं , जैसा कि वर्तमान Userमें मालिक है।
  • दूसरी ओर, आप इसके बारे में नहीं पूछ रहे हैं, लेकिन एक बात जानने लायक है। groupsऔर usersएक दूसरे के साथ संयुक्त नहीं कर रहे हैं। मेरा मतलब है, Group1.users से User1 उदाहरण हटाने के बाद, User1.groups संग्रह स्वचालित रूप से नहीं बदला गया है (जो मेरे लिए काफी आश्चर्यजनक है),
  • सब सब में, मैं आपको सुझाव दूंगा कि मालिक कौन है। मान लीजिए कि Userमालिक है। फिर किसी उपयोगकर्ता को हटाते समय संबंधित उपयोगकर्ता-समूह स्वचालित रूप से अपडेट हो जाएगा। लेकिन जब किसी समूह को हटाते हैं तो आपको इस तरह से स्वयं को हटाने का ध्यान रखना पड़ता है:

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

Thnx! मुझे भी यही समस्या थी और आपके समाधान ने इसे हल किया। लेकिन मुझे पता होना चाहिए कि क्या इस समस्या को हल करने का कोई और तरीका है। यह एक भयानक कोड का उत्पादन करता है। यह em.remove (इकाई) क्यों नहीं हो सकता है और यह है?
रॉय फ़्रीफ़ेल्ड

2
क्या यह पर्दे के पीछे से अनुकूलित है? cuz मैं पूरे डेटासेट को क्वेरी नहीं करना चाहता।
गत

1
यह वास्तव में बुरा दृष्टिकोण है, अगर आपके पास उस समूह के कई हजारों उपयोगकर्ता हैं तो क्या होगा?
सेर्गेई सोकोलेंको

2
यह संबंध तालिका को अपडेट नहीं करता है, संबंध तालिका में इस संबंध के बारे में पंक्तियां अभी भी बनी हुई हैं। मैंने परीक्षण नहीं किया, लेकिन इससे समस्याएँ पैदा हो सकती हैं।
Saygın Doğu

@SergiySokolenko लूप के लिए कोई डेटाबेस क्वेरी निष्पादित नहीं की जाती है। ट्रांसेक्शनल फ़ंक्शन के बचे रहने के बाद सभी को बैच दिया गया है।
Kilves

42

निम्नलिखित मेरे लिए काम करता है। निम्न विधि को उस इकाई में जोड़ें जो संबंध का स्वामी नहीं है (समूह)

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

ध्यान रखें कि इसके लिए काम करने के लिए, समूह के पास उपयोगकर्ताओं की एक अद्यतन सूची होनी चाहिए (जो स्वचालित रूप से नहीं होती है)। इसलिए हर बार जब आप उपयोगकर्ता इकाई में समूह सूची में एक समूह जोड़ते हैं, तो आपको उपयोगकर्ता को समूह इकाई में उपयोगकर्ता सूची में भी जोड़ना चाहिए।


1
जब आप इस समाधान का उपयोग करना चाहते हैं तो कैस्केड = CascadeType.ALL को सेट नहीं किया जाना चाहिए। अन्यथा यह सही काम करता है!
ltsstar

@ एडमियन हाय मैंने आपके समाधान का उपयोग किया है, लेकिन मुझे एक त्रुटि मिली है समवर्तीमॉडिफिकेशन अपवाद। मुझे लगता है कि आपने कारण बताया कि समूह के पास उपयोगकर्ताओं की अद्यतन सूची होनी चाहिए। क्योंकि अगर मैं उपयोगकर्ता को एक से अधिक समूह जोड़ता हूं तो मुझे यह अपवाद मिलता है ...
अदनान अब्दुल खालिक

@ डैमियन ध्यान रखें कि इस काम के लिए, समूह के पास उपयोगकर्ताओं की एक अद्यतन सूची होनी चाहिए (जो स्वचालित रूप से नहीं की जाती है)। इसलिए हर बार जब आप उपयोगकर्ता इकाई में समूह सूची में एक समूह जोड़ते हैं, तो आपको समूह इकाई में उपयोगकर्ता सूची में एक उपयोगकर्ता भी जोड़ना चाहिए। क्या आप इस बारे में विस्तार से बता सकते हैं, मुझे एक उदाहरण दें, धन्यवाद
अदनान अब्दुल खालिक

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;
    }

मैंने यह कोशिश की है और यह काम करता है। जब आप भूमिका को हटाते हैं, तो संबंध भी हटा दिए जाते हैं (लेकिन अनुमति निकाय नहीं) और जब आप अनुमति हटाते हैं, तो भूमिका के साथ संबंध भी हटा दिए जाते हैं (लेकिन भूमिका उदाहरण नहीं)। लेकिन हम दो बार एक यूनिडायरेक्शनल रिलेशनशिप को मैप कर रहे हैं और दोनों एंटिटी रिलेशन के मालिक हैं। क्या इससे हाइबरनेट को कुछ समस्याएं हो सकती हैं? किस प्रकार की समस्याएं?

धन्यवाद!

ऊपर दिया गया कोड संबंधित किसी अन्य पोस्ट से है।


संग्रह ताज़ा समस्याओं?
जेलियाँ

12
यह सही नहीं है। द्विदिश ManyToMany रिश्ते का एक और केवल एक मालिक पक्ष होना चाहिए। यह समाधान दोनों पक्षों को मालिक बना रहा है और अंततः डुप्लिकेट रिकॉर्ड में परिणाम देगा। डुप्लिकेट रिकॉर्ड से बचने के लिए, सूचियों के बजाय सेट का उपयोग किया जाना चाहिए। लेकिन, सेट का उपयोग केवल कुछ ऐसा करने के लिए एक समाधान है जो अनुशंसित नहीं है।
एल। होलांडा

4
तो फिर इस तरह के एक सामान्य दर्शनीय स्थल के लिए सबसे अच्छा अभ्यास क्या है?
सालुटोनमांडो

8

JPA / हाइबरनेट समाधान के विकल्प के रूप में: आप अपनी ज्वाइन टेबल पर अपनी फोरगिन कुंजी की डेटाबेस परिभाषा में CASCADE DELETE क्लॉज का उपयोग कर सकते हैं, जैसे (Oracle सिंटैक्स):

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

इस तरह DBMS खुद ही उस पंक्ति को हटा देता है जो समूह को हटाते समय समूह को इंगित करता है। और यह काम करता है कि क्या डिलीट हाइबरनेट / जेपीए, जेडीबीसी से किया जाता है, मैन्युअल रूप से डीबी या किसी अन्य तरीके से।

कैस्केड हटाने की सुविधा सभी प्रमुख 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 कार्यक्षेत्र का उपयोग कैस्केड के लिए किया।

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

एसओ में आपका स्वागत है। कृपया एक क्षण लें और अपने स्वरूपण और प्रूफ रीडिंग को बेहतर बनाने के लिए इसे देखें: stackoverflow.com/help/how-to-ask
petezurich

1

यही मैंने समाप्त किया। उम्मीद है कि कोई इसे उपयोगी पा सकता है।

@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);
}

0

मेरे मामले के लिए, मैंने मैप किए गए को हटा दिया और इस तरह तालिकाओं में शामिल हो गया:

@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 का उपयोग न करें। डिलीट करने पर यह ए से जुड़े सभी बी रिकॉर्ड्स को डिलीट करने का रिकॉर्ड बनाता है। और बी रिकॉर्ड सभी संबंधित ए रिकॉर्ड को हटाते हैं। और इसी तरह। बड़ा ना-ना।
योशिय्याह

0

यह मेरे लिए एक समान मुद्दे पर काम करता है जहां मैं संदर्भ के कारण उपयोगकर्ता को हटाने में विफल रहा। धन्यवाद

@ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST,CascadeType.REFRESH})
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.