FetchMode स्प्रिंग डेटा JPA में कैसे काम करता है


95

मेरी परियोजना में तीन मॉडल ऑब्जेक्ट के बीच एक संबंध है (पोस्ट के अंत में मॉडल और रिपॉजिटरी स्निपेट।

जब मैं कॉल PlaceRepository.findByIdकरता हूं तो यह तीन चुनिंदा प्रश्नों को फायर करता है:

( "एसक्यूएल")

  1. SELECT * FROM place p where id = arg
  2. SELECT * FROM user u where u.id = place.user.id
  3. SELECT * FROM city c LEFT OUTER JOIN state s on c.woj_id = s.id where c.id = place.city.id

बल्कि यह असामान्य व्यवहार है (मेरे लिए)। जहां तक ​​मैं हाइबरनेट प्रलेखन को पढ़ने के बाद बता सकता हूं, इसमें हमेशा जॉइन प्रश्नों का उपयोग करना चाहिए। कक्षा में FetchType.LAZYपरिवर्तित होने पर प्रश्नों में कोई अंतर नहीं होता है (अतिरिक्त चयन के साथ क्वेरी), वर्ग के लिए समान जब (जोइन के साथ क्वेरी) में बदल जाता है ।FetchType.EAGERPlaceCityFetchType.LAZYFetchType.EAGER

जब मैं CityRepository.findByIdदो फायर को दबाने का उपयोग करता हूं :

  1. SELECT * FROM city c where id = arg
  2. SELECT * FROM state s where id = city.state.id

मेरा लक्ष्य सभी स्थितियों में एक समान व्यवहार रखना है (या तो हमेशा शामिल हों या चयन करें, जोइन को हालांकि पसंद किया जाता है)।

मॉडल परिभाषाएँ:

स्थान:

@Entity
@Table(name = "place")
public class Place extends Identified {

    @Fetch(FetchMode.JOIN)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "id_user_author")
    private User author;

    @Fetch(FetchMode.JOIN)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "area_city_id")
    private City city;
    //getters and setters
}

Faridabad:

@Entity
@Table(name = "area_city")
public class City extends Identified {

    @Fetch(FetchMode.JOIN)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "area_woj_id")
    private State state;
    //getters and setters
}

डेटा संग्रह स्थान:

PlaceRepository

public interface PlaceRepository extends JpaRepository<Place, Long>, PlaceRepositoryCustom {
    Place findById(int id);
}

UserRepository:

public interface UserRepository extends JpaRepository<User, Long> {
        List<User> findAll();
    User findById(int id);
}

CityRepository:

public interface CityRepository extends JpaRepository<City, Long>, CityRepositoryCustom {    
    City findById(int id);
}

: Hava 5 तरीके पर एक नज़र आलसी relationsships प्रारंभ करने में thoughts-on-java.org/...
ग्रिगोरी Kislin

जवाबों:


114

मुझे लगता है कि स्प्रिंग डेटा FetchMode को अनदेखा करता है। मैं हमेशा का उपयोग @NamedEntityGraphऔर @EntityGraphजब वसंत डाटा के साथ काम करने एनोटेशन

@Entity
@NamedEntityGraph(name = "GroupInfo.detail",
  attributeNodes = @NamedAttributeNode("members"))
public class GroupInfo {

  // default fetch mode is lazy.
  @ManyToMany
  List<GroupMember> members = new ArrayList<GroupMember>();

  …
}

@Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {

  @EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
  GroupInfo getByGroupName(String name);

}

यहां प्रलेखन की जाँच करें


1
मैं मेरे लिए काम नहीं कर रहा हूँ। मेरा मतलब है कि यह काम करता है, लेकिन ... जब मैं '@ अनंतता' के साथ रिपॉजिटरी का एनोटेट करता हूं तो यह खुद (आमतौर पर) काम नहीं करता है। उदाहरण के लिए: `Place findById (int id);` कार्य करता है लेकिन List<Place> findAll();अपवाद के साथ समाप्त होता है org.springframework.data.mapping.PropertyReferenceException: No property find found for type Place!। यह काम करता है जब मैं मैन्युअल रूप से जोड़ता हूं @Query("select p from Place p")। हालांकि वर्कअराउंड की तरह लगता है।
सिरकोमेटा

