एक स्प्रिंग नियंत्रक में JPA और हाइबरनेट के साथ FetchType.LAZY संघों को लाने के लिए कैसे


146

मेरा एक व्यक्ति वर्ग है:

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToMany(fetch = FetchType.LAZY)
    private List<Role> roles;
    // etc
}

कई-कई संबंधों के साथ जो आलसी है।

मेरे नियंत्रक में मेरे पास है

@Controller
@RequestMapping("/person")
public class PersonController {
    @Autowired
    PersonRepository personRepository;

    @RequestMapping("/get")
    public @ResponseBody Person getPerson() {
        Person person = personRepository.findOne(1L);
        return person;
    }
}

और PersonalRepository सिर्फ यह कोड है, जो इस गाइड के अनुसार लिखा गया है

public interface PersonRepository extends JpaRepository<Person, Long> {
}

हालांकि, इस नियंत्रक में मुझे वास्तव में आलसी-डेटा की आवश्यकता है। मैं इसकी लोडिंग को कैसे ट्रिगर कर सकता हूं?

इसे एक्सेस करने की कोशिश विफल रहेगी

भूमिका के एक संग्रह को शुरू करने में विफल रहा: no.dusken.momus.model.Person.roles, प्रॉक्सी को प्रारंभ नहीं कर सका - कोई सत्र नहीं

या अन्य अपवाद जो मैं कोशिश करता हूं, उसके आधार पर।

जरूरत के मामले में मेरा एक्सएमएल-विवरण

धन्यवाद।


क्या आप एक विधि लिख सकते हैं, जो Personकुछ पैरामीटर दिए गए ऑब्जेक्ट को लाने के लिए एक क्वेरी बनाएगा ? उस में Query, fetchखंड को शामिल करें और Rolesव्यक्ति के लिए भी लोड करें ।
सुदोहरूल

जवाबों:


206

इसे आरंभ करने के लिए आपको आलसी संग्रह पर एक स्पष्ट कॉल करना होगा (सामान्य अभ्यास .size()इस उद्देश्य के लिए कॉल करना है)। हाइबरनेट में इसके लिए एक समर्पित विधि है ( Hibernate.initialize()), लेकिन जेपीए के पास इसके बराबर नहीं है। बेशक, आपको यह सुनिश्चित करना होगा कि जब सत्र अभी भी उपलब्ध है, तो आह्वान किया जाता है, इसलिए अपने नियंत्रक विधि को एनोटेट करें @Transactional। एक विकल्प यह है कि नियंत्रक और रिपॉजिटरी के बीच एक इंटरमीडिएट सर्विस लेयर बनाया जाए जो आलसी संग्रह को शुरू करने वाले तरीकों को उजागर कर सके।

अपडेट करें:

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

public interface PersonRepository extends JpaRepository<Person, Long> {

    @Query("SELECT p FROM Person p JOIN FETCH p.roles WHERE p.id = (:id)")
    public Person findByIdAndFetchRolesEagerly(@Param("id") Long id);

}

यह विधि डेटाबेस में एकल राउंड-ट्रिप में रोल्स एसोसिएशन को उत्सुकता से लोड करने के लिए जेपीएनएल के भ्रूण जॉइन क्लॉज का उपयोग करेगी , और इसलिए उपरोक्त समाधान में दो अलग-अलग प्रश्नों द्वारा किए गए प्रदर्शन दंड को कम कर देगी।


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

मैंने अब जोस के जवाब के लिए एक उदाहरण के लिए कहा, मुझे स्वीकार करना होगा कि मैं पूरी तरह से नहीं समझता हूं।
मात्सेमन्न

कृपया मेरे अद्यतन उत्तर में वांछित क्वेरी विधि के लिए एक संभावित समाधान की जाँच करें।
ज़गयी

7
ध्यान देने वाली बात, अगर आप बस इसके joinबिना fetch, सेट के साथ वापस आ जाएंगे initialized = false; इसलिए सेट के एक्सेस होने के बाद भी दूसरी क्वेरी जारी करना। fetchयह सुनिश्चित करने के लिए महत्वपूर्ण है कि संबंध पूरी तरह से भरा हुआ है और दूसरी क्वेरी से बच रहा है।
FGreg

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

37

हालांकि यह एक पुरानी पोस्ट है, कृपया @NamedEntityGraph (Javax Persistence) और @EntityGraph (स्प्रिंग डेटा JPA) का उपयोग करने पर विचार करें। संयोजन काम करता है।

उदाहरण

