जेपीए हैशकोड () / बराबर () दुविधा


311

किया गया है कुछ विचार विमर्श जेपीए संस्थाओं और जिसके बारे में यहां hashCode()/equals() कार्यान्वयन जेपीए इकाई वर्गों के लिए इस्तेमाल किया जाना चाहिए। उनमें से अधिकांश (यदि सभी नहीं) हाइबरनेट पर निर्भर करते हैं, लेकिन मैं उन पर चर्चा करना चाहूंगा जेपीए-कार्यान्वयन-न्यूट्रली (मैं एक्लिप्सलिंक का उपयोग कर रहा हूं, वैसे)।

सभी संभावित कार्यान्वयन के अपने फायदे और नुकसान हैं :

  • hashCode()/equals() अनुबंध अनुरूपता (अपरिवर्तनीयता) के लिए List/Set संचालन
  • चाहे समान हो ऑब्जेक्ट्स (जैसे विभिन्न सत्रों से, आलसी-लोड डेटा संरचनाओं से गतिशील प्रॉक्सी) का पता लगाया जा सकता है
  • क्या निकाय अलग (या गैर-जारी) स्थिति में सही व्यवहार करते हैं

जहाँ तक मैं देख सकता हूँ, वहाँ तीन विकल्प हैं :

  1. उन्हें ओवरराइड न करें; भरोसा करो Object.equals()औरObject.hashCode()
    • hashCode()/ यह equals()काम
    • समान वस्तुओं की पहचान नहीं कर सकते, गतिशील परदे के पीछे की समस्याओं
    • अलग संस्थाओं के साथ कोई समस्या नहीं है
  2. प्राथमिक कुंजी के आधार पर, उन्हें ओवरराइड करें
    • hashCode()/ equals()टूट गए हैं
    • सही पहचान (सभी प्रबंधित संस्थाओं के लिए)
    • अलग संस्थाओं के साथ समस्याएं
  3. उन्हें ओवरराइड करें, बिज़नेस-ईद (गैर-प्राथमिक कुंजी फ़ील्ड के आधार पर ; विदेशी कुंजियों के बारे में क्या?)
    • hashCode()/ equals()टूट गए हैं
    • सही पहचान (सभी प्रबंधित संस्थाओं के लिए)
    • अलग संस्थाओं के साथ कोई समस्या नहीं है

मेरे प्रश्न हैं:

  1. क्या मुझे एक विकल्प और / या समर्थक / कांग्रेस बिंदु याद आया?
  2. आपने क्या विकल्प चुना और क्यों?



अद्यतन 1:

द्वारा " hashCode()/ equals()टूट रहे हैं", मुझे लगता है कि लगातार मतलब hashCode()आमंत्रण भिन्न मूल्यों वापस आ सकते हैं, जो है (जब सही ढंग से लागू) के अर्थ में टूट नहीं ObjectAPI दस्तावेज़ है, लेकिन जब एक से एक बदली हुई इकाई प्राप्त करने का प्रयास है जो समस्याओं का कारण बनता Map, Setया अन्य हैश-आधारित Collection। नतीजतन, जेपीए कार्यान्वयन (कम से कम एक्लिप्सलिंक) कुछ मामलों में सही ढंग से काम नहीं करेगा।

अद्यतन 2:

आपके उत्तर के लिए धन्यवाद - उनमें से अधिकांश में उल्लेखनीय गुणवत्ता है।
दुर्भाग्य से, मैं अभी भी अनिश्चित हूं कि वास्तविक जीवन के आवेदन के लिए कौन सा दृष्टिकोण सबसे अच्छा होगा, या मेरे आवेदन के लिए सबसे अच्छा तरीका कैसे निर्धारित किया जाएगा। इसलिए, मैं प्रश्न को खुला रखूंगा और कुछ और चर्चाओं और / या राय की उम्मीद करूंगा।


4
मुझे समझ में नहीं आ रहा है कि "हैशकोड () / बराबरी () टूटा हुआ" से क्या मतलब है
नंद

4
वे उस अर्थ में "टूटे" नहीं होंगे, जैसा कि विकल्प 2 और 3 में आप एक ही रणनीति का उपयोग करके दोनों समान () और हैशकोड () को लागू करेंगे।
मैट बी

11
यह विकल्प 3 के सच नहीं है। हैशकोड () और बराबर () समान मापदंड का उपयोग करना चाहिए, इसलिए यदि आपका कोई फ़ील्ड बदलता है, तो हाँ हैशकोड () विधि पिछले उदाहरण की तुलना में उसी उदाहरण के लिए एक अलग मान लौटाएगी, लेकिन इतना ही बराबर होगा ()। आपने हैशकोड () javadoc से वाक्य के दूसरे भाग को छोड़ दिया है: जब भी जावा एप्लिकेशन के निष्पादन के दौरान एक से अधिक बार एक ही वस्तु पर इसे लगाया जाता है, हैशकोड विधि को लगातार एक ही पूर्णांक वापस करना चाहिए, बशर्ते कोई जानकारी नहीं ऑब्जेक्ट पर तुलना के बराबर उपयोग किया जाता है
मैट बी

1
वास्तव में वाक्य के उस हिस्से का अर्थ है विपरीत - hashcode()एक ही वस्तु उदाहरण पर कॉल करना, उसी मूल्य को वापस करना चाहिए, जब तक कि equals()कार्यान्वयन में उपयोग किए गए किसी भी फ़ील्ड का उपयोग नहीं किया जाता है । दूसरे शब्दों में, यदि आपके पास अपनी कक्षा में तीन फ़ील्ड हैं और आपकी equals()विधि उदाहरणों की समानता का निर्धारण करने के लिए उनमें से केवल दो का उपयोग करती है, तो आप hashcode()उन फ़ील्ड के मूल्यों में से एक को बदलने के लिए वापसी मूल्य की उम्मीद कर सकते हैं - जो आपके विचार करने पर समझ में आता है यह ऑब्जेक्ट इंस्टेंस अब उस मूल्य के लिए "बराबर" नहीं है जो पुराने इंस्टेंस का प्रतिनिधित्व करता है।
मैट बी

2
"किसी मैप, सेट या अन्य हैश-आधारित संग्रह से परिवर्तित इकाई को पुनः प्राप्त करने की कोशिश करते समय समस्याएँ" ... यह तब होना चाहिए "जब किसी हैशपॉइंट, हैशसेट या अन्य हैश-आधारित संग्रह से परिवर्तित इकाई को पुनः प्राप्त करने का प्रयास कर रहे हों
नंदा

जवाबों:


122

इस विषय पर यह बहुत अच्छा लेख पढ़ें: अपनी पहचान चोरी न करें

लेख का निष्कर्ष इस प्रकार है:

ऑब्जेक्ट की पहचान सही ढंग से सही ढंग से लागू करने के लिए कठिन होती है जब ऑब्जेक्ट डेटाबेस से जुड़े होते हैं। हालाँकि, समस्याएँ पूरी तरह से वस्तुओं को बिना आईडी के अस्तित्व में आने से पहले ही बचा देती हैं। हम हाइबरनेट जैसे ऑब्जेक्ट-रिलेशनल मैपिंग फ़्रेमवर्क से ऑब्जेक्ट आईडी असाइन करने की ज़िम्मेदारी लेकर इन समस्याओं को हल कर सकते हैं। इसके बजाय, ऑब्जेक्ट ID को ऑब्जेक्ट के तुरंत बाद असाइन किया जा सकता है। यह ऑब्जेक्ट पहचान को सरल और त्रुटि मुक्त बनाता है, और डोमेन मॉडल में आवश्यक कोड की मात्रा को कम करता है।


21
नहीं, यह एक अच्छा लेख नहीं है। यह इस विषय पर एक शानदार लेख है, और इसे हर JPA प्रोग्रामर के लिए पढ़ना चाहिए! +1!
टॉम एंडरसन

2
हाँ, मैं उसी समाधान का उपयोग कर रहा हूं। डीबी को उत्पन्न नहीं होने देने से आईडी के अन्य फायदे भी हैं, जैसे कि एक ऑब्जेक्ट बनाने में सक्षम होना और इसे बनाए रखने से पहले इसे संदर्भित करने वाली अन्य ऑब्जेक्ट पहले से ही बनाना। यह क्लाइंट-सर्वर ऐप्स में विलंबता और कई अनुरोध / प्रतिक्रिया चक्रों को हटा सकता है। यदि आपको इस तरह के समाधान के लिए प्रेरणा की आवश्यकता है, तो मेरी परियोजनाओं की जांच करें: suid.js और suid-server-java । मूल रूप suid.jsसे आईडी ब्लॉक प्राप्त करते हैं, suid-server-javaजहां से आप क्लाइंट-साइड प्राप्त कर सकते हैं और उपयोग कर सकते हैं।
स्टिजन डे विट

