जेपीए इनहेरिटेंस @EntityGraph में उपवर्गों के वैकल्पिक संघ शामिल हैं


12

निम्नलिखित डोमेन मॉडल को देखते हुए, मैं Answerअपने Valueएस और उनके संबंधित उप-बच्चों सहित सभी को लोड करना चाहता हूं और इसे AnswerDTOतब JSON में परिवर्तित कर सकता हूं। मेरे पास एक कार्यशील समाधान है लेकिन यह एन + 1 समस्या से ग्रस्त है जिसे मैं एक तदर्थ का उपयोग करके छुटकारा चाहता हूं @EntityGraph। सभी एसोसिएशन कॉन्फ़िगर किए गए हैं LAZY

यहाँ छवि विवरण दर्ज करें

@Query("SELECT a FROM Answer a")
@EntityGraph(attributePaths = {"value"})
public List<Answer> findAll();

विधि @EntityGraphपर एक ऐड-हॉक का उपयोग करके Repositoryमैं यह सुनिश्चित कर सकता हूं कि Answer->Valueएसोसिएशन पर एन + 1 को रोकने के लिए मूल्यों को पूर्व-निर्धारित किया गया है । जबकि मेरा परिणाम ठीक है, आलसी एस के selectedसहयोग को लोड करने के कारण एक और एन + 1 समस्या है MCValue

इसका उपयोग करना

@EntityGraph(attributePaths = {"value.selected"})

विफल रहता है, क्योंकि selectedक्षेत्र निश्चित रूप से कुछ Valueसंस्थाओं का हिस्सा है :

Unable to locate Attribute  with the the given name [selected] on this ManagedType [x.model.Value];

मैं कैसे कह सकता हूं कि selectedमूल्य के मामले में जेपीए केवल एसोसिएशन लाने की कोशिश करता है MCValue? मुझे कुछ चाहिए optionalAttributePaths

जवाबों:


8

आप केवल तभी उपयोग कर सकते हैं EntityGraphयदि एसोसिएशन विशेषता सुपरक्लास का हिस्सा है और उसके द्वारा भी सभी उपवर्गों का हिस्सा है। अन्यथा, EntityGraphवसीयत Exceptionउस के साथ हमेशा विफल रहेगी जो आप वर्तमान में प्राप्त करते हैं।

अपने N + 1 चयन समस्या से बचने का सबसे अच्छा तरीका है कि आप अपनी क्वेरी को 2 प्रश्नों में विभाजित करें:

पहली क्वेरी विशेषता द्वारा मैप किए गए एसोसिएशन को लाने के लिए MCValueसंस्थाओं का उपयोग EntityGraphकरती selectedहै। उस क्वेरी के बाद, इन संस्थाओं को फिर से हाइबरनेट के प्रथम स्तर कैश / दृढ़ता संदर्भ में संग्रहीत किया जाता है। जब वह दूसरी क्वेरी के परिणाम को संसाधित करता है, तो हाइबरनेट उनका उपयोग करेगा।

@Query("SELECT m FROM MCValue m") // add WHERE clause as needed ...
@EntityGraph(attributePaths = {"selected"})
public List<MCValue> findAll();

दूसरी क्वेरी फिर Answerइकाई लाती है और EntityGraphसंबद्ध Valueसंस्थाओं को लाने के लिए भी उपयोग करती है । प्रत्येक Valueइकाई के लिए, हाइबरनेट विशिष्ट उपवर्ग को तुरंत रोक देगा और जांच करेगा कि क्या प्रथम स्तर के कैश में पहले से ही उस वर्ग और प्राथमिक कुंजी संयोजन के लिए कोई ऑब्जेक्ट है। यदि ऐसा है, तो हाइबरनेट क्वेरी द्वारा दिए गए डेटा के बजाय 1 स्तर कैश से ऑब्जेक्ट का उपयोग करता है।

@Query("SELECT a FROM Answer a")
@EntityGraph(attributePaths = {"value"})
public List<Answer> findAll();