हो सकता है कि यह findAll पर काम करता है (क्योंकि यह JpaRepository इंटरफ़ेस से एक मौजूदा विधि है, जबकि आपकी अन्य विधि "findById" रनटाइम पर उत्पन्न एक कस्टम क्वेरी विधि है।
wesker317

मैंने इसे उचित उत्तर के रूप में चिह्नित करने का निर्णय लिया है क्योंकि यह सबसे अच्छा है। हालांकि यह सही नहीं है। यह ज्यादातर परिदृश्यों में काम करता है लेकिन अब तक मैंने स्प्रिंग-डेटा-जेपीए में बग्स को अधिक जटिल एंटिटीग्राफ के साथ देखा है। धन्यवाद :)
सिरकटोमा २२'१५ को

2
@EntityGraphके बाद से यह निर्दिष्ट किया जा नहीं कर सकते वास्तविक स्थितियों में लगभग ununsable है एक तरह से क्या Fetchहम उपयोग करना चाहते हैं ( JOIN, SUBSELECT, SELECT, BATCH)। यह @OneToManyसंघ के साथ संयोजन में और हाइबरनेट फ़ेच को मेमोरी में पूरी तालिका बनाता है, भले ही हम क्वेरी का उपयोग करें MaxResults
ओन्ड्रेज बोजेक

1
धन्यवाद, मैं यह कहना चाहता था कि जेपीक्यूएल क्वेरी चुनिंदा पॉलिसी के साथ डिफॉल्ट लाने की रणनीति को ओवरराइड कर सकती है ।
adrhc

53

सबसे पहले, @Fetch(FetchMode.JOIN)और @ManyToOne(fetch = FetchType.LAZY)विरोधी हैं, एक ईएजीआर को लाने का निर्देश देता है, जबकि दूसरा एक एलएजीवाई लाने का सुझाव देता है।

उत्सुक लाने वाला शायद ही कभी एक अच्छा विकल्प होता है और एक पूर्वानुमानित व्यवहार के लिए, आप क्वेरी-टाइम JOIN FETCHनिर्देश का उपयोग करके बेहतर होते हैं :

public interface PlaceRepository extends JpaRepository<Place, Long>, PlaceRepositoryCustom {

    @Query(value = "SELECT p FROM Place p LEFT JOIN FETCH p.author LEFT JOIN FETCH p.city c LEFT JOIN FETCH c.state where p.id = :id")
    Place findById(@Param("id") int id);
}

public interface CityRepository extends JpaRepository<City, Long>, CityRepositoryCustom { 
    @Query(value = "SELECT c FROM City c LEFT JOIN FETCH c.state where c.id = :id")   
    City findById(@Param("id") int id);
}

3
मानदंड और स्प्रिंग डेटा विनिर्देशों के साथ समान परिणाम प्राप्त करने का कोई तरीका है?
svlada

2
नहीं लाने के लिए हिस्सा है, जो JPA लाने की आवश्यकता है प्रोफ़ाइल।
व्लाद मिहालसी

Vlad Mihalcea, क्या आप स्प्रिंग डेटा JPA मानदंड (विनिर्देश) का उपयोग करके ऐसा करने के लिए एक उदाहरण के साथ लिंक साझा कर सकते हैं? कृपया
बजे यान खोंस्की

मेरे पास ऐसा कोई उदाहरण नहीं है, लेकिन आप निश्चित रूप से स्प्रिंग डेटा जेपीए ट्यूटोरियल में एक पा सकते हैं।
व्लाद मिहेल्सिया

यदि क्वेरी-समय का उपयोग करते हैं ..... तो क्या आपको इकाई पर @OneToMany ... आदि को परिभाषित करने की आवश्यकता होगी?
एरिक हुआंग

19

स्प्रिंग-जेपा इकाई प्रबंधक का उपयोग करके क्वेरी बनाता है, और हाइबरनेट, भ्रूण मोड को अनदेखा कर देगा यदि क्वेरी इकाई प्रबंधक द्वारा बनाई गई थी।