2
यह बस पागल है। मैं हुड के तहत कामकाज को हाइबरनेट करने के लिए नया हूं, इकाई परीक्षण लिख रहा था, और पता चला कि मैं इसे संशोधित करने के बाद एक सेट से किसी ऑब्जेक्ट को हटा नहीं सकता, यह निष्कर्ष निकाला कि यह हैशकोड परिवर्तन के कारण है, लेकिन यह समझने में असमर्थ था कि कैसे समाधान करना। लेख सरल भव्य है!
एक्समैट

यह एक बेहतरीन लेख है। हालाँकि, जो लोग पहली बार लिंक देखते हैं, मैं सुझाव दूंगा कि यह अधिकांश अनुप्रयोगों के लिए एक ओवरकिल हो सकता है। इस पृष्ठ पर सूचीबद्ध अन्य 3 विकल्प कम या ज्यादा को कई तरीकों से हल करना चाहिए।
होपकिंग

1
क्या डेटाबेस में रिकॉर्ड पहले से मौजूद है या नहीं यह जांचने के लिए हाइबरनेट / जेपीए एक इकाई के बराबर और हैशकोड विधि का उपयोग करता है?
तुषार बन्ने

64

मैं हमेशा बराबर / हैशकोड को ओवरराइड करता हूं और बिजनेस आईडी के आधार पर इसे लागू करता हूं। मेरे लिए सबसे उचित समाधान लगता है। निम्नलिखित लिंक देखें ।

यह सब सामान समेटने के लिए, यहाँ इस बात की एक सूची दी गई है कि समान / हैशकोड को संभालने के लिए अलग-अलग तरीकों से क्या काम करेगा या नहीं करेगा: यहां छवि विवरण दर्ज करें

संपादित करें :

यह समझाने के लिए कि यह मेरे लिए क्यों काम करता है:

  1. मैं आमतौर पर अपने JPA एप्लिकेशन में हैशेड-आधारित संग्रह (HashMap / HashSet) का उपयोग नहीं करता हूं। अगर मुझे चाहिए, तो मैं UniqueList समाधान बनाना पसंद करता हूं।
  2. मुझे लगता है कि रनटाइम पर बिजनेस आईडी बदलना किसी भी डेटाबेस एप्लिकेशन के लिए सबसे अच्छा अभ्यास नहीं है। ऐसे दुर्लभ मामलों पर जहां कोई अन्य समाधान नहीं है, मैं विशेष उपचार करना चाहता हूं जैसे तत्व को हटा दें और इसे हैशेड-आधारित संग्रह पर वापस रख दें।
  3. अपने मॉडल के लिए, मैंने व्यवसाय आईडी को कंस्ट्रक्टर पर सेट किया है और इसके लिए सेटर्स प्रदान नहीं करता है। मैंने क्षेत्र बदलने के लिए जेपीए को लागू करने दिया संपत्ति के बदले ।
  4. UUID समाधान ओवरकिल लगता है। यदि आपके पास प्राकृतिक व्यवसाय आईडी है तो UUID क्यों? मैं सब के बाद डेटाबेस में बिजनेस आईडी की विशिष्टता सेट करूंगा। तब डेटाबेस में प्रत्येक तालिका के लिए तीन सूचकांक क्यों हैं ?

1
लेकिन इस तालिका में एक पांचवीं पंक्ति का "सूची / सेट्स के साथ काम करता है" (यदि आप एक इकाई को हटाने के बारे में सोचते हैं जो एक OneToMany मानचित्रण से एक सेट का हिस्सा है) का अभाव है, जिसका उत्तर अंतिम दो विकल्पों पर "नहीं" दिया जाएगा क्योंकि इसका हैशकोड ) परिवर्तन जो इसके अनुबंध का उल्लंघन करता है।
MRalwasser

प्रश्न पर टिप्पणी देखें। आप बराबर / हैशकोड अनुबंध को गलत समझ रहे हैं
नंद

1
@MRwwasser: मुझे लगता है कि आप सही बात का मतलब है, यह सिर्फ बराबरी / हैशकोड () अनुबंध नहीं है जो उल्लंघन है। लेकिन एक उत्परिवर्तित बराबरी / हैशकोड सेट अनुबंध के साथ समस्याएं पैदा करता है ।
क्रिस लेचर

3
@Malwasser: हैशकोड तभी बदल सकता है जब बिज़नेस आईडी बदलती है, और मुद्दा यह है कि बिज़नेस आईडी नहीं बदलती है। इसलिए हैशकोड नहीं बदलता है, और यह पूरी तरह से हैशेड संग्रह के साथ काम करता है।
टॉम एंडरसन

1
यदि आपके पास प्राकृतिक व्यवसाय कुंजी नहीं है तो क्या होगा? उदाहरण के लिए, दो-आयामी बिंदु के मामले में, बिंदु (एक्स, वाई), एक ग्राफ-ड्राइंग अनुप्रयोग में? आप उस बिंदु को इकाई के रूप में कैसे संग्रहीत करेंगे?
jhegedus

35

हमारे पास आमतौर पर दो आईडी हैं:

  1. दृढ़ता परत के लिए ही है (ताकि दृढ़ता प्रदाता और डेटाबेस वस्तुओं के बीच संबंधों का पता लगा सकें)।
  2. हमारे आवेदन की जरूरतों के लिए है ( equals()और hashCode()विशेष रूप से)

जरा देखो तो:

@Entity
public class User {

    @Id
    private int id;  // Persistence ID
    private UUID uuid; // Business ID

    // assuming all fields are subject to change
    // If we forbid users change their email or screenName we can use these
    // fields for business ID instead, but generally that's not the case
    private String screenName;
    private String email;

    // I don't put UUID generation in constructor for performance reasons. 
    // I call setUuid() when I create a new entity
    public User() {
    }

    // This method is only called when a brand new entity is added to 
    // persistence context - I add it as a safety net only but it might work 
    // for you. In some cases (say, when I add this entity to some set before 
    // calling em.persist()) setting a UUID might be too late. If I get a log 
    // output it means that I forgot to call setUuid() somewhere.
    @PrePersist
    public void ensureUuid() {
        if (getUuid() == null) {
            log.warn(format("User's UUID wasn't set on time. " 
                + "uuid: %s, name: %s, email: %s",
                getUuid(), getScreenName(), getEmail()));
            setUuid(UUID.randomUUID());
        }
    }

    // equals() and hashCode() rely on non-changing data only. Thus we 
    // guarantee that no matter how field values are changed we won't 
    // lose our entity in hash-based Sets.
    @Override
    public int hashCode() {
        return getUuid().hashCode();
    }

    // Note that I don't use direct field access inside my entity classes and
    // call getters instead. That's because Persistence provider (PP) might
    // want to load entity data lazily. And I don't use 
    //    this.getClass() == other.getClass() 
    // for the same reason. In order to support laziness PP might need to wrap
    // my entity object in some kind of proxy, i.e. subclassing it.
    @Override
    public boolean equals(final Object obj) {
        if (this == obj)
            return true;
        if (!(obj instanceof User))
            return false;
        return getUuid().equals(((User) obj).getUuid());
    }

    // Getters and setters follow
}

संपादित करें:setUuid() विधि के लिए कॉल के संबंध में अपनी बात स्पष्ट करने के लिए । यहाँ एक विशिष्ट परिदृश्य है:

User user = new User();
// user.setUuid(UUID.randomUUID()); // I should have called it here
user.setName("Master Yoda");
user.setEmail("yoda@jedicouncil.org");

jediSet.add(user); // here's bug - we forgot to set UUID and 
                   //we won't find Yoda in Jedi set

em.persist(user); // ensureUuid() was called and printed the log for me.

jediCouncilSet.add(user); // Ok, we got a UUID now

जब मैं अपने परीक्षण चलाता हूं और लॉग आउटपुट देखता हूं तो मैं समस्या को हल करता हूं:

User user = new User();
user.setUuid(UUID.randomUUID());

वैकल्पिक रूप से, एक अलग कंस्ट्रक्टर प्रदान कर सकता है:

@Entity
public class User {

    @Id
    private int id;  // Persistence ID
    private UUID uuid; // Business ID

    ... // fields

    // Constructor for Persistence provider to use
    public User() {
    }

    // Constructor I use when creating new entities
    public User(UUID uuid) {
        setUuid(uuid);
    }

    ... // rest of the entity.
}

तो मेरा उदाहरण इस तरह दिखेगा:

User user = new User(UUID.randomUUID());
...
jediSet.add(user); // no bug this time

em.persist(user); // and no log output

मैं एक डिफाल्टर कंस्ट्रक्टर और एक सेटर का उपयोग करता हूं, लेकिन आपको दो-कंस्ट्रक्टर्स का दृष्टिकोण आपके लिए अधिक उपयुक्त लग सकता है।


2
मेरा मानना ​​है, कि यह एक सही और अच्छा उपाय है। इसका थोड़ा प्रदर्शन लाभ भी हो सकता है, क्योंकि पूर्णांक आमतौर पर uuids की तुलना में डेटाबेस इंडेक्स में बेहतर प्रदर्शन करते हैं। लेकिन इसके अलावा, आप शायद वर्तमान पूर्णांक आईडी संपत्ति को समाप्त कर सकते हैं, और इसे (आवेदन सौंपा) uuid से बदल सकते हैं?
क्रिस लेचर