क्योंकि हम पहले से ही MCValueसंबंधित संस्थाओं के साथ सभी संस्थाओं को selectedप्राप्त कर चुके हैं, अब हम Answerएक आरंभिक valueसंघ के साथ संस्थाओं को प्राप्त करते हैं । और अगर एसोसिएशन में एक MCValueइकाई शामिल है , तो इसके selectedएसोसिएशन को भी आरंभीकृत किया जाएगा।


मैंने दो प्रश्नों के बारे में सोचा, जवाब पाने के लिए 1 + मूल्य और selectedउन जवाबों के लिए एक दूसरा लाने के लिए जिनके पास ए है MCValue। मुझे यह नापसंद था कि इसके लिए एक अतिरिक्त लूप की आवश्यकता होगी और मुझे डेटा सेट के बीच मैपिंग को प्रबंधित करने की आवश्यकता होगी। मुझे इसके लिए हाइबरनेट कैश का फायदा उठाने का आपका विचार पसंद है। क्या आप इस बारे में विस्तार से बता सकते हैं कि परिणामों को शामिल करने के लिए कैश पर निर्भर रहना कितना सुरक्षित (स्थिरता के संदर्भ में) है? क्या यह काम तब होता है जब लेनदेन में प्रश्न किए जाते हैं? मैं मुश्किल से हाजिर और छिटपुट आलसी आरंभीकरण त्रुटियों से डरता हूं।
अटक गया

1
आपको एक ही लेन-देन के भीतर दोनों प्रश्नों को करने की आवश्यकता है। जब तक आप ऐसा करते हैं, और अपनी दृढ़ता के संदर्भ को स्पष्ट नहीं करते, यह बिल्कुल सुरक्षित है। आपके प्रथम स्तर के कैश में हमेशा MCValueइकाइयां होंगी । और आपको एक अतिरिक्त लूप की आवश्यकता नहीं है। आपको MCValue1 क्वेरी के साथ सभी इकाइयाँ प्राप्त करनी चाहिए जो Answerआपके वर्तमान क्वेरी के समान WHERE क्लॉज़ में मिलती है और उसी का उपयोग करती है। मैंने इस बारे में आज की लाइव स्ट्रीम में भी बात की: youtu.be/70B9znTmi00?t=238 यह 3:58 पर शुरू हुआ, लेकिन मैंने बीच में कुछ और सवाल किए ...
थोरबेन जैन्सेन

महान, फॉलोअप के लिए धन्यवाद! इसके अलावा, मैं यह जोड़ना चाहता हूं कि इस समाधान के लिए प्रति उपवर्ग की 1 क्वेरी आवश्यक है। इसलिए स्थिरता हमारे लिए ठीक है, लेकिन यह समाधान सभी मामलों के लिए उपयुक्त नहीं हो सकता है।
फंसे

मुझे अपनी पिछली टिप्पणी को थोड़ा सुधारने की आवश्यकता है: बेशक आपको केवल प्रति उपवर्ग की एक क्वेरी की आवश्यकता है जो समस्या से ग्रस्त है। इसके अलावा, यह ध्यान देने योग्य है, कि उपवर्गों की विशेषताओं के लिए ऐसा लगता है कि वे किसी मुद्दे का उपयोग नहीं कर रहे हैं, क्योंकि इसका उपयोग किया जा रहा है SINGLE_TABLE_INHERITANCE
अटक गया

7

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

@EntityView(Answer.class)
interface AnswerDTO {
  @IdMapping
  Long getId();
  ValueDTO getValue();
}
@EntityView(Value.class)
@EntityViewInheritance
interface ValueDTO {
  @IdMapping
  Long getId();
}
@EntityView(TextValue.class)
interface TextValueDTO extends ValueDTO {
  String getText();
}
@EntityView(RatingValue.class)
interface RatingValueDTO extends ValueDTO {
  int getRating();
}
@EntityView(MCValue.class)
interface TextValueDTO extends ValueDTO {
  @Mapping("selected.id")
  Set<Long> getOption();
}

ब्लेज़-पर्सिस्टेंस द्वारा प्रदान किए गए वसंत डेटा एकीकरण के साथ आप इस तरह के एक भंडार को परिभाषित कर सकते हैं और सीधे परिणाम का उपयोग कर सकते हैं

@Transactional(readOnly = true)
interface AnswerRepository extends Repository<Answer, Long> {
  List<AnswerDTO> findAll();
}

