मैं JPA OneToOne के संबंध को कैसे आलसी बना सकता हूं


212

इस एप्लिकेशन में हम विकसित कर रहे हैं, हमने देखा कि एक दृश्य विशेष रूप से धीमा था। मैंने दृश्य को प्रोफाइल किया और देखा कि हाइबरनेट द्वारा निष्पादित एक क्वेरी थी जिसे डेटाबेस में लाने के लिए केवल दो ऑब्जेक्ट होने पर भी 10 सेकंड लगते थे। सभी OneToManyऔर ManyToManyसंबंध आलसी थे इसलिए यह समस्या नहीं थी। जब वास्तविक एसक्यूएल का निष्पादन किया जा रहा था, तो मैंने देखा कि क्वेरी में 80 से अधिक जोड़ थे।

आगे इस मुद्दे का निरीक्षण करते हुए, मैंने देखा कि समस्या इकाई वर्गों के बीच गहरी पदानुक्रम OneToOneऔर ManyToOneसंबंधों के कारण हुई थी । इसलिए, मैंने सोचा, मैं उन्हें आलसी बना दूंगा, जिससे समस्या का समाधान होना चाहिए। लेकिन व्याख्या या तो @OneToOne(fetch=FetchType.LAZY)या @ManyToOne(fetch=FetchType.LAZY)काम करने के लिए प्रतीत नहीं होता। या तो मुझे एक अपवाद मिलता है या फिर उन्हें वास्तव में एक छद्म वस्तु के साथ प्रतिस्थापित नहीं किया जाता है और इस तरह आलसी किया जाता है।

किसी भी विचार मैं यह कैसे काम करने के लिए मिल जाएगा? ध्यान दें कि मैं persistence.xmlसंबंधों या कॉन्फ़िगरेशन विवरण को परिभाषित करने के लिए उपयोग नहीं करता हूं , सब कुछ जावा कोड में किया जाता है।

जवाबों:


218

सबसे पहले, KLE के उत्तर के लिए कुछ स्पष्टीकरण :

  1. असंबंधित (अशक्त) वन-टू-वन एसोसिएशन एकमात्र ऐसा है जिसे बायटेक इंस्ट्रूमेंटेशन के बिना अनुमानित नहीं किया जा सकता है। इसका कारण यह है कि मालिक इकाई को यह पता होना चाहिए कि क्या एसोसिएशन की संपत्ति में एक प्रॉक्सी ऑब्जेक्ट या NULL होना चाहिए और यह निर्धारित नहीं कर सकता है कि आम तौर पर साझा किए गए PK के माध्यम से एक-से-एक मैप किए जाने के कारण इसकी बेस टेबल के कॉलम को देखकर, इसलिए यह वैसे भी बहुत उत्सुकता से छद्म व्यर्थ बना दिया है। यहाँ एक अधिक विस्तृत विवरण दिया गया है।

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

  3. एक-से-एक के साथ एक-से-कई को बदलना बहुत अच्छा विचार है। आप इसे अनूठे कई-से-एक के साथ बदल सकते हैं लेकिन अन्य (संभवतः बेहतर) विकल्प हैं।

रोब एच , एक वैध बिंदु है लेकिन आप अपने मॉडल के आधार पर यह लागू करने में सक्षम नहीं हो सकता है (उदाहरण के लिए अपने एक-से-एक संघ है, तो है नल)।

अब, जहां तक ​​मूल प्रश्न है:

A) @ManyToOne(fetch=FetchType.LAZY)ठीक काम करना चाहिए। क्या आप सुनिश्चित हैं कि यह क्वेरी में ही नहीं लिखा जा रहा है? join fetchएचक्यूएल और / या स्पष्ट रूप से निर्दिष्ट करना संभव है मानदंड मानदंड के माध्यम से लाने के लिए जो वर्ग एनोटेशन पर पूर्वता ले जाएगा। अगर ऐसा नहीं है और आपको अभी भी समस्या हो रही है, तो कृपया अपनी कक्षाओं, क्वेरी और परिणामस्वरूप SQL को अधिक-से-अधिक वार्तालाप के लिए पोस्ट करें।