4
यह JVM समानता और दृढ़ता समानता के लिए डिफ़ॉल्ट hashCode/ equalsविधियों का उपयोग करने के लिए कैसे अलग है id? यह मेरे लिए बिल्कुल भी समझ में नहीं आता है।
बेहरंग सईदज़ादेह

2
यह उन मामलों में काम करता है जब आपके पास डेटाबेस में एक ही पंक्ति की ओर इशारा करते हुए कई इकाई ऑब्जेक्ट होते हैं। Objectकी equals()वापसी होगी falseइस मामले में। UUID- आधारित equals()रिटर्न true
एंड्रयू Андрей Листочкин

4
-1 - मुझे दो आईडी और किसी भी प्रकार की पहचान के लिए कोई कारण नहीं दिखता है। यह मेरे लिए पूरी तरह से व्यर्थ और संभावित रूप से हानिकारक लगता है।
टॉम एंडरसन

1
एक को इंगित किए बिना आपके समाधान की आलोचना करने के लिए क्षमा करें। संक्षेप में, मैं ऑब्जेक्ट्स को एक एकल आईडी फ़ील्ड दूंगा, मैं उसके आधार पर समान और हैशकोड लागू करूंगा, और डेटाबेस को सहेजते समय मैं ऑब्जेक्ट निर्माण पर इसके मूल्य को उत्पन्न करूंगा। इस तरह, ऑब्जेक्ट के सभी रूप उसी तरह से काम करते हैं: गैर-स्थिर, लगातार और अलग। हाइबरनेट प्रॉक्सी (या समान) को भी सही ढंग से काम करना चाहिए, और मुझे लगता है कि समान और हैशकोड कॉल को संभालने के लिए हाइड्रेटेड रहने की भी आवश्यकता नहीं होगी।
टॉम एंडरसन

31

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

लेकिन आगे भी विकल्प हैं (उनके पेशेवरों और विपक्षों के साथ):


a) हैशकोड / समान एक सेट के आधार पर अपरिवर्तनीय है , अशक्त नहीं , निर्माणकर्ता को सौंपा गया है , फ़ील्ड

(+) सभी तीन मानदंडों की गारंटी है

(-) फ़ील्ड मान एक नया उदाहरण बनाने के लिए उपलब्ध होना चाहिए

- () हैंडलिंग को जटिल बनाता है यदि आपको किसी एक को बदलना होगा


ख) हैशकोड / बराबर एक प्राथमिक कुंजी पर आधारित है जो JPA के बजाय एप्लिकेशन (कंस्ट्रक्टर में) द्वारा सौंपा गया है

(+) सभी तीन मानदंडों की गारंटी है

(-) आप डीबी अनुक्रमों की तरह साधारण विश्वसनीय आईडी जनरेशन स्टैटीज़ का लाभ नहीं उठा सकते हैं

(-) जटिल अगर एक वितरित वातावरण (क्लाइंट / सर्वर) या ऐप सर्वर क्लस्टर में नई इकाइयां बनाई जाती हैं


ग) हैशकोड / बराबर इकाई के निर्माता द्वारा सौंपे गए एक यूयूआईडी पर आधारित है

(+) सभी तीन मानदंडों की गारंटी है

(-) यूयूआईडी पीढ़ी का ओवरहेड

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


मैं विकल्प 1 और दृष्टिकोण सी का भी प्रशंसक हूं । तब तक कुछ भी न करें जब तक कि आपको बिल्कुल ज़रूरत न हो, यह अधिक चुस्त दृष्टिकोण है
एडम गेंट

2
विकल्प के लिए +1 (बी)। IMHO, यदि किसी इकाई के पास एक प्राकृतिक व्यवसाय आईडी है, तो वह उसका डेटाबेस प्राथमिक कुंजी भी होनी चाहिए। यह सरल, सीधा, अच्छा डेटाबेस डिज़ाइन है। यदि उसके पास ऐसी आईडी नहीं है, तो एक सरोगेट कुंजी की आवश्यकता है। यदि आप उसे वस्तु निर्माण में स्थापित करते हैं, तो बाकी सब कुछ सरल है। यह तब होता है जब लोग एक प्राकृतिक कुंजी का उपयोग नहीं करते हैं, और जल्दी से एक सरोगेट कुंजी उत्पन्न नहीं करते हैं जो उन्हें परेशानी में डालती है। कार्यान्वयन में जटिलता के लिए - हाँ, कुछ है। लेकिन वास्तव में बहुत कुछ नहीं है, और इसे बहुत ही सामान्य तरीके से किया जा सकता है जो इसे सभी संस्थाओं के लिए एक बार हल करता है।
टॉम एंडरसन

मैं विकल्प 1 भी पसंद करता हूं, लेकिन फिर पूर्ण समानता पर जोर देने के लिए यूनिट टेस्ट कैसे लिखा जाए यह एक बड़ी समस्या है, क्योंकि हमें इसके लिए समान पद्धति को लागू करना होगा Collection
OOD वाटरबॉल


29

यदि आप equals()/hashCode()अपने सेट के लिए उपयोग करना चाहते हैं , तो इस अर्थ में कि एक ही इकाई केवल एक बार वहां हो सकती है, तो केवल एक ही विकल्प है: विकल्प 2। ऐसा इसलिए है क्योंकि एक प्राथमिक कुंजी परिभाषा द्वारा किसी इकाई के लिए कभी नहीं बदलती है (यदि कोई वास्तव में अपडेट करता है यह, यह अब एक ही इकाई नहीं है)

आपको इसे सचमुच में लेना चाहिए: चूंकि आपके equals()/hashCode() प्राथमिक कुंजी पर आधारित है, इसलिए आपको इन विधियों का उपयोग नहीं करना चाहिए, जब तक कि प्राथमिक कुंजी सेट न हो जाए। इसलिए आपको सेट में इकाइयां नहीं डालनी चाहिए, जब तक कि उन्हें एक प्राथमिक कुंजी नहीं सौंपी जाए। (हां, यूयूआईडी और इसी तरह की अवधारणाएं प्राथमिक कुंजी को जल्दी असाइन करने में मदद कर सकती हैं।)

अब, यह सैद्धांतिक रूप से भी संभव है कि विकल्प 3 के साथ, भले ही तथाकथित "व्यापार-कुंजी" के पास कमियां हैं जो वे बदल सकते हैं: "आपको बस इतना करना होगा कि सेट से पहले से सम्मिलित संस्थाओं को हटा दें ( एस), और उन्हें फिर से डालें। " यह सच है - लेकिन इसका मतलब यह भी है, कि एक वितरित प्रणाली में, आपको यह सुनिश्चित करना होगा, कि यह पूरी तरह से हर जगह किया गया है जहां डेटा डाला गया है (और आपको यह सुनिश्चित करना होगा, कि अपडेट किया गया है , अन्य चीजों के होने से पहले)। आपको एक परिष्कृत अपडेट तंत्र की आवश्यकता होगी, खासकर अगर कुछ रिमोट सिस्टम वर्तमान में उपलब्ध नहीं हैं ...

विकल्प 1 का उपयोग केवल तभी किया जा सकता है, जब आपके सेट की सभी वस्तुएँ समान हाइबरनेट सत्र से हों। हाइबरनेट दस्तावेज़ अध्याय 13.1.3 में इसे बहुत स्पष्ट करता है वस्तु पहचान को ध्यान में रखते हुए :

एक सत्र के भीतर आवेदन वस्तुओं की तुलना करने के लिए सुरक्षित रूप से == का उपयोग कर सकता है।

हालाँकि, सत्र के बाहर == का उपयोग करने वाला अनुप्रयोग अप्रत्याशित परिणाम उत्पन्न कर सकता है। कुछ अप्रत्याशित स्थानों पर भी ऐसा हो सकता है। उदाहरण के लिए, यदि आप एक ही सेट में दो अलग-अलग उदाहरण डालते हैं, तो दोनों की समान डेटाबेस पहचान हो सकती है (यानी, वे एक ही पंक्ति का प्रतिनिधित्व करते हैं)। JVM पहचान, हालांकि, एक अलग राज्य में उदाहरणों के लिए गारंटी नहीं है। डेवलपर को लगातार कक्षाओं में बराबर () और हैशकोड () विधियों को ओवरराइड करना पड़ता है और ऑब्जेक्ट की एकता की अपनी धारणा को लागू करना पड़ता है।

यह विकल्प 3 के पक्ष में बहस करना जारी रखता है:

एक चेतावनी है: समानता को लागू करने के लिए डेटाबेस पहचानकर्ता का उपयोग कभी न करें। एक व्यवसाय कुंजी का उपयोग करें जो अद्वितीय, आमतौर पर अपरिवर्तनीय, विशेषताओं का एक संयोजन है। डेटाबेस पहचानकर्ता बदल जाएगा यदि एक क्षणिक वस्तु को लगातार बनाया जाता है। यदि क्षणिक आवृत्ति (आमतौर पर अलग-अलग उदाहरणों के साथ) एक सेट में आयोजित की जाती है, तो हैशकोड बदलने से सेट का अनुबंध टूट जाता है।