@Entity
@Table(name = "Employee", schema = "dbo", catalog = "ARCHO")
@NamedEntityGraph(name = "employeeAuthorities",
            attributeNodes = @NamedAttributeNode("employeeGroups"))
public class EmployeeEntity implements Serializable, UserDetails {
// your props
}

और फिर नीचे के रूप में वसंत रेपो

@RepositoryRestResource(collectionResourceRel = "Employee", path = "Employee")
public interface IEmployeeRepository extends PagingAndSortingRepository<EmployeeEntity, String>           {

    @EntityGraph(value = "employeeAuthorities", type = EntityGraphType.LOAD)
    EmployeeEntity getByUsername(String userName);

}

1
ध्यान दें कि @NamedEntityGraphजेपीए 2.1 एपीआई का एक हिस्सा है, जो 4.3.0 संस्करण से पहले हाइबरनेट में लागू नहीं किया गया है।
naXa

2
@EntityGraph(attributePaths = "employeeGroups")एक स्प्रिंग डेटा रिपॉजिटरी में सीधे @NamedEntityGraphअपने @Entity - कम कोड की आवश्यकता के बिना एक विधि को एनोटेट करने के लिए इस्तेमाल किया जा सकता है , जब आप रेपो खोलते हैं, तो समझना आसान है।
देसीस्लाव कामेनोव

13

आपके पास कुछ विकल्प हैं

  • रिपॉजिटरी पर एक विधि लिखें जो आरजे द्वारा सुझाए गए अनुसार एक प्रारंभिक इकाई लौटाता है।

अधिक काम, सबसे अच्छा प्रदर्शन।

  • संपूर्ण अनुरोध के लिए सत्र खुला रखने के लिए OpenEntityManagerInViewFilter का उपयोग करें।

कम काम, आमतौर पर वेब वातावरण में स्वीकार्य है।

  • आवश्यकता पड़ने पर संस्थाओं को आरंभ करने के लिए एक सहायक वर्ग का उपयोग करें।

कम काम, उपयोगी जब OEMIV विकल्प में नहीं है, उदाहरण के लिए एक स्विंग एप्लिकेशन में, लेकिन किसी भी इकाई को एक शॉट में आरंभीकृत करने के लिए रिपॉजिटरी कार्यान्वयन पर भी उपयोगी हो सकता है।

अंतिम विकल्प के लिए, मैंने एक उपयोगिता वर्ग, JpaUtils को लिखा है कि कुछ डेफ़ पर संस्थाओं को निष्क्रिय करना।

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

@Transactional
public class RepositoryHelper {

    @PersistenceContext
    private EntityManager em;

    public void intialize(Object entity, int depth) {
        JpaUtils.initialize(em, entity, depth);
    }
}

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

मैं पहला काम कैसे करूँ? मुझे पता है कि क्वेरी कैसे लिखना है, लेकिन यह नहीं कि आप क्या कहते हैं। क्या आप कृपया एक उदाहरण दिखा सकते हैं? बहुत मददगार होगा।
मत्स्येमन

ज़ागी ने अपने जवाब में एक उदाहरण दिया, मुझे सही दिशा में इशारा करने के लिए धन्यवाद, वैसे भी।
मत्स्येमन

मुझे नहीं पता कि आपकी कक्षा को कैसे बुलाया जाएगा! पूरा नहीं होने से दूसरों का समय बर्बाद नहीं होता
Shady Sherif

संपूर्ण अनुरोध के लिए सत्र खुला रखने के लिए OpenEntityManagerInViewFilter का उपयोग करें। - बुरा विचार। मैं अपनी संस्थाओं के लिए सभी संग्रह लाने का अतिरिक्त अनुरोध करूंगा।
यान खोंस्की

8

यह केवल एक लेन-देन के भीतर आलसी लोड हो सकता है। तो आप अपनी रिपॉजिटरी में संग्रह का उपयोग कर सकते हैं, जिसमें एक लेनदेन है - या जो मैं सामान्य रूप से करता हूं वह है get with association, या उत्सुकता लाने के लिए सेट करें।


6

मुझे लगता है कि आपके सत्र को दृश्य प्रतिपादन के दौरान खुला रखने के लिए आपको OpenSessionInViewFilter की आवश्यकता है (लेकिन यह बहुत अच्छा अभ्यास नहीं है)।


1
जैसा कि मैं JSP या किसी भी चीज़ का उपयोग नहीं कर रहा हूँ, बस एक REST-api बना रहा हूँ, @ ट्रेंसेक्शनल मेरे लिए करेगा। लेकिन अन्य समय पर उपयोगी होगा। धन्यवाद।
मत्स्येमन