बी) @OneToOneपेचीदा मामला है। यदि यह निश्चित रूप से अशक्त नहीं है, तो रोब एच। के सुझाव पर जाएं और इसे इस प्रकार निर्दिष्ट करें:

@OneToOne(optional = false, fetch = FetchType.LAZY)

अन्यथा, यदि आप अपना डेटाबेस बदल सकते हैं (स्वामी तालिका में एक विदेशी कुंजी कॉलम जोड़ें), ऐसा करें और इसे "शामिल" के रूप में मैप करें:

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="other_entity_fk")
public OtherEntity getOther()

और अन्य में:

@OneToOne(mappedBy = "other")
public OwnerEntity getOwner()

यदि आप ऐसा नहीं कर सकते (और उत्सुकता के साथ नहीं रह सकते हैं) तो बायटेकोड इंस्ट्रूमेंटेशन आपका एकमात्र विकल्प है। मुझे CPerkins से सहमत होना होगा , हालाँकि - अगर आपके पास 80 हैं !!! उत्सुक OneToOne संघों के कारण, आप बड़ी समस्याओं तो यह :-) है


शायद एक और विकल्प है, लेकिन मैंने व्यक्तिगत रूप से इसका परीक्षण नहीं किया है: गैर-विवश पक्ष पर, one-to-oneजैसे सूत्र के साथ उपयोग करें select other_entity.id from other_entity where id = other_entity.id। बेशक, यह क्वेरी प्रदर्शन के लिए आदर्श नहीं है।
Frédéric

1
वैकल्पिक = गलत, मेरे लिए काम नहीं करता है। @OneToOne (fetch = FetchType.LAZY, mappedBy = "fundSeries", वैकल्पिक = false) निजी FundSeriesDetailEntity fundSeriesDetail;
ओलेग कट्स

21

आलसी लोड करने के लिए अशक्त एक-से-एक मैपिंग पर काम करने के लिए आपको हाइबरनेट को समय इंस्ट्रूमेंटेशन@LazyToOne(value = LazyToOneOption.NO_PROXY) करने की आवश्यकता है और एक-से-एक संबंध जोड़ने के लिए।

उदाहरण मानचित्रण:

@OneToOne(fetch = FetchType.LAZY)  
@JoinColumn(name="other_entity_fk")
@LazyToOne(value = LazyToOneOption.NO_PROXY)
public OtherEntity getOther()

उदाहरण चींटी बिल्ड फ़ाइल एक्सटेंशन (हाइबरनेट संकलन समय इंस्ट्रूमेंटेशन करने के लिए):

<property name="src" value="/your/src/directory"/><!-- path of the source files --> 
<property name="libs" value="/your/libs/directory"/><!-- path of your libraries --> 
<property name="destination" value="/your/build/directory"/><!-- path of your build directory --> 

<fileset id="applibs" dir="${libs}"> 
  <include name="hibernate3.jar" /> 
  <!-- include any other libraries you'll need here --> 
</fileset> 

<target name="compile"> 
  <javac srcdir="${src}" destdir="${destination}" debug="yes"> 
    <classpath> 
      <fileset refid="applibs"/> 
    </classpath> 
  </javac> 
</target> 

<target name="instrument" depends="compile"> 
  <taskdef name="instrument" classname="org.hibernate.tool.instrument.javassist.InstrumentTask"> 
    <classpath> 
      <fileset refid="applibs"/> 
    </classpath> 
  </taskdef> 

  <instrument verbose="true"> 
    <fileset dir="${destination}"> 
      <!-- substitute the package where you keep your domain objs --> 
      <include name="/com/mycompany/domainobjects/*.class"/> 
    </fileset> 
  </instrument> 
</target>

3
क्यों LazyToOneOption.NO_PROXYऔर क्या नहीं LazyToOneOption.PROXY?
टेलमो Marques