यह सच है, यदि आप

  • आईडी को शीघ्रता से निर्दिष्ट नहीं किया जा सकता है (जैसे यूयूआईडी का उपयोग करके)
  • और फिर भी आप बिल्कुल क्षणिक अवस्था में रहते हुए अपनी वस्तुओं को सेट में रखना चाहते हैं।

अन्यथा, आप विकल्प 2 का चयन करने के लिए स्वतंत्र हैं।

फिर इसमें एक सापेक्ष स्थिरता की आवश्यकता का उल्लेख किया गया है:

व्यावसायिक कुंजी के लिए विशेषताएँ डेटाबेस प्राथमिक कुंजी के रूप में स्थिर होने के लिए नहीं है; आपको केवल तब तक स्थिरता की गारंटी देनी है जब तक कि ऑब्जेक्ट एक ही सेट में हों।

यह सही है। इसके साथ मैं जो व्यावहारिक समस्या देख रहा हूं वह यह है: यदि आप पूर्ण स्थिरता की गारंटी नहीं दे सकते हैं, तो आप स्थिरता की गारंटी कैसे दे पाएंगे "जब तक वस्तुएं समान सेट में हैं"। मैं कुछ विशेष मामलों की कल्पना कर सकता हूं (जैसे केवल बातचीत के लिए सेट का उपयोग करना और फिर उसे फेंक देना), लेकिन मैं इस बात की सामान्य व्यावहारिकता पर सवाल उठाऊंगा।


लघु संस्करण:

  • विकल्प 1 का उपयोग केवल एक सत्र के भीतर वस्तुओं के साथ किया जा सकता है।
  • यदि आप कर सकते हैं, तो विकल्प 2 का उपयोग करें। (संभव के रूप में जल्द से जल्द पीके असाइन करें, क्योंकि आप सेट में ऑब्जेक्ट का उपयोग नहीं कर सकते जब तक कि पीके को असाइन नहीं किया गया है।)
  • यदि आप सापेक्ष स्थिरता की गारंटी दे सकते हैं, तो आप विकल्प 3 का उपयोग कर सकते हैं।

आपकी यह धारणा कि प्राथमिक कुंजी कभी नहीं बदलती है, झूठी है। उदाहरण के लिए, हाइबरनेट केवल सत्र को सहेजने पर प्राथमिक कुंजी आवंटित करता है। इसलिए, यदि आप प्राथमिक हैश का उपयोग अपने हैशकोड के रूप में करते हैं, तो इससे पहले कि आप ऑब्जेक्ट को सेव करते हैं, हैशकोड () का परिणाम होगा और आपके द्वारा पहली बार ऑब्जेक्ट को सेव करने के बाद अलग होगा। इससे भी बदतर, इससे पहले कि आप सत्र को बचाएं, दो नई बनाई गई वस्तुओं में समान हैशकोड होगा और संग्रह में जोड़े जाने पर एक-दूसरे को अधिलेखित कर सकते हैं। आप अपने आप को उस दृष्टिकोण का उपयोग करने के लिए ऑब्जेक्ट निर्माण पर तुरंत एक सहेजें / फ्लश करने के लिए मजबूर कर सकते हैं।
विलियम बिलिंग्सले

2
@William: एक इकाई की प्राथमिक कुंजी नहीं बदलती है। मैप की गई ऑब्जेक्ट की आईडी प्रॉपर्टी बदल सकती है। यह तब होता है, जैसा कि आपने समझाया, खासकर जब एक क्षणिक वस्तु को लगातार बनाया जाता है । कृपया मेरे उत्तर के भाग को ध्यान से पढ़ें, जहाँ मैंने बराबरी / हैशकोड विधियों के बारे में कहा था: "आपको इन विधियों का उपयोग नहीं करना चाहिए, जब तक कि प्राथमिक कुंजी सेट न हो जाए।"
क्रिस लेचर

पूर्णतया सहमत। विकल्प 2 के साथ, आप सुपर क्लास में बराबरी / हैशकोड का कारक भी बन सकते हैं और इसे आप सभी संस्थाओं द्वारा फिर से इस्तेमाल किया जा सकता है।
थियो

+1 मैं जेपीए के लिए नया हूं, लेकिन यहां कुछ टिप्पणियां और उत्तर का अर्थ है कि लोग "प्राथमिक कुंजी" शब्द का अर्थ नहीं समझते हैं।
रायादावल

16
  1. यदि आपके पास कोई व्यवसाय कुंजी है , तो आपको उसके लिए equals/ का उपयोग करना चाहिए hashCode
  2. यदि आपके पास कोई व्यवसाय कुंजी नहीं है, तो आपको इसे डिफ़ॉल्ट Objectबराबरी और हैशकोड कार्यान्वयन के साथ नहीं छोड़ना चाहिए क्योंकि यह आपके mergeऔर इकाई के बाद काम नहीं करता है ।
  3. आप इस पोस्ट में सुझाए गए अनुसार इकाई पहचानकर्ता का उपयोग कर सकते हैं । एकमात्र पकड़ यह है कि आपको एक hashCodeकार्यान्वयन का उपयोग करने की आवश्यकता है जो हमेशा एक ही मूल्य लौटाता है, जैसे:

    @Entity
    public class Book implements Identifiable<Long> {
    
        @Id
        @GeneratedValue
        private Long id;
    
        private String title;
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Book)) return false;
            Book book = (Book) o;
            return getId() != null && Objects.equals(getId(), book.getId());
        }
    
        @Override
        public int hashCode() {
            return 31;
        }
    
        //Getters and setters omitted for brevity
    }

कौन सा बेहतर है: (1) onjava.com/pub/a/onjava/2006/09/13/… या (2) vladmihalcea.com/… ? समाधान (2) (1) की तुलना में आसान है। तो मुझे (1) का उपयोग क्यों करना चाहिए क्या दोनों के प्रभाव समान हैं? क्या दोनों एक ही समाधान की गारंटी देते हैं?
निम्मो

और आपके समाधान के साथ: "हैशकोड मान नहीं बदलता" उसी उदाहरण के बीच। यह उसी तरह का व्यवहार है जैसे कि यह "एक ही" uid (समाधान (1) से) की तुलना की जा रही थी। क्या मैं सही हू?
निम्मो

1
और डेटाबेस में UUID को स्टोर करें और रिकॉर्ड और बफर पूल में पदचिह्न बढ़ाएं? मुझे लगता है कि यह अद्वितीय हैशकोड की तुलना में लंबे समय तक अधिक प्रदर्शन के मुद्दों को जन्म दे सकता है। अन्य समाधान के रूप में, आप यह देखने के लिए देख सकते हैं कि क्या यह सभी इकाई राज्य परिवर्तनों के बीच स्थिरता प्रदान करता है। आप उस परीक्षण को पा सकते हैं जो GitHub पर जाँच करता है
व्लादिमीर मिहालसी

1
यदि आपके पास एक अपरिवर्तनीय व्यावसायिक कुंजी है, तो हैशकोड इसका उपयोग कर सकता है और यह कई बाल्टियों से लाभान्वित होने वाला है, इसलिए यदि आपके पास एक है तो इसका उपयोग करने के लायक है। अन्यथा, केवल इकाई पहचानकर्ता का उपयोग करें जैसा कि मेरे लेख में बताया गया है।
व्लाद मिहालसी

1
मुझे खुशी है कि आपने इसे पसंद किया। मेरे पास जेपीए और हाइबरनेट के बारे में सैकड़ों और लेख हैं।
व्लाद मिहालसी

10

यद्यपि एक व्यावसायिक कुंजी (विकल्प 3) का उपयोग करना सबसे अधिक अनुशंसित दृष्टिकोण है ( हाइबरनेट समुदाय विकी , "जावा दृढ़ता के साथ हाइबरनेट" पृष्ठ 398), और यही वह है जो हम ज्यादातर उपयोग करते हैं, एक हाइबरनेट बग इसे उत्सुकता के लिए तोड़ता है। सेट: HHH-3799 । इस मामले में, हाइबरनेट अपने खेतों को आरंभीकृत करने से पहले एक सेट में एक इकाई जोड़ सकता है। मुझे यकीन नहीं है कि इस बग ने अधिक ध्यान क्यों नहीं दिया है, क्योंकि यह वास्तव में अनुशंसित व्यापार-कुंजी दृष्टिकोण को समस्याग्रस्त बनाता है।

