JPA 2 में, मानदंड का उपयोग करते हुए, परिणामों की गणना कैसे करें


114

मैं JPA 2 के लिए नया हूँ और यह CriteriaBuilder / CriteriaQuery API है:

CriteriaQuery जावाडोक

CriteriaQuery जावा ईई 6 ट्यूटोरियल में

मैं वास्तव में उन्हें पुनः प्राप्त किए बिना एक मानदंड के परिणामों की गणना करना चाहूंगा। क्या यह संभव है, मुझे ऐसा कोई तरीका नहीं मिला, ऐसा करने का एकमात्र तरीका होगा:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();

CriteriaQuery<MyEntity> cq = cb
        .createQuery(MyEntityclass);

// initialize predicates here

return entityManager.createQuery(cq).getResultList().size();

और यह करने के लिए उचित तरीका नहीं हो सकता ...

क्या कोई उपाय है?


यह बहुत उपयोगी होगा यदि कोई नीचे दिए गए उत्तरों में मदद कर सकता है या शामिल कर सकता है। JPA मानदंड एपीआई का उपयोग करके निम्नलिखित क्वेरी कैसे प्राप्त करें? my_table से count (अलग col1, col2, col3) का चयन करें;
भावेश

नीचे उत्तर देख रहे हैं, लेकिन इसके बजाय qb.count का उपयोग करें qb.distinctCount @Bhavesh
टोनिनो

जवाबों:


220

एक क्वेरी प्रकार MyEntityवापस आने वाला है MyEntity। आप एक क्वेरी चाहते हैं Long

CriteriaBuilder qb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = qb.createQuery(Long.class);
cq.select(qb.count(cq.from(MyEntity.class)));
cq.where(/*your stuff*/);
return entityManager.createQuery(cq).getSingleResult();

जाहिर है आप उदाहरण में छोड़ दिए गए प्रतिबंधों और समूहों आदि के साथ अपनी अभिव्यक्ति का निर्माण करना चाहेंगे।


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

6
@Barett अगर यह एक बड़ी गिनती है, तो आप शायद सैकड़ों या हजारों संस्थाओं की सूची को मेमोरी में लोड नहीं करना चाहते हैं ताकि पता चल सके कि कितने हैं!
Affe

@ इसका उपयोग पेजेशन के मामले में बहुत किया जाता है। इसलिए कुल संख्या और वास्तविक पंक्तियों के केवल सबसेट की आवश्यकता है।
gkephorus 13:27

2
ध्यान रहे कि qb.countअधिक किया जाता है Root<MyEntity>(आपकी क्वेरी के Root<MyEntity>myEntity = cq.from (MyEntity.class)) और इस बार अपने सामान्य चयन कोड में पहले से ही है और जब आप भूल जाते हैं कि आप एक आत्म करने के लिए शामिल होने के साथ खत्म।
gkephorus 13:27

2
ऑब्जेक्ट्स की पुनर्प्राप्ति के लिए समान मानदंड और गणना के लिए आपको रूट पर उपनाम का उपयोग करने की आवश्यकता हो सकती है, उदाहरण के लिए forum.hibernate.org/viewtopic.php?p=2471522#p2471522 देखें ।
पूल

31

मैंने इसे cb.createQuery () (परिणाम प्रकार पैरामीटर के बिना) का उपयोग करके हल किया है:

public class Blah() {

    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
    CriteriaQuery query = criteriaBuilder.createQuery();
    Root<Entity> root;
    Predicate whereClause;
    EntityManager entityManager;
    Class<Entity> domainClass;

    ... Methods to create where clause ...

    public Blah(EntityManager entityManager, Class<Entity> domainClass) {
        this.entityManager = entityManager;
        this.domainClass = domainClass;
        criteriaBuilder = entityManager.getCriteriaBuilder();
        query = criteriaBuilder.createQuery();
        whereClause = criteriaBuilder.equal(criteriaBuilder.literal(1), 1);
        root = query.from(domainClass);
    }

    public CriteriaQuery<Entity> getQuery() {
        query.select(root);
        query.where(whereClause);
        return query;
    }

    public CriteriaQuery<Long> getQueryForCount() {
        query.select(criteriaBuilder.count(root));
        query.where(whereClause);
        return query;
    }

    public List<Entity> list() {
        TypedQuery<Entity> q = this.entityManager.createQuery(this.getQuery());
        return q.getResultList();
    }