यह "क्यों" का जवाब नहीं देता है, लेकिन इस तथ्य का यहां भी उल्लेख किया
डैनियलएम

12

हाइबरनेट में XToOnes के पीछे मूल विचार यह है कि वे ज्यादातर मामलों में आलसी नहीं हैं।

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

संपादित: विवरण के लिए, कृपया ChssPly76 के उत्तर को देखें । यह कम सटीक और विस्तृत है, इसमें कुछ भी नहीं है। धन्यवाद ChssPly76


यहां कई चीजें गलत हैं - मैंने एक स्पष्टीकरण के साथ नीचे एक और जवाब दिया है (बहुत अधिक सामान, एक टिप्पणी में फिट नहीं होगा)
ChssPly76

8

यहाँ कुछ है जो मेरे लिए काम कर रहा है (इंस्ट्रूमेंटेशन के बिना):

@OneToOneदोनों पक्षों पर उपयोग करने के बजाय , मैं @OneToManyरिश्ते के व्युत्क्रम भाग (एक के साथ mappedBy) में उपयोग करता हूं । यह संपत्ति को एक संग्रह बनाता है ( Listनीचे दिए गए उदाहरण में), लेकिन मैं इसे गटर में एक आइटम में अनुवाद करता हूं, जिससे यह ग्राहकों के लिए पारदर्शी हो जाता है।

इस सेटअप lazily काम करता है, यह है कि, चयन केवल बना रहे हैं जब getPrevious()या getNext()कहा जाता है - और केवल एक प्रत्येक कॉल के लिए चयन करें।

तालिका संरचना:

CREATE TABLE `TB_ISSUE` (
    `ID`            INT(9) NOT NULL AUTO_INCREMENT,
    `NAME`          VARCHAR(255) NULL,
    `PREVIOUS`      DECIMAL(9,2) NULL
    CONSTRAINT `PK_ISSUE` PRIMARY KEY (`ID`)
);
ALTER TABLE `TB_ISSUE` ADD CONSTRAINT `FK_ISSUE_ISSUE_PREVIOUS`
                 FOREIGN KEY (`PREVIOUS`) REFERENCES `TB_ISSUE` (`ID`);

कक्षा:

@Entity
@Table(name = "TB_ISSUE") 
public class Issue {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Integer id;

    @Column
    private String name;

    @OneToOne(fetch=FetchType.LAZY)  // one to one, as expected
    @JoinColumn(name="previous")
    private Issue previous;

    // use @OneToMany instead of @OneToOne to "fake" the lazy loading
    @OneToMany(mappedBy="previous", fetch=FetchType.LAZY)
    // notice the type isnt Issue, but a collection (that will have 0 or 1 items)
    private List<Issue> next;

    public Integer getId() { return id; }
    public String getName() { return name; }

    public Issue getPrevious() { return previous; }
    // in the getter, transform the collection into an Issue for the clients
    public Issue getNext() { return next.isEmpty() ? null : next.get(0); }

}

7

जैसा कि मैंने इस लेख में बताया है , जब तक कि आप बायटेकोड एनहांसमेंट का उपयोग नहीं कर रहे हैं , आप माता-पिता के @OneToOneसहयोग से आलस्य नहीं पाल सकते ।

हालाँकि, सबसे अधिक बार, यदि आप @MapsIdक्लाइंट साइड पर उपयोग करते हैं, तो आपको पैरेंट-साइड एसोसिएशन की भी आवश्यकता नहीं है :

@Entity(name = "PostDetails")
@Table(name = "post_details")
public class PostDetails {

    @Id
    private Long id;

    @Column(name = "created_on")
    private Date createdOn;

    @Column(name = "created_by")
    private String createdBy;

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    private Post post;

    public PostDetails() {}

    public PostDetails(String createdBy) {
        createdOn = new Date();
        this.createdBy = createdBy;
    }

    //Getters and setters omitted for brevity
}