मुझे लगता है कि इस मामले का दिल बराबर है और हैशकोड अपरिवर्तनीय स्थिति पर आधारित होना चाहिए (संदर्भ Okyky एट अल। ), और हाइबरनेट-प्रबंधित प्राथमिक कुंजी के साथ एक हाइबरनेट इकाई में ऐसी कोई अपरिवर्तनीय स्थिति नहीं है। प्राथमिक कुंजी को हाइबरनेट द्वारा संशोधित किया जाता है जब एक क्षणिक वस्तु लगातार बनी रहती है। व्यावसायिक कुंजी को हाइबरनेट द्वारा भी संशोधित किया जाता है, जब यह इनिशियलाइज़ होने की प्रक्रिया में किसी ऑब्जेक्ट को हाइड्रेट करता है।

यह केवल विकल्प 1 छोड़ता है, java.lang.Object कार्यान्वयन को प्राप्त करता है जो ऑब्जेक्ट पहचान पर आधारित है, या जेम्स ब्रंडेगे द्वारा सुझाए गए एप्लिकेशन-प्रबंधित प्राथमिक कुंजी का उपयोग करते हुए "डोंट लेट हाइबरनेट स्टील योर आइडेंटिटी" (पहले से ही Stijn Geukens के जवाब में संदर्भित) न करें। ) और लांस अर्लॉस द्वारा "ऑब्जेक्ट जनरेशन: ए बेटर अप्रोच टू हाइबरनेट इंटीग्रेशन"

विकल्प 1 के साथ सबसे बड़ी समस्या यह है कि अलग-अलग उदाहरणों की तुलना। किन्तु वह ठीक है; बराबरी और हैशकोड का अनुबंध डेवलपर को यह तय करने के लिए छोड़ देता है कि प्रत्येक वर्ग के लिए समानता का क्या अर्थ है। तो बस बराबर और हैशकोड ऑब्जेक्ट से विरासत में मिलता है। यदि आपको एक अलग उदाहरण से एक अलग उदाहरण की तुलना करने की आवश्यकता है, तो आप उस उद्देश्य के लिए एक नया तरीका स्पष्ट रूप से बना सकते हैंboolean sameEntity या boolean dbEquivalentया boolean businessEquals


5

मैं एंड्रयू के जवाब से सहमत हूं। हम अपने आवेदन में एक ही काम करते हैं लेकिन UUIDs को VARCHAR / CHAR के रूप में संग्रहीत करने के बजाय, हम इसे लंबे लंबे मानों में विभाजित करते हैं। UUID.getLeastSignificantBits () और UUID.getMostSignificantBits () देखें।

विचार करने के लिए एक और बात, यह है कि UUID.randomUUID () पर कॉल बहुत धीमी हैं, इसलिए आप केवल जरूरत पड़ने पर ही UUID को उत्पन्न करना चाहते हैं, जैसे दृढ़ता या कॉल के दौरान बराबर () / हैशकोड ()

@MappedSuperclass
public abstract class AbstractJpaEntity extends AbstractMutable implements Identifiable, Modifiable {

    private static final long   serialVersionUID    = 1L;

    @Version
    @Column(name = "version", nullable = false)
    private int                 version             = 0;

    @Column(name = "uuid_least_sig_bits")
    private long                uuidLeastSigBits    = 0;

    @Column(name = "uuid_most_sig_bits")
    private long                uuidMostSigBits     = 0;

    private transient int       hashCode            = 0;

    public AbstractJpaEntity() {
        //
    }

    public abstract Integer getId();

    public abstract void setId(final Integer id);

    public boolean isPersisted() {
        return getId() != null;
    }

    public int getVersion() {
        return version;
    }

    //calling UUID.randomUUID() is pretty expensive, 
    //so this is to lazily initialize uuid bits.
    private void initUUID() {
        final UUID uuid = UUID.randomUUID();
        uuidLeastSigBits = uuid.getLeastSignificantBits();
        uuidMostSigBits = uuid.getMostSignificantBits();
    }

    public long getUuidLeastSigBits() {
        //its safe to assume uuidMostSigBits of a valid UUID is never zero
        if (uuidMostSigBits == 0) {
            initUUID();
        }
        return uuidLeastSigBits;
    }

    public long getUuidMostSigBits() {
        //its safe to assume uuidMostSigBits of a valid UUID is never zero
        if (uuidMostSigBits == 0) {
            initUUID();
        }
        return uuidMostSigBits;
    }

    public UUID getUuid() {
        return new UUID(getUuidMostSigBits(), getUuidLeastSigBits());
    }

    @Override
    public int hashCode() {
        if (hashCode == 0) {
            hashCode = (int) (getUuidMostSigBits() >> 32 ^ getUuidMostSigBits() ^ getUuidLeastSigBits() >> 32 ^ getUuidLeastSigBits());
        }
        return hashCode;
    }

    @Override
    public boolean equals(final Object obj) {
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof AbstractJpaEntity)) {
            return false;
        }
        //UUID guarantees a pretty good uniqueness factor across distributed systems, so we can safely
        //dismiss getClass().equals(obj.getClass()) here since the chance of two different objects (even 
        //if they have different types) having the same UUID is astronomical
        final AbstractJpaEntity entity = (AbstractJpaEntity) obj;
        return getUuidMostSigBits() == entity.getUuidMostSigBits() && getUuidLeastSigBits() == entity.getUuidLeastSigBits();
    }

    @PrePersist
    public void prePersist() {
        // make sure the uuid is set before persisting
        getUuidLeastSigBits();
    }

}

ठीक है, वास्तव में यदि आप समतुल्य () / हैशकोड () को ओवरराइड करते हैं, तो आपको हर इकाई के लिए वैसे भी यूयूआईडी उत्पन्न करना होगा (मेरा मानना ​​है कि आप अपने कोड में आपके द्वारा बनाई गई प्रत्येक इकाई को बनाए रखना चाहते हैं)। आप इसे केवल एक बार करते हैं - पहली बार डेटाबेस में संग्रहीत करने से पहले। उसके बाद UUID सिर्फ Persistence प्रदाता द्वारा लोड किया जाता है। इस प्रकार मैं इसे lazily करने की बात नहीं देखता।
एंड्रयू Андрей Листочкин

मैंने आपके उत्तर को वोट दिया क्योंकि मैं वास्तव में आपके अन्य विचारों को पसंद करता हूं: यूयूआईडी को डेटाबेस में संख्याओं की एक जोड़ी के रूप में संग्रहीत करना और बराबर (विशेष) विधि के अंदर कास्टिंग नहीं करना - यह वास्तव में साफ है! मैं भविष्य में इन दो तरकीबों का उपयोग जरूर करूंगा।
एंड्रयू Андрей Листочкин

1
वोट के लिए धन्यवाद। UUID को प्रारंभिक रूप से शुरू करने का कारण हमारे ऐप में था कि हम बहुत सारी एंटिटी बनाते हैं जो कभी भी किसी HashMap में नहीं रखी जाती है या बनी रहती है। इसलिए हमने प्रदर्शन में 100x की गिरावट देखी जब हम ऑब्जेक्ट (उनमें से 100,000) बना रहे थे। इसलिए हम केवल UUID को ही भेजते हैं यदि इसकी आवश्यकता हो तो। मैं चाहता हूं कि 128 बिट संख्या के लिए MySql में अच्छा समर्थन था, इसलिए हम बस आईडी के लिए UUID का उपयोग कर सकते थे और auto_increment के बारे में परवाह नहीं करते थे।
आकर्षित किया

ओह मैं समझा। मेरे मामले में हम यूयूआईडी क्षेत्र की घोषणा भी नहीं करते हैं, अगर इसी इकाई को संग्रह में नहीं रखा जाएगा। दोष यह है कि कभी-कभी हमें इसे जोड़ना पड़ता है क्योंकि बाद में यह पता चलता है कि हमें वास्तव में उन्हें संग्रह में रखने की आवश्यकता है। यह कभी-कभी विकास के दौरान होता है, लेकिन सौभाग्य से हमारे लिए ग्राहक की प्रारंभिक तैनाती के बाद ऐसा कभी नहीं हुआ, इसलिए यह एक बड़ी बात नहीं थी। अगर ऐसा होता है तो सिस्टम लाइव होने के बाद हमें db माइग्रेशन की जरूरत होगी। आलसी यूयूआईडी ऐसी स्थितियों में बहुत मददगार होते हैं।
एंड्रयू Андрей Листочкин

हो सकता है कि आप तेजी से यूयूआईडी जनरेटर का प्रयास करें एडम ने उनके जवाब में सुझाव दिया था कि यदि प्रदर्शन आपकी स्थिति में एक महत्वपूर्ण मुद्दा है।
एंड्रयू Андрей Листочкин

3

जैसा कि अन्य लोग मुझसे पहले से इशारा कर चुके हैं, वैसे ही कई तरह की रणनीतियाँ हैं। हालांकि ऐसा लगता है कि लागू किए गए डिज़ाइन पैटर्न के अधिकांश लोग सफलता के लिए अपने तरीके को हैक करने की कोशिश करते हैं। यदि कंस्ट्रक्टर इनवोकेशन को पूरी तरह से विशेष कंस्ट्रक्टर्स और फैक्ट्री मेथड के साथ बाधित नहीं करते हैं तो वे कंस्ट्रक्टर एक्सेस को सीमित कर देते हैं। वास्तव में यह स्पष्ट कट एपीआई के साथ हमेशा सुखद होता है। लेकिन अगर एकमात्र कारण equals- और hashCode ओवरराइड आवेदन के साथ संगत होना करने के लिए है, तो मुझे आश्चर्य है कि अगर उन रणनीतियों KISS के अनुपालन में हैं (यह सरल बेवकूफ रखें)।

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