निम्नलिखित काम है कि मैंने इस्तेमाल किया है:

  1. एक कस्टम रिपॉजिटरी लागू करें जो SimpleJpaRepository से विरासत में मिली है

  2. विधि को ओवरराइड करें getQuery(Specification<T> spec, Sort sort):

    @Override
    protected TypedQuery<T> getQuery(Specification<T> spec, Sort sort) { 
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<T> query = builder.createQuery(getDomainClass());
    
        Root<T> root = applySpecificationToCriteria(spec, query);
        query.select(root);
    
        applyFetchMode(root);
    
        if (sort != null) {
            query.orderBy(toOrders(sort, root, builder));
        }
    
        return applyRepositoryMethodMetadata(entityManager.createQuery(query));
    }
    

    विधि के बीच में, applyFetchMode(root);हाइबरनेट को सही जोड़ के साथ क्वेरी बनाने के लिए, फ़िंच मोड लागू करने के लिए जोड़ें ।

    (दुर्भाग्य से हमें पूरी विधि और संबंधित निजी विधियों को आधार वर्ग से कॉपी करने की आवश्यकता है क्योंकि कोई अन्य विस्तार बिंदु नहीं था।)

  3. लागू करें applyFetchMode:

    private void applyFetchMode(Root<T> root) {
        for (Field field : getDomainClass().getDeclaredFields()) {
    
            Fetch fetch = field.getAnnotation(Fetch.class);
    
            if (fetch != null && fetch.value() == FetchMode.JOIN) {
                root.fetch(field.getName(), JoinType.LEFT);
            }
        }
    }
    

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

क्या आप कृपया सभी आयात विवरण जोड़ सकते हैं? धन्यवाद।
ग्रेनडाकोडर

3

" FetchType.LAZY" केवल प्राथमिक टेबल के लिए आग लगाएगा। यदि आपके कोड में आप किसी अन्य विधि को कहते हैं जिसमें मूल तालिका निर्भरता है, तो यह उस तालिका जानकारी को प्राप्त करने के लिए क्वेरी को फायर करेगा। (FIRES MULTIPLE Select)

" FetchType.EAGER" प्रासंगिक माता-पिता की तालिका सहित सभी तालिका से सीधे जुड़ जाएगा। (USES JOIN)

कब उपयोग करें: मान लें कि आपको अनिवार्य रूप से आश्रित अभिभावक तालिका सूचना का उपयोग करने की आवश्यकता है तो चुनें FetchType.EAGER। यदि आपको केवल कुछ रिकॉर्ड के लिए जानकारी की आवश्यकता है तो उपयोग करें FetchType.LAZY

याद रखें, FetchType.LAZYअपने कोड में उस स्थान पर एक सक्रिय db सेशन फैक्ट्री की आवश्यकता है जहाँ यदि आप मूल तालिका जानकारी प्राप्त करना चुनते हैं।

जैसे LAZY:

.. Place fetched from db from your dao loayer
.. only place table information retrieved
.. some code
.. getCity() method called... Here db request will be fired to get city table info

अतिरिक्त संदर्भ


दिलचस्प बात यह है कि मुझे NamedEntityGraphगैर-हाइड्रेटेड ऑब्जेक्ट ग्राफ के बाद से यह उत्तर सही रास्ते पर मिला ।
जेजे ज़ाबकर

यह उत्तर अधिक उत्थान के योग्य है। यह सकुशल है और मुझे यह समझने में बहुत मदद मिली कि मैं बहुत सारे "जादुई रूप से ट्रिगर" प्रश्नों को क्यों देख रहा था ... बहुत बहुत धन्यवाद!
क्लिंट ईस्टवुड

3

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

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