    public Long count() {
        TypedQuery<Long> q = this.entityManager.createQuery(this.getQueryForCount());
        return q.getSingleResult();
    }
}

आशा करता हूँ की ये काम करेगा :)


23
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Long> cq = cb.createQuery(Long.class);
cq.select(cb.count(cq.from(MyEntity.class)));

return em.createQuery(cq).getSingleResult();

12

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

इस उत्तर को थोड़ा संशोधित किया गया है ।

public <T> long count(final CriteriaBuilder cb, final CriteriaQuery<T> selectQuery,
        Root<T> root) {
    CriteriaQuery<Long> query = createCountQuery(cb, selectQuery, root);
    return this.entityManager.createQuery(query).getSingleResult();
}

private <T> CriteriaQuery<Long> createCountQuery(final CriteriaBuilder cb,
        final CriteriaQuery<T> criteria, final Root<T> root) {

    final CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
    final Root<T> countRoot = countQuery.from(criteria.getResultType());

    doJoins(root.getJoins(), countRoot);
    doJoinsOnFetches(root.getFetches(), countRoot);

    countQuery.select(cb.count(countRoot));
    countQuery.where(criteria.getRestriction());

    countRoot.alias(root.getAlias());

    return countQuery.distinct(criteria.isDistinct());
}

@SuppressWarnings("unchecked")
private void doJoinsOnFetches(Set<? extends Fetch<?, ?>> joins, Root<?> root) {
    doJoins((Set<? extends Join<?, ?>>) joins, root);
}

private void doJoins(Set<? extends Join<?, ?>> joins, Root<?> root) {
    for (Join<?, ?> join : joins) {
        Join<?, ?> joined = root.join(join.getAttribute().getName(), join.getJoinType());
        joined.alias(join.getAlias());
        doJoins(join.getJoins(), joined);
    }
}

private void doJoins(Set<? extends Join<?, ?>> joins, Join<?, ?> root) {
    for (Join<?, ?> join : joins) {
        Join<?, ?> joined = root.join(join.getAttribute().getName(), join.getJoinType());
        joined.alias(join.getAlias());
        doJoins(join.getJoins(), joined);
    }
}

आशा है कि यह किसी का समय बचाता है।

क्योंकि IMHO JPA मानदंड API सहज नहीं है और न ही काफी पठनीय है।


2
@specializt निश्चित रूप से यह सही नहीं है - उदाहरण के लिए उपरोक्त समाधान अभी भी भ्रूण पर एक पुनरावर्ती जोड़ों को याद कर रहा है। लेकिन क्या आपको लगता है कि सिर्फ इस वजह से मुझे अपने विचार साझा नहीं करने चाहिए? IMHO ज्ञान साझा करना StackOverfow के पीछे मुख्य विचार है।
जी। डेमेकी

डेटाबेस पर पुनरावृत्ति हमेशा सबसे खराब संभव समाधान है कल्पनाशील ... एक शुरुआती गलती है।
specializt

@specializt recursion on databases? मैं एपीआई स्तर पर पुनरावृत्ति के बारे में बात कर रहा था। इन अवधारणाओं को भ्रमित न करें :-) जेपीए बहुत शक्तिशाली / जटिल एपीआई के साथ आता है जो आपको एक ही क्वेरी में कई जॉइन / भ्रूण / एकत्रीकरण / उपनाम आदि करने की अनुमति देता है। आपको गिनती करते समय इससे निपटना होगा।
जी डेमेकी

1
जाहिरा तौर पर आपने अभी तक यह समझा है कि जेपीए कैसे काम करता है - आप में से अधिकांश मानदंडों को उचित डेटाबेस प्रश्नों पर मैप किया जाएगा, इनमें (बेहद अजीब) जोड़ शामिल हैं। SQL आउटपुट सक्रिय करें और अपनी गलती मानें - कोई "API लेयर" नहीं है, JPA एक ABSTRACTION लेयर है
विशेष

सबसे अधिक संभावना है, आपको कई कैसकेड जॉइन दिखाई देंगे - क्योंकि जेपीए कैंट अभी तक एसक्यूएल फ़ंक्शन स्वचालित रूप से नहीं बनाते हैं; लेकिन यह कभी-कभी बदल जाएगा ... शायद जेपीए 3 के साथ, मुझे इन बातों के बारे में चर्चा याद है
विशेष