यद्यपि सावधानी के शब्द: यदि आपकी संस्थाएँ अलग-अलग तालिकाओं में संग्रहीत हैं और आपका प्रदाता प्राथमिक कुंजी के लिए एक ऑटो-पीढ़ी की रणनीति का उपयोग करता है, तो आपको इकाई प्रकारों में डुप्लिकेट की गई प्राथमिक कुंजियाँ मिलेंगी। ऐसी स्थिति में, रन टाइम टाइप्स को कॉल करने के लिए ऑब्जेक्ट # getClass () के साथ तुलना करें जो निश्चित रूप से यह असंभव बना देगा कि दो अलग-अलग प्रकार को समान माना जाता है। यह मुझे सबसे अधिक भाग के लिए ठीक लगता है।


यहां तक ​​कि एक डीबी लापता अनुक्रम (जैसे मैसिकल) के साथ, उन्हें अनुकरण करना संभव है (उदाहरण के लिए, टेबल हाइबरनेट_ परिणाम)। इसलिए आपको हमेशा तालिकाओं में एक अद्वितीय आईडी मिल सकती है। +++ लेकिन आपको इसकी आवश्यकता नहीं है। Object#getClass() एच। प्रॉक्सिस के कारण कॉलिंग खराब है। कॉलिंग Hibernate.getClass(o)मदद करता है, लेकिन विभिन्न प्रकार की संस्थाओं की समानता की समस्या बनी हुई है। वहाँ canEqual का उपयोग कर एक समाधान है , थोड़ा जटिल है, लेकिन प्रयोग करने योग्य है। सहमत है कि आमतौर पर इसकी जरूरत नहीं है। +++ null ID पर eq / hc में फेंकना अनुबंध का उल्लंघन करता है, लेकिन यह बहुत ही व्यावहारिक है।
मआर्टिनस

2

यहाँ स्पष्ट रूप से पहले से ही बहुत जानकारीपूर्ण उत्तर हैं लेकिन मैं आपको बताऊंगा कि हम क्या करते हैं।

हम कुछ नहीं करते (यानी ओवरराइड नहीं करते)।

अगर हमें यूयूआईडी का उपयोग करने वाले संग्रह के लिए काम करने के लिए समान / हैशकोड की आवश्यकता है। आप बस कंस्ट्रक्टर में यूयूआईडी बनाएं। हम UUID के लिए http://wiki.fasterxml.com/JugHome का उपयोग करते हैं । UUID थोड़ा अधिक महंगा CPU वार है, लेकिन क्रमांकन और DB अभिगम की तुलना में सस्ता है।


1

मैंने हमेशा अतीत में विकल्प 1 का उपयोग किया है क्योंकि मुझे इन चर्चाओं के बारे में पता था और सोचा था कि कुछ भी नहीं करना बेहतर है जब तक कि मुझे सही बात नहीं पता। वे प्रणालियां अभी भी सफलतापूर्वक चल रही हैं।

हालाँकि, अगली बार मैं विकल्प 2 का प्रयास कर सकता हूं - डेटाबेस से उत्पन्न आईडी का उपयोग करना।

अगर सेट नहीं किया गया है तो हैशकोड और बराबर इललीगलस्टैप्शन को फेंक देगा।

यह अनपेक्षित संस्थाओं को शामिल करने से होने वाली सूक्ष्म त्रुटियों को अप्रत्याशित रूप से प्रकट होने से रोकेगा।

लोग इस दृष्टिकोण के बारे में क्या सोचते हैं?


1

व्यावसायिक कुंजी दृष्टिकोण हमारे लिए उपयुक्त नहीं है। हम डीबी उत्पन्न का उपयोग आईडी , अस्थायी क्षणिक tempId और ओवरराइड बराबर () / hashCode () दुविधा को हल करने के। सभी संस्थाएँ एंटिटी की सन्तान हैं। पेशेवरों:

  1. डीबी में कोई अतिरिक्त क्षेत्र नहीं
  2. वंशज संस्थाओं में कोई अतिरिक्त कोडिंग नहीं, सभी के लिए एक दृष्टिकोण
  3. कोई प्रदर्शन समस्याएँ (जैसे UUID के साथ), DB Id पीढ़ी
  4. हश्मप्स के साथ कोई समस्या नहीं (बराबर और आदि के उपयोग को ध्यान में रखने की आवश्यकता नहीं है)
  5. नई इकाई के हैशकोड कायम रहने के बाद भी समय में बदलाव नहीं होता है

विपक्ष:

  1. अनुक्रमिक और deserializing के साथ समस्याएँ नहीं हो सकती हैं निरंतर इकाइयां
  2. डीबी से पुनः लोड होने के बाद सहेजी गई इकाई का हैशकोड बदल सकता है
  3. नहीं बनी वस्तुओं को हमेशा अलग माना जाता है (शायद यह सही है?)
  4. और क्या?

हमारा कोड देखें:

@MappedSuperclass
abstract public class Entity implements Serializable {

    @Id
    @GeneratedValue
    @Column(nullable = false, updatable = false)
    protected Long id;

    @Transient
    private Long tempId;

    public void setId(Long id) {
        this.id = id;
    }

    public Long getId() {
        return id;
    }

    private void setTempId(Long tempId) {
        this.tempId = tempId;
    }

    // Fix Id on first call from equal() or hashCode()
    private Long getTempId() {
        if (tempId == null)
            // if we have id already, use it, else use 0
            setTempId(getId() == null ? 0 : getId());
        return tempId;
    }

    @Override
    public boolean equals(Object obj) {
        if (super.equals(obj))
            return true;
        // take proxied object into account
        if (obj == null || !Hibernate.getClass(obj).equals(this.getClass()))
            return false;
        Entity o = (Entity) obj;
        return getTempId() != 0 && o.getTempId() != 0 && getTempId().equals(o.getTempId());
    }

    // hash doesn't change in time
    @Override
    public int hashCode() {
        return getTempId() == 0 ? super.hashCode() : getTempId().hashCode();
    }
}

1

कृपया पूर्वनिर्धारित प्रकार के पहचानकर्ता और आईडी के आधार पर निम्नलिखित दृष्टिकोण पर विचार करें।

JPA के लिए विशिष्ट धारणाएँ:

  • समान "प्रकार" और समान गैर-शून्य आईडी की इकाइयाँ समान मानी जाती हैं
  • गैर-स्थायी इकाइयां (कोई आईडी नहीं मानती) अन्य संस्थाओं के बराबर नहीं हैं

अमूर्त इकाई:

@MappedSuperclass
public abstract class AbstractPersistable<K extends Serializable> {

  @Id @GeneratedValue
  private K id;

  @Transient
  private final String kind;

  public AbstractPersistable(final String kind) {
    this.kind = requireNonNull(kind, "Entity kind cannot be null");
  }

  @Override
  public final boolean equals(final Object obj) {
    if (this == obj) return true;
    if (!(obj instanceof AbstractPersistable)) return false;
    final AbstractPersistable<?> that = (AbstractPersistable<?>) obj;
    return null != this.id
        && Objects.equals(this.id, that.id)
        && Objects.equals(this.kind, that.kind);
  }

  @Override
  public final int hashCode() {
    return Objects.hash(kind, id);
  }

  public K getId() {
    return id;
  }

  protected void setId(final K id) {
    this.id = id;
  }
}

कंक्रीट इकाई उदाहरण:

static class Foo extends AbstractPersistable<Long> {
  public Foo() {
    super("Foo");
  }
}

परीक्षण उदाहरण:

@Test
public void test_EqualsAndHashcode_GivenSubclass() {
  // Check contract
  EqualsVerifier.forClass(Foo.class)
    .suppress(Warning.NONFINAL_FIELDS, Warning.TRANSIENT_FIELDS)
    .withOnlyTheseFields("id", "kind")
    .withNonnullFields("id", "kind")
    .verify();
  // Ensure new objects are not equal
  assertNotEquals(new Foo(), new Foo());
}

यहाँ मुख्य लाभ:

  • सादगी
  • यह सुनिश्चित करता है कि उपवर्ग प्रकार की पहचान प्रदान करें
  • अनुमानित कक्षाओं के साथ व्यवहार की भविष्यवाणी की

नुकसान:

  • कॉल करने के लिए प्रत्येक इकाई की आवश्यकता होती है super()

टिप्पणियाँ:

  • वंशानुक्रम का उपयोग करते समय ध्यान देने की आवश्यकता है। उदाहरण के लिए समानता की class Aऔर class B extends Aआवेदन के ठोस विवरण पर निर्भर हो सकता है।
  • आदर्श रूप में, आईडी के रूप में व्यवसाय कुंजी का उपयोग करें