@ मात्सेमन्न मुझे पता है कि अब देर हो चुकी है ... लेकिन आप OpenSessionInViewFilter का उपयोग एक नियंत्रक में भी कर सकते हैं और साथ ही साथ सत्र भी मौजूद रहेगा जब तक कि कोई प्रतिक्रिया संकलित नहीं हो जाती ...
Vishwas Shashidhar

@ मत्स्यसेन धन्यवाद! लेन-देन-एनोटेशन ने किया मेरे लिए टोटका! fyi: यह भी काम करता है अगर आप सिर्फ एक बाकी वर्ग के सुपरक्लास को एनोटेट करते हैं।
desperateCoder

3

स्प्रिंग डेटा JpaRepository

स्प्रिंग डेटा JpaRepositoryनिम्नलिखित दो विधियों को परिभाषित करता है:

  • getOne, जो एक इकाई प्रॉक्सी को लौटाता है जो एक बाल इकाई को बनाए रखने के लिए @ManyToOneया @OneToOneमाता - पिता के सहयोग को स्थापित करने के लिए उपयुक्त है ।
  • findById, जो संबंधित तालिका से इकाई को लोड करने वाले सेलेक्ट स्टेटमेंट को चलाने के बाद इकाई POJO लौटाता है

हालाँकि, आपके मामले में, आपने getOneया तो फोन नहीं किया findById:

Person person = personRepository.findOne(1L);

इसलिए, मुझे लगता है कि findOneविधि वह विधि है जिसे आपने में परिभाषित किया है PersonRepository। हालाँकि, findOneविधि आपके मामले में बहुत उपयोगी नहीं है। चूंकि आपको संग्रह Personके साथ लाने की आवश्यकता है roles, इसलिए findOneWithRolesइसके बजाय एक विधि का उपयोग करना बेहतर है ।

कस्टम स्प्रिंग डेटा विधियाँ

आप एक PersonRepositoryCustomइंटरफेस को परिभाषित कर सकते हैं , इस प्रकार है:

public interface PersonRepository
    extends JpaRepository<Person, Long>, PersonRepositoryCustom { 

}

public interface PersonRepositoryCustom {
    Person findOneWithRoles(Long id);
}

और इसके कार्यान्वयन को इस तरह परिभाषित करें:

public class PersonRepositoryImpl implements PersonRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public Person findOneWithRoles(Long id)() {
        return entityManager.createQuery("""
            select p 
            from Person p
            left join fetch p.roles
            where p.id = :id 
            """, Person.class)
        .setParameter("id", id)
        .getSingleResult();
    }
}

बस!


क्या कोई कारण है कि आपने स्वयं क्वेरी लिखी और @rakpan उत्तर में EntityGraph जैसे समाधान का उपयोग नहीं किया? यह एक ही परिणाम नहीं होगा?
जीरो वन्देवेल्डे

EntityGraph का उपयोग करने के लिए ओवरहेड JPQL क्वेरी से अधिक है। लंबे समय तक, आप क्वेरी लिखना बेहतर समझते हैं।
व्लाद मिहालसी

क्या आप ओवरहेड पर विस्तृत कर सकते हैं (यह कहां से आता है, क्या यह ध्यान देने योग्य है, ...)? क्योंकि मुझे समझ में नहीं आता कि अगर दोनों एक ही क्वेरी उत्पन्न करते हैं तो एक उच्च ओवरहेड क्यों होता है।
जीरो वन्देवेल्डे

1
क्योंकि EntityGraphs की योजनाएँ कैश नहीं हैं जैसे JPQL हैं। यह एक महत्वपूर्ण प्रदर्शन हिट हो सकता है।
व्लाद मिहालसीया

1
बिल्कुल सही। मुझे इसके बारे में एक लेख लिखना होगा जब मेरे पास कुछ समय होगा।
व्लाद मिहालसी

1

आप इस तरह से कर सकते हैं:

@Override
public FaqQuestions getFaqQuestionById(Long questionId) {
    session = sessionFactory.openSession();
    tx = session.beginTransaction();
    FaqQuestions faqQuestions = null;
    try {
        faqQuestions = (FaqQuestions) session.get(FaqQuestions.class,
                questionId);
        Hibernate.initialize(faqQuestions.getFaqAnswers());

        tx.commit();
        faqQuestions.getFaqAnswers().size();
    } finally {
        session.close();
    }
    return faqQuestions;
}

बस अपने नियंत्रक का उपयोग करें।

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