स्प्रिंग डेटा अनुमान यहां मदद कर सकते हैं, लेकिन कुछ बिंदु पर आपको समाधान की आवश्यकता होगी ब्लेज़-पर्सिस्टेंस एंटिटी व्यूज़ जो इसे बहुत आसान बनाता है और इसमें बहुत अधिक विशेषताएं हैं जो आस्तीन में काम में आएंगे! आप बस प्रति इकाई एक DTO इंटरफ़ेस बनाते हैं जहाँ गेटर्स आपके द्वारा आवश्यक डेटा के सबसेट का प्रतिनिधित्व करते हैं। आपकी समस्या का समाधान इस तरह दिख सकता है

@EntityView(Identified.class)
public interface IdentifiedView {
    @IdMapping
    Integer getId();
}

@EntityView(Identified.class)
public interface UserView extends IdentifiedView {
    String getName();
}

@EntityView(Identified.class)
public interface StateView extends IdentifiedView {
    String getName();
}

@EntityView(Place.class)
public interface PlaceView extends IdentifiedView {
    UserView getAuthor();
    CityView getCity();
}

@EntityView(City.class)
public interface CityView extends IdentifiedView {
    StateView getState();
}

public interface PlaceRepository extends JpaRepository<Place, Long>, PlaceRepositoryCustom {
    PlaceView findById(int id);
}

public interface UserRepository extends JpaRepository<User, Long> {
    List<UserView> findAllByOrderByIdAsc();
    UserView findById(int id);
}

public interface CityRepository extends JpaRepository<City, Long>, CityRepositoryCustom {    
    CityView findById(int id);
}

डिस्क्लेमर, मैं ब्लेज़-पर्सिस्टेंस का लेखक हूं, इसलिए मैं पक्षपाती हो सकता हूं।


2

मैं ने स्वप्निल@Fetch एनोटेशन को संभालने के लिए सपने 83619 के बारे में विस्तार से बताया । नेस्टेड संबद्ध कक्षाओं में एनोटेशन खोजने के लिए मैंने पुनरावर्ती विधि का उपयोग किया।

इसलिए आपको कस्टम रिपॉजिटरी को लागू करना होगा और ओवरराइड getQuery(spec, domainClass, sort)विधि । दुर्भाग्य से आपको सभी संदर्भित निजी विधियों की प्रतिलिपि भी बनानी होगी :(

यहां कोड है, कॉपी किए गए निजी तरीकों को छोड़ दिया गया है।
संपादित करें: शेष निजी तरीकों को जोड़ा गया।

@NoRepositoryBean
public class EntityGraphRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> {

    private final EntityManager em;
    protected JpaEntityInformation<T, ?> entityInformation;

    public EntityGraphRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.em = entityManager;
        this.entityInformation = entityInformation;
    }

    @Override
    protected <S extends T> TypedQuery<S> getQuery(Specification<S> spec, Class<S> domainClass, Sort sort) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<S> query = builder.createQuery(domainClass);

        Root<S> root = applySpecificationToCriteria(spec, domainClass, query);

        query.select(root);
        applyFetchMode(root);

        if (sort != null) {
            query.orderBy(toOrders(sort, root, builder));
        }

        return applyRepositoryMethodMetadata(em.createQuery(query));
    }

    private Map<String, Join<?, ?>> joinCache;

    private void applyFetchMode(Root<? extends T> root) {
        joinCache = new HashMap<>();
        applyFetchMode(root, getDomainClass(), "");
    }

    private void applyFetchMode(FetchParent<?, ?> root, Class<?> clazz, String path) {
        for (Field field : clazz.getDeclaredFields()) {
            Fetch fetch = field.getAnnotation(Fetch.class);

            if (fetch != null && fetch.value() == FetchMode.JOIN) {
                FetchParent<?, ?> descent = root.fetch(field.getName(), JoinType.LEFT);
                String fieldPath = path + "." + field.getName();
                joinCache.put(path, (Join) descent);

                applyFetchMode(descent, field.getType(), fieldPath);
            }
        }
    }

    /**
     * Applies the given {@link Specification} to the given {@link CriteriaQuery}.
     *
     * @param spec can be {@literal null}.
     * @param domainClass must not be {@literal null}.
     * @param query must not be {@literal null}.
     * @return
     */
    private <S, U extends T> Root<U> applySpecificationToCriteria(Specification<U> spec, Class<U> domainClass,
        CriteriaQuery<S> query) {

        Assert.notNull(query);
        Assert.notNull(domainClass);
        Root<U> root = query.from(domainClass);

        if (spec == null) {
            return root;
        }

        CriteriaBuilder builder = em.getCriteriaBuilder();
        Predicate predicate = spec.toPredicate(root, query, builder);

        if (predicate != null) {
            query.where(predicate);
        }

        return root;
    }

    private <S> TypedQuery<S> applyRepositoryMethodMetadata(TypedQuery<S> query) {
        if (getRepositoryMethodMetadata() == null) {
            return query;
        }

        LockModeType type = getRepositoryMethodMetadata().getLockModeType();
        TypedQuery<S> toReturn = type == null ? query : query.setLockMode(type);

        applyQueryHints(toReturn);

        return toReturn;
    }

    private void applyQueryHints(Query query) {
        for (Map.Entry<String, Object> hint : getQueryHints().entrySet()) {
            query.setHint(hint.getKey(), hint.getValue());
        }
    }

    public Class<T> getEntityType() {
        return entityInformation.getJavaType();
    }

    public EntityManager getEm() {
        return em;
    }
}