आपकी टिप्पणी की अपेक्षा करता हूँ।


0

यह हर आईटी प्रणाली में एक आम समस्या है जो जावा और जेपीए का उपयोग करती है। दर्द बिंदु बराबर () और हैशकोड () को लागू करने से परे फैली हुई है, यह प्रभावित करता है कि एक संगठन एक इकाई को कैसे संदर्भित करता है और उसके ग्राहक एक ही इकाई को कैसे संदर्भित करते हैं। मैंने इस बात की पर्याप्त पीड़ा देखी है कि मैंने अपना विचार व्यक्त करने के लिए अपना ब्लॉग लिखा था ।

संक्षेप में: एक छोटी, मानव पठनीय, अनुक्रमिक आईडी का उपयोग सार्थक उपसर्गों के साथ किया जाता है, जो कि राम के अलावा किसी भी भंडारण पर निर्भरता के बिना उत्पन्न होने वाली व्यावसायिक कुंजी है। ट्विटर का स्नोफ्लेक एक बहुत अच्छा उदाहरण है।


0

IMO आपके पास समान / हैशकोड लागू करने के लिए 3 विकल्प हैं

  • एक एप्लिकेशन जेनरेट की गई पहचान यानी UUID का उपयोग करें
  • एक व्यावसायिक कुंजी के आधार पर इसे लागू करें
  • इसे प्राथमिक कुंजी के आधार पर लागू करें

एप्लिकेशन जेनरेट की गई पहचान का उपयोग करना सबसे आसान तरीका है, लेकिन कुछ डाउनसाइड के साथ आता है

  • पीके के रूप में उपयोग करने पर जोड़ धीमे होते हैं क्योंकि 128 बिट केवल 32 या 64 बिट से बड़ा होता है
  • "डीबगिंग कठिन है" क्योंकि आपकी खुद की आँखों से जाँच करना कुछ डेटा सही है बहुत कठिन है

यदि आप इन डाउनसाइड के साथ काम कर सकते हैं , तो बस इस दृष्टिकोण का उपयोग करें।

ज्वाइन इश्यू को दूर करने के लिए यूयूआईडी का उपयोग प्राकृतिक कुंजी के रूप में किया जा सकता है और प्राथमिक कुंजी के रूप में एक अनुक्रम मान, लेकिन फिर भी आप रचनात्मक बाल संस्थाओं में बराबर / हैशकोड कार्यान्वयन समस्याओं में भाग ले सकते हैं जो कि आईडी से जुड़े हैं क्योंकि आप इसमें शामिल होना चाहते हैं। प्राथमिक कुंजी पर। बाल संस्थाओं की आईडी में प्राकृतिक कुंजी का उपयोग करना और माता-पिता को संदर्भित करने के लिए प्राथमिक कुंजी एक अच्छा समझौता है।

@Entity class Parent {
  @Id @GeneratedValue Long id;
  @NaturalId UUID uuid;
  @OneToMany(mappedBy = "parent") Set<Child> children;
  // equals/hashCode based on uuid
}

@Entity class Child {
  @EmbeddedId ChildId id;
  @ManyToOne Parent parent;

  @Embeddable class ChildId {
    UUID parentUuid;
    UUID childUuid;
    // equals/hashCode based on parentUuid and childUuid
  }
  // equals/hashCode based on id
}

IMO यह सबसे साफ तरीका है क्योंकि यह सभी डाउनसाइड्स से बच जाएगा और साथ ही आपको एक मूल्य (यूयूआईडी) प्रदान करता है जिसे आप सिस्टम इंटर्ल्स को उजागर किए बिना बाहरी सिस्टम के साथ साझा कर सकते हैं।

इसे एक व्यवसाय कुंजी के आधार पर लागू करें यदि आप उम्मीद कर सकते हैं कि उपयोगकर्ता से एक अच्छा विचार है, लेकिन कुछ गिरावट के साथ भी आता है

अधिकांश समय यह व्यवसाय कुंजी किसी प्रकार का कोड होगा जो उपयोगकर्ता प्रदान करता है और कम अक्सर कई विशेषताओं का एक संयोजन होता है।

  • जॉइन्ट स्लो होते हैं क्योंकि वेरिएबल लेंथ टेक्स्ट के आधार पर जॉइनिंग धीमी होती है। यदि किसी निश्चित लंबाई से अधिक हो तो कुछ DBMS को एक इंडेक्स बनाने में भी समस्या हो सकती है।
  • मेरे अनुभव में, व्यवसाय कुंजी बदल जाती है जिसके लिए इसे संदर्भित करने वाली वस्तुओं को कैस्केडिंग अपडेट की आवश्यकता होगी। यह असंभव है अगर बाहरी सिस्टम इसे संदर्भित करते हैं

IMO आपको किसी व्यवसाय कुंजी के साथ विशेष रूप से कार्यान्वित या काम नहीं करना चाहिए। यह एक अच्छा ऐड-ऑन है यानी उपयोगकर्ता उस व्यावसायिक कुंजी को जल्दी से खोज सकते हैं, लेकिन सिस्टम को संचालन के लिए उस पर भरोसा नहीं करना चाहिए।

प्राथमिक कुंजी के आधार पर इसे लागू करने में समस्याएं हैं, लेकिन शायद यह इतनी बड़ी बात नहीं है

यदि आपको आईडी को बाहरी सिस्टम में उजागर करने की आवश्यकता है, तो मेरे द्वारा सुझाए गए UUID दृष्टिकोण का उपयोग करें। यदि आप नहीं करते हैं, तो आप अभी भी UUID दृष्टिकोण का उपयोग कर सकते हैं लेकिन आपके पास नहीं है। DBMS जेनरेट की गई आईडी को बराबर / हैशकोड में उपयोग करने की समस्या इस तथ्य से उपजी है कि आईडी को असाइन करने से पहले ऑब्जेक्ट को हैश आधारित संग्रह में जोड़ा जा सकता है।

इसके आस-पास जाने का स्पष्ट तरीका यह है कि आप आईडी को असाइन करने से पहले हैश आधारित संग्रह में ऑब्जेक्ट को न जोड़ें। मैं समझता हूं कि यह हमेशा संभव नहीं है क्योंकि आप पहले से ही आईडी आवंटित करने से पहले कटौती कर सकते हैं। अभी भी हैश आधारित संग्रह का उपयोग करने में सक्षम होने के लिए, आपको बस आईडी असाइन करने के बाद संग्रह को फिर से बनाना होगा।

आप ऐसा कुछ कर सकते हैं:

@Entity class Parent {
  @Id @GeneratedValue Long id;
  @OneToMany(mappedBy = "parent") Set<Child> children;
  // equals/hashCode based on id
}

@Entity class Child {
  @EmbeddedId ChildId id;
  @ManyToOne Parent parent;

  @PrePersist void postPersist() {
    parent.children.remove(this);
  }
  @PostPersist void postPersist() {
    parent.children.add(this);
  }

  @Embeddable class ChildId {
    Long parentId;
    @GeneratedValue Long childId;
    // equals/hashCode based on parentId and childId
  }
  // equals/hashCode based on id
}

मैंने स्वयं सटीक दृष्टिकोण का परीक्षण नहीं किया है, इसलिए मुझे यकीन नहीं है कि पूर्व और बाद की घटनाओं में संग्रह कैसे बदलते हैं, लेकिन विचार यह है:

  • अस्थायी रूप से ऑब्जेक्ट को हैश आधारित संग्रह से निकालें
  • इसे जारी रखें
  • ऑब्जेक्ट को हैश आधारित संग्रह में फिर से जोड़ें

इसे हल करने का एक अन्य तरीका यह है कि किसी अपडेट / दृढ़ता के बाद अपने सभी हैश आधारित मॉडलों का पुनर्निर्माण किया जाए।

अंत में, यह आपके ऊपर है। मैं व्यक्तिगत रूप से ज्यादातर समय अनुक्रम आधारित दृष्टिकोण का उपयोग करता हूं और केवल यूयूआईडी दृष्टिकोण का उपयोग करता हूं अगर मुझे बाहरी सिस्टम के लिए एक पहचानकर्ता को उजागर करने की आवश्यकता होती है।


0

instanceofजावा 14 से नई शैली के साथ , आप equalsएक पंक्ति में लागू कर सकते हैं ।

@Override
public boolean equals(Object obj) {
    return this == obj || id != null && obj instanceof User otherUser && id.equals(otherUser.id);
}

@Override
public int hashCode() {
    return 31;
}

-1

यदि यूयूआईडी कई लोगों के लिए जवाब है, तो हम केवल व्यवसायिक परत से फैक्ट्री के तरीकों का उपयोग नहीं करते हैं ताकि संस्थाएं बनाई जा सकें और निर्माण समय पर प्राथमिक कुंजी प्रदान की जा सके?

उदाहरण के लिए:

@ManagedBean
public class MyCarFacade {
  public Car createCar(){
    Car car = new Car();
    em.persist(car);
    return car;
  }
}

इस तरह हम दृढ़ता प्रदाता से इकाई के लिए एक डिफ़ॉल्ट प्राथमिक कुंजी प्राप्त करेंगे, और हमारे हैशकोड () और बराबर () फ़ंक्शन उस पर भरोसा कर सकते हैं।

हम कार के कंस्ट्रक्टर्स को संरक्षित घोषित कर सकते हैं और फिर उन्हें एक्सेस करने के लिए हमारे व्यापार के तरीके में प्रतिबिंब का उपयोग कर सकते हैं। इस तरह से डेवलपर्स इंस्टेंटिएट कार पर नए के साथ इरादे नहीं होंगे, लेकिन कारखाने के माध्यम से।

कि कैसे?


एक ऐसा तरीका जो शानदार काम करता है यदि आप डेटाबेस लुकअप करते समय प्रदर्शन उत्पन्न करने में दोनों को प्रभावित करते हैं।
माइकल विल्स

1
यूनिट टेस्टिंग कार के बारे में क्या? इस मामले में आपको परीक्षण के लिए डेटाबेस कनेक्शन की आवश्यकता है? साथ ही, आपकी डोमेन ऑब्जेक्ट्स दृढ़ता पर निर्भर नहीं होनी चाहिए।
jhegedus

-1

मैंने खुद इस सवाल का जवाब देने की कोशिश की और जब तक मैं इस पोस्ट और विशेष रूप से DREW एक को नहीं मिला पाया समाधानों से पूरी तरह खुश नहीं था। मुझे यह पसंद आया कि जिस तरह से उसने आलुइड बनाया और उसे जानबूझकर संग्रहीत किया।

लेकिन मैं और भी अधिक लचीलापन जोड़ना चाहता था, यानी आलसी यूयूआईडी केवल तभी बनाते हैं जब हैशकोड () / बराबर () को प्रत्येक समाधान के फायदे के साथ इकाई के पहले दृढ़ता से पहले एक्सेस किया जाता है:

  • बराबर () का अर्थ है "वस्तु एक ही तार्किक इकाई को संदर्भित करता है"
  • जितना संभव हो डेटाबेस आईडी का उपयोग करें क्योंकि मैं दो बार काम क्यों करूंगा (प्रदर्शन चिंता)
  • हैशकोड एक्सेस करने के दौरान समस्या को रोकने के लिए () / बराबर () पर अभी तक स्थायी इकाई नहीं है और इसे व्यवहार करने के बाद एक ही व्यवहार रखें।

मैं वास्तव में नीचे मेरे मिश्रित समाधान पर प्रतिक्रिया देना चाहता हूं

public class MyEntity { 

    @Id()
    @Column(name = "ID", length = 20, nullable = false, unique = true)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id = null;

    @Transient private UUID uuid = null;

    @Column(name = "UUID_MOST", nullable = true, unique = false, updatable = false)
    private Long uuidMostSignificantBits = null;
    @Column(name = "UUID_LEAST", nullable = true, unique = false, updatable = false)
    private Long uuidLeastSignificantBits = null;

    @Override
    public final int hashCode() {
        return this.getUuid().hashCode();
    }

    @Override
    public final boolean equals(Object toBeCompared) {
        if(this == toBeCompared) {
            return true;
        }
        if(toBeCompared == null) {
            return false;
        }
        if(!this.getClass().isInstance(toBeCompared)) {
            return false;
        }
        return this.getUuid().equals(((MyEntity)toBeCompared).getUuid());
    }

    public final UUID getUuid() {
        // UUID already accessed on this physical object
        if(this.uuid != null) {
            return this.uuid;
        }
        // UUID one day generated on this entity before it was persisted
        if(this.uuidMostSignificantBits != null) {
            this.uuid = new UUID(this.uuidMostSignificantBits, this.uuidLeastSignificantBits);
        // UUID never generated on this entity before it was persisted
        } else if(this.getId() != null) {
            this.uuid = new UUID(this.getId(), this.getId());
        // UUID never accessed on this not yet persisted entity
        } else {
            this.setUuid(UUID.randomUUID());
        }
        return this.uuid; 
    }

    private void setUuid(UUID uuid) {
        if(uuid == null) {
            return;
        }
        // For the one hypothetical case where generated UUID could colude with UUID build from IDs
        if(uuid.getMostSignificantBits() == uuid.getLeastSignificantBits()) {
            throw new Exception("UUID: " + this.getUuid() + " format is only for internal use");
        }
        this.uuidMostSignificantBits = uuid.getMostSignificantBits();
        this.uuidLeastSignificantBits = uuid.getLeastSignificantBits();
        this.uuid = uuid;
    }

"अनुनय से पहले इस इकाई पर उत्पन्न एक दिन" का क्या मतलब है? क्या आप इस मामले के लिए एक उदाहरण दे सकते हैं?
jhegedus

क्या आप नियत पीढ़ी का उपयोग कर सकते हैं? पहचान की आवश्यकता क्यों है? क्या इसका कुछ लाभ सौंपा गया है?
jhegedus

क्या होता है यदि आप 1) एक नया MyEntity बनाते हैं, 2) इसे एक सूची में डालें, 3) फिर इसे डेटाबेस में सहेजें फिर 4) आप उस इकाई को डीबी और 5 से वापस लोड करते हैं) यह देखने की कोशिश करें कि क्या लोड सूची में है । मेरा अनुमान है कि यह नहीं होना चाहिए, भले ही यह होना चाहिए।
jhegedus

आपकी पहली टिप्पणियों के लिए धन्यवाद जिसने मुझे दिखाया मैं उतना स्पष्ट नहीं था जितना मुझे होना चाहिए। सबसे पहले, "UUID एक दिन इस इकाई पर उत्पन्न होने से पहले मैं कायम था" एक टाइपो था ... "आईटी से पहले कायम था" इसके बजाय पढ़ा जाना चाहिए था। अन्य टिप्पणियों के लिए, मैं अपने पोस्ट को जल्द ही संपादित करने का प्रयास करूंगा ताकि मेरे समाधान को बेहतर ढंग से समझाया जा सके।
user2083808

-1

व्यवहार में ऐसा लगता है, कि विकल्प 2 (प्राथमिक कुंजी) सबसे अधिक बार उपयोग किया जाता है। प्राकृतिक और IMMUTABLE व्यावसायिक कुंजी शायद ही कभी होती है, सिंथेटिक कुंजी बनाने और समर्थन करने के लिए स्थितियों को हल करना बहुत भारी होता है, जो शायद कभी नहीं हुआ। स्प्रिंग-डेटा-जेपीए एब्सपर्सिस्टेबल कार्यान्वयन (केवल एक चीज: हाइबरनेट कार्यान्वयन उपयोग के लिएHibernate.getClass ) पर एक नज़र डालें ।

public boolean equals(Object obj) {
    if (null == obj) {
        return false;
    }
    if (this == obj) {
        return true;
    }
    if (!getClass().equals(ClassUtils.getUserClass(obj))) {
        return false;
    }
    AbstractPersistable<?> that = (AbstractPersistable<?>) obj;
    return null == this.getId() ? false : this.getId().equals(that.getId());
}

@Override
public int hashCode() {
    int hashCode = 17;
    hashCode += null == getId() ? 0 : getId().hashCode() * 31;
    return hashCode;
}

बस HashSet / HashMap में नई वस्तुओं में हेरफेर करने के बारे में पता है। इसके विपरीत, विकल्प 1 ( Objectलागू रहना ) बस के बाद टूट गया है merge, यह बहुत ही सामान्य स्थिति है।

यदि आपके पास कोई व्यवसाय कुंजी नहीं है और हैश संरचना में नई इकाई को हेरफेर करने के लिए एक वास्तविक आवश्यकता है, तो hashCodeव्लादिमीर मिहेल्शिया से नीचे सलाह दी गई थी।


-2

नीचे स्काला के लिए एक सरल (और परीक्षण किया गया) समाधान है।

  • ध्यान दें कि यह समाधान प्रश्न में दी गई 3 श्रेणियों में से किसी में भी फिट नहीं है।

  • मेरी सभी इकाइयां UUIDEntity के उपवर्ग हैं, इसलिए मैं न दोहराए जाने वाले अपने (DRY) सिद्धांत का पालन करता हूं।

  • यदि आवश्यक हो तो UUID पीढ़ी को और अधिक सटीक बनाया जा सकता है (अधिक छद्म यादृच्छिक संख्याओं का उपयोग करके)।

स्केल कोड:

import javax.persistence._
import scala.util.Random

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
abstract class UUIDEntity {
  @Id  @GeneratedValue(strategy = GenerationType.TABLE)
  var id:java.lang.Long=null
  var uuid:java.lang.Long=Random.nextLong()
  override def equals(o:Any):Boolean= 
    o match{
      case o : UUIDEntity => o.uuid==uuid
      case _ => false
    }
  override def hashCode() = uuid.hashCode()
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.