साथ @MapsId, idबच्चे को तालिका में संपत्ति प्राथमिक कुंजी और विदेशी कुंजी माता पिता तालिका प्राथमिक कुंजी के लिए दोनों के रूप में कार्य करता है।

इसलिए, यदि आपके पास मूल Postइकाई का संदर्भ है, तो आप मूल इकाई पहचानकर्ता का उपयोग करके आसानी से बाल इकाई ला सकते हैं:

PostDetails details = entityManager.find(
    PostDetails.class,
    post.getId()
);

इस तरह, आपके पास N + 1 क्वेरी समस्याएँ नहीं होंगी, जो कि पैरेंटस mappedBy @OneToOneएसोसिएशन के कारण हो सकती हैं ।


इस तरह अब हम माता-पिता से बच्चे तक के ऑपरेशन को कैसकेड नहीं कर सकते: /
हम्दी

दृढ़ता के लिए, यह सिर्फ एक अतिरिक्त कॉल है, हटाए जाने के लिए, आप DDL कैस्केड का उपयोग कर सकते हैं।
व्लाद मिहालसीया

6

देशी हाइबरनेट XML मैपिंग में, आप इसे एक-टू-वन मैपिंग को विवश विशेषता के साथ सही करने के लिए घोषित करके पूरा कर सकते हैं । मुझे यकीन नहीं है कि हाइबरनेट / जेपीए एनोटेशन उसके बराबर है, और डॉक्टर की त्वरित खोज ने कोई जवाब नहीं दिया, लेकिन उम्मीद है कि इससे आपको आगे बढ़ने का मौका मिलेगा।


5
एक अच्छे सुझाव के लिए +1; unfortunatelly यह हमेशा लागू नहीं होता है क्योंकि डोमेन मॉडल वास्तव में अशक्तता की आवश्यकता हो सकती है। एनोटेशन के माध्यम से इसे मैप करने का उचित तरीका है@OneToOne(optional=false,fetch=FetchMode.LAZY)
ChssPly76

मैंने यह कोशिश की और प्रदर्शन में कोई सुधार नहीं हुआ। मैंने अभी भी डिबगर के माध्यम से हाइबरनेट आउटपुट में कई प्रश्न देखे।
पी। ब्रायन। मैके

3

जैसा कि पहले से ही ChssPly76 द्वारा पूरी तरह से समझाया गया है, हाइबरनेट की प्रॉक्सी एक-से-एक संघों के साथ अप्रतिबंधित (अशक्त) में मदद नहीं करती है, लेकिन वहाँ एक चाल है जिसे इंस्ट्रूमेंटेशन स्थापित करने से बचने के लिए यहाँ समझाया गया है। हाइबरनेट को मूर्ख बनाने के लिए विचार यह है कि जिस इकाई वर्ग का हम उपयोग करना चाहते हैं, वह पहले से ही साधनित है: आप इसे स्रोत कोड में मैन्युअल रूप से लिखतें हैं। यह आसान है! मैंने इसे सीजीएलआईबी के साथ बाईटेकोड प्रदाता के रूप में लागू किया है और यह काम करता है (सुनिश्चित करें कि आप आलसी = "नो-प्रॉक्सी" और "एचओएल" में "सिलेक्ट", "ज्वाइन" न करें) का चयन करें।

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


1

यह सवाल काफी पुराना है, लेकिन हाइबरनेट 5.1.10 के साथ, कुछ नए बेहतर आरामदायक समाधान हैं।

@OneToOne एसोसिएशन के मूल पक्ष को छोड़कर आलसी लोडिंग कार्य करता है। ऐसा इसलिए है क्योंकि हाइबरनेट के पास यह जानने का कोई अन्य तरीका नहीं है कि इस चर को एक अशक्त या एक प्रॉक्सी कैसे निर्दिष्ट किया जाए। अधिक विवरण आप इस लेख में पा सकते हैं

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

0

यदि संबंध द्विदिश नहीं होना चाहिए, तो @ElementCollection एक आलसी One2Many संग्रह का उपयोग करने की तुलना में आसान हो सकता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.