मैं आपके समाधान की कोशिश कर रहा हूं, लेकिन मेरे पास एक निजी मेटाडेटा चर है जो कॉपी करने के तरीकों में से एक है जो परेशानी दे रहा है। क्या आप अंतिम कोड साझा कर सकते हैं?
होमर १

पुनरावर्ती काम नहीं करता है। अगर मेरे पास OneToMany है तो यह java.util से गुजरता है। अगले पुनरावृत्ति पर जाएं
antohoho

नहीं अच्छी तरह से अभी तक यह परीक्षण किया है, लेकिन लगता है कि कुछ इस तरह होना चाहिए ((शामिल हों) वंश) .getJavaType () field.getType (के बजाय) के कॉल रिकर्सिवली applyFetchMode
antohoho

2


इस लिंक से http://jdpgrailsdev.github.io/blog/2014/09/09/spring_data_hibernate_join.html :

यदि आप हाइबरनेट के ऊपर JPA का उपयोग कर रहे हैं, तो Hibernate द्वारा JOINHowever में उपयोग किए गए FetchMode को सेट करने का कोई तरीका नहीं है, यदि आप JPA का उपयोग Hibernate के शीर्ष पर कर रहे हैं, तो Hibernate से JOIN तक उपयोग किए गए FetchMode को सेट करने का कोई तरीका नहीं है।

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

final long userId = 1;

final Specification<User> spec = new Specification<User>() {
   @Override
    public Predicate toPredicate(final Root<User> root, final 
     CriteriaQuery<?> query, final CriteriaBuilder cb) {
    query.distinct(true);
    root.fetch("permissions", JoinType.LEFT);
    return cb.equal(root.get("id"), userId);
 }
};

List<User> users = userRepository.findAll(spec);

2

व्लाद मिहालसी के अनुसार (देखें https://vladmihalcea.com/hibernate-facts-the-importance-of-fetch-strategy/ ):

JPQL क्वेरी डिफ़ॉल्ट फ़ेचिंग रणनीति को ओवरराइड कर सकती है। यदि हम स्पष्ट रूप से यह घोषित नहीं करते हैं कि हम आंतरिक या बायीं ओर जुड़ने वाले निर्देशों का उपयोग करके क्या प्राप्त करना चाहते हैं, तो डिफ़ॉल्ट चयन भ्रूण नीति लागू होती है।

ऐसा लगता है कि JPQL क्वेरी आपकी घोषित फ़ोकसिंग रणनीति को ओवरराइड कर सकती है, इसलिए आपको join fetchकुछ संदर्भित इकाई को उत्सुकता से लोड करने के लिए उपयोग करना होगा या केवल EntityManager के साथ आईडी द्वारा लोड करना होगा (जो आपकी फ़िशिंग रणनीति का पालन करेगा लेकिन आपके मामले के लिए समाधान नहीं हो सकता है )।

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