यह एक एचक्यूएल क्वेरी उत्पन्न करेगा जो आपके द्वारा मैप की गई सामग्री का चयन करता है AnswerDTOजिसमें निम्नलिखित कुछ है।

SELECT
  a.id, 
  v.id,
  TYPE(v), 
  CASE WHEN TYPE(v) = TextValue THEN v.text END,
  CASE WHEN TYPE(v) = RatingValue THEN v.rating END,
  CASE WHEN TYPE(v) = MCValue THEN s.id END
FROM Answer a
LEFT JOIN a.value v
LEFT JOIN v.selected s

हम्म को आपकी लाइब्रेरी के संकेत के लिए धन्यवाद जो मैंने पहले ही पाया था, लेकिन हम इसे 2 मुख्य कारणों के लिए उपयोग नहीं करेंगे: 1) हम अपनी परियोजना के जीवनकाल में समर्थित होने के लिए आपकी कंपनी (आपकी कंपनी ब्लेज़बिट नहीं बल्कि छोटी है) पर भरोसा कर सकते हैं इसकी शुरुआत में)। 2) हम एक क्वेरी के अनुकूलन के लिए अधिक जटिल तकनीक-स्टैक के लिए प्रतिबद्ध नहीं होंगे। (मुझे पता है कि आपका परिवाद अधिक कर सकता है, लेकिन हम एक सामान्य तकनीकी स्टैक पसंद करते हैं और यदि कोई जेपीए समाधान नहीं है तो केवल एक कस्टम क्वेरी / परिवर्तन को लागू करेगा)।
Stuck

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

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

यह केवल जेपीए के साथ संभव नहीं है। आपको TREAT ऑपरेटर की आवश्यकता है जो न तो किसी JPA प्रदाता से पूरी तरह से समर्थित है, और न ही यह EntityGraph एनोटेशन में समर्थित है। तो जिस तरह से आप इसे मॉडल कर सकते हैं वह हाइबरनेट निहित सबटाइप प्रॉपर्टी रिज़ॉल्यूशन सुविधा के माध्यम से है, जिससे आपको स्पष्ट रूप से जुड़ने की आवश्यकता होती है।
क्रिश्चियन बेकोव

1
आपके उत्तर में दृश्य परिभाषा होनी चाहिएinterface MCValueDTO extends ValueDTO { @Mapping("selected.id") Set<Long> getOption(); }
Stuck

0

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

हमारा डेटा प्रवाह कुछ इस तरह दिखता है:

  1. ग्राफ़कॉल अनुरोध प्राप्त करें
  2. Parse GraphQL अनुरोध और क्वेरी में इकाई ग्राफ नोड्स की सूची में परिवर्तित
  3. खोजे गए नोड्स से इकाई ग्राफ बनाएं और निष्पादन के लिए भंडार में पास करें

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

यह खोजने से पहले मैंने स्प्रिंग जेपीए / हाइबरनेट डॉक्स में सुझाए गए और हर दूसरे विकल्प की कोशिश की थी, लेकिन समस्या को हल करने के लिए या कम से कम अतिरिक्त कोड के साथ कम से कम कुछ भी नहीं लगा।


सुपर प्रकार से ज्ञात लोडिंग संघों की समस्या को कैसे हल करता है? इसके अलावा, जैसा कि अन्य उत्तर में कहा गया है, हम जानना चाहते हैं कि क्या शुद्ध जेपीए समाधान है, लेकिन मुझे यह भी लगता है कि यह समस्या उसी समस्या से ग्रस्त है, जो selectedएसोसिएशन सभी उप प्रकारों के लिए उपलब्ध नहीं है value
अटक गया

यदि आप ग्राफ़कॉल में रुचि रखते हैं, तो हमारे पास ग्रेफ़ल-जावा के साथ ब्लेज़-पर्सिस्टेंस एंटिटी व्यूज़ का एकीकरण है: दृढ़ता
ईसाई बेइकोव

@ChristianBeikov धन्यवाद, लेकिन हम SQPR का उपयोग हमारे मॉडल / विधियों से हमारे स्कीमा प्रोग्रामेटिक रूप से उत्पन्न करने के लिए कर रहे हैं
aarbor

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