5

यह थोड़ा मुश्किल है, आपके द्वारा उपयोग किए जाने वाले JPA 2 कार्यान्वयन के आधार पर, यह एक EclipseLink 2.4.1 के लिए काम करता है, लेकिन हाइबरनेट के लिए नहीं, यहाँ EclipseLink के लिए एक सामान्य मानदंड गणना:

public static Long count(final EntityManager em, final CriteriaQuery<?> criteria)
  {
    final CriteriaBuilder builder=em.getCriteriaBuilder();
    final CriteriaQuery<Long> countCriteria=builder.createQuery(Long.class);
    countCriteria.select(builder.count(criteria.getRoots().iterator().next()));
    final Predicate
            groupRestriction=criteria.getGroupRestriction(),
            fromRestriction=criteria.getRestriction();
    if(groupRestriction != null){
      countCriteria.having(groupRestriction);
    }
    if(fromRestriction != null){
      countCriteria.where(fromRestriction);
    }
    countCriteria.groupBy(criteria.getGroupList());
    countCriteria.distinct(criteria.isDistinct());
    return em.createQuery(countCriteria).getSingleResult();
  }

दूसरे दिन जब मैं एक्लिप्सलिंक से हाइबरनेट में माइग्रेट हुआ और मुझे अपनी गिनती फ़ंक्शन को निम्न में बदलना पड़ा, तो बेझिझक इसका उपयोग करें क्योंकि यह हल करने के लिए एक कठिन समस्या है, यह आपके मामले के लिए काम नहीं कर सकता है, यह हाइबरनेट के उपयोग में है। 4.x, ध्यान दें कि मैं यह अनुमान लगाने की कोशिश नहीं करता कि कौन सी जड़ है, इसके बजाय मैं इसे प्रश्न से हल करता हूं ताकि समस्या हल हो जाए, बहुत से अस्पष्ट कोने के मामलों में अनुमान लगाने की कोशिश करें:

  public static <T> long count(EntityManager em,Root<T> root,CriteriaQuery<T> criteria)
  {
    final CriteriaBuilder builder=em.getCriteriaBuilder();
    final CriteriaQuery<Long> countCriteria=builder.createQuery(Long.class);

    countCriteria.select(builder.count(root));

    for(Root<?> fromRoot : criteria.getRoots()){
      countCriteria.getRoots().add(fromRoot);
    }

    final Predicate whereRestriction=criteria.getRestriction();
    if(whereRestriction!=null){
      countCriteria.where(whereRestriction);
    }

    final Predicate groupRestriction=criteria.getGroupRestriction();
    if(groupRestriction!=null){
      countCriteria.having(groupRestriction);
    }

    countCriteria.groupBy(criteria.getGroupList());
    countCriteria.distinct(criteria.isDistinct());
    return em.createQuery(countCriteria).getSingleResult();
  }

क्या होगा यदि क्वेरी शामिल हो गई है?
डेव

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

1
यदि मूल क्वेरी GroupBy क्वेरी है, तो परिणाम प्रत्येक समूह के लिए एक गणना होगा। अगर हम एक सबटाइकाइटर को एक सबडिवीके में बना सकते हैं, तो सबक्वेरी को गिनें, यह सभी मामलों में काम करेगा। क्या हम वह कर सकते हैं?
डेव

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

1

आप अनुमानों का उपयोग भी कर सकते हैं:

ProjectionList projection = Projections.projectionList();
projection.add(Projections.rowCount());
criteria.setProjection(projection);

Long totalRows = (Long) criteria.list().get(0);

1
मुझे डर है कि अनुमान एपीआई हाइबरनेट विशिष्ट है लेकिन सवाल जेपीए 2 के बारे में पूछ रहा है।
gersonZaragocin

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

gersonZaragocin सहमत हैं, लेकिन टिप्पणियों में कोई कोड ब्लॉक नहीं है
Pavel Evstigneev

0

स्प्रिंग डेटा जपा के साथ, हम इस विधि का उपयोग कर सकते हैं:

    /*
     * (non-Javadoc)
     * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#count(org.springframework.data.jpa.domain.Specification)
     */
    @Override
    public long count(@Nullable Specification<T> spec) {
        return executeCountQuery(getCountQuery(spec, getDomainClass()));
    }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.