0

आपकी टिप्पणी के बाद संपादित:

माफी माँगता हूँ, मैंने आपको पहले दौर में जारी नहीं किया है, आपका मुद्दा वसंत-डेटा के स्टार्टअप पर होता है, न केवल तब जब आप findAll () को कॉल करने का प्रयास करते हैं।

तो, अब आप नेविगेट कर सकते हैं पूरा उदाहरण मेरे github से खींचा जा सकता है: https://github.com/bdzzaid/stackoverflow-java/blob/master/jpa-hibernate/

आप आसानी से इस परियोजना के अंदर अपने मुद्दे को पुन: पेश कर सकते हैं और ठीक कर सकते हैं।

प्रभावी रूप से, स्प्रिंग डेटा और हाइबरनेट डिफ़ॉल्ट रूप से "चयनित" ग्राफ को निर्धारित करने में सक्षम नहीं हैं और आपको चयनित विकल्प को इकट्ठा करने का तरीका निर्दिष्ट करने की आवश्यकता है।

तो सबसे पहले, आपको कक्षा के उत्तर के नामांकितताग्रहों को घोषित करना होगा

जैसा कि आप देख सकते हैं, वर्ग उत्तर की विशेषता मान के लिए दो NamedEntityGraph है

  • लोड करने के लिए विशिष्ट संबंध के बिना सभी मान के लिए पहला

  • विशिष्ट मल्टीचॉइस मूल्य के लिए दूसरा । यदि आप इसे हटाते हैं, तो आप अपवाद को पुन: उत्पन्न करते हैं।

दूसरा, यदि आपको डेटा का प्रकार LAZY में लाना है तो आपको एक ट्रांसेक्शनल संदर्भ answerRepository.findAll () में होना चाहिए।

@Entity
@Table(name = "answer")
@NamedEntityGraphs({
    @NamedEntityGraph(
            name = "graph.Answer", 
            attributeNodes = @NamedAttributeNode(value = "value")
    ),
    @NamedEntityGraph(
            name = "graph.AnswerMultichoice",
            attributeNodes = @NamedAttributeNode(value = "value"),
            subgraphs = {
                    @NamedSubgraph(
                            name = "graph.AnswerMultichoice.selected",
                            attributeNodes = {
                                    @NamedAttributeNode("selected")
                            }
                    )
            }
    )
}
)
public class Answer
{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(updatable = false, nullable = false)
    private int id;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "value_id", referencedColumnName = "id")
    private Value value;
// ..
}

समस्या value-association प्राप्त नहीं कर Answerरही है लेकिन selectedमामले में एसोसिएशन प्राप्त करना valueएक है MCValue। आपके उत्तर में उस संबंध में कोई जानकारी शामिल नहीं है।
Stuck

@Stuck आपके उत्तर के लिए धन्यवाद, क्या आप कृपया मेरे साथ वर्ग MCValue साझा कर सकते हैं, मैं आपको स्थानीय स्तर पर जारी करने की कोशिश करूँगा।
bdzzaid

आपका उदाहरण केवल इसलिए काम करता है क्योंकि आपने एसोसिएशन OneToManyको परिभाषित किया है FetchType.EAGERलेकिन जैसा कि प्रश्न में कहा गया है: सभी संघ हैं LAZY
Stuck

@Stuck मैंने आपके अंतिम अपडेट के बाद से मेरा जवाब अपडेट किया, आशा है कि मेरा जवाब पता है कि आप अपने मुद्दे को हल करने में मदद करेंगे, और वैकल्पिक संबंधों सहित एन यूनिट ग्राफ को लोड करने के तरीके को समझने में आपकी मदद करेंगे।
bdzzaid

आपका "समाधान" अभी भी मूल N + 1 समस्या से ग्रस्त है जो इस प्रश्न के बारे में है: सम्मिलित करें और अपने परीक्षण के विभिन्न लेन-देन में विधियां खोजें और आप देखें कि jpa selectedउन्हें उल्टा लोड करने के बजाय हर उत्तर के लिए DB क्वेरी जारी करेगा ।
Stuck
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.