JPA 2'de, CriteriaQuery kullanarak sonuçların nasıl sayılacağı


114

JPA 2 için oldukça yeniyim ve CriteriaBuilder / CriteriaQuery API'sı:

CriteriaQuery javadoc

CriteriaQuery Java EE 6 eğitiminde

Bir CriteriaQuery'nin sonuçlarını gerçekte onları almadan saymak istiyorum. Bu mümkün mü, böyle bir yöntem bulamadım, tek yol şunu yapmak olurdu:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();

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

// initialize predicates here

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

Ve bunu yapmanın doğru yolu bu olamaz ...

Bir çözüm var mı?


Birinin yardımcı olabilmesi veya aşağıdaki cevaplara yer vermesi çok faydalı olacaktır. JPA kriterleri API'sini kullanarak aşağıdaki sayma sorgusu nasıl elde edilir? my_table'dan count (farklı col1, col2, col3) seçin;
Bhavesh

aşağıdaki cevaba bakıyorum ama qb.count yerine qb.distinctCount kullanın @Bhavesh
Tonino

Yanıtlar:


220

Bir tür sorgu MyEntitydönecektir MyEntity. Bir 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();

Açıkçası, örnekte atladığınız kısıtlamalar ve gruplamalar vb. İle ifadenizi oluşturmak isteyeceksiniz.


3
Ben de öyle düşünmüştüm, teşekkürler. Ancak bu, sonuçların sayısını ve SQL ile benzer olduğunu bildiğim gerçek sonuçları sorgulamak için aynı sorgu örneğini kullanamayacağım anlamına gelir, ancak bu, bu API'yi çok daha OOP benzeri yapar. En azından bazı yüklemleri yeniden kullanabilirim, sanırım.
Sean Patrick Floyd

6
@Barett eğer oldukça büyük bir sayı ise, muhtemelen kaç tane olduğunu bulmak için belleğe yüzlerce veya binlerce varlığın bir listesini yüklemek istemezsiniz!
Affe

@Barett bu çok sayfalandırma durumunda kullanılır. Bu nedenle, toplam sayıya ve gerçek satırların yalnızca bir alt kümesine ihtiyaç vardır.
gkephorus

2
Unutmamanız qb.countüzerinde yapılır Root<MyEntity>(Sorgunuzun Root<MyEntity>myEntity = cq.from (MyEntity.class)) ve bu normal seçme kodunda zaten sık sık ve unutur zaman bir kendine katılmak ile bitirmek.
gkephorus

2
Nesnelerin ve sayımın yeniden alınması için aynı kriterleri yeniden kullanmak için, kökte takma adlar kullanmanız gerekebilir, örnek için forum.hibernate.org/viewtopic.php?p=2471522#p2471522'ye bakın .
Havuz

31

Bunu cb.createQuery () kullanarak (sonuç türü parametresi olmadan) sıraladım:

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();
    }
}

Umarım yardımcı olur :)


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

Diğer yanıtlar doğru, ancak çok basit olduğundan, eksiksizlik adına SELECT COUNT, karmaşık bir JPA Ölçüt sorgusu (birden çok birleştirme, getirme, koşul ile) .

Bu cevap biraz değiştirildi .

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);
    }
}

Umarım birinin zamanını kurtarır.

Çünkü IMHO JPA Criteria API sezgisel veya oldukça okunabilir değildir.


2
@specializt elbette mükemmel değil - örneğin yukarıdaki çözümde getirmelerde yinelemeli birleştirme eksik. Ama sırf bu yüzden düşüncelerimi paylaşmamam gerektiğini düşünüyor musun? IMHO bilgi paylaşımı, StackOverfow'un arkasındaki ana fikirdir.
G. Demecki

Veritabanlarında yineleme her zaman akla gelebilecek en kötü çözümdür ... bu yeni başlayanlar için bir hatadır.
specializt

@specializt recursion on databases? API seviyesinde özyinelemeden bahsediyordum. Bu kavramları karıştırmayın :-) JPA, tek bir sorguda birden çok birleştirme / getirme / toplama / takma ad vb. Yapmanızı sağlayan çok güçlü / karmaşık API ile birlikte gelir . Sayarken bununla başa çıkmalısın.
G. Demecki

1
Görünüşe göre JPA'nın nasıl çalıştığını henüz anlamadınız - kriterlerinizin büyük çoğunluğu, bunlar (son derece tuhaf) birleştirmeler de dahil olmak üzere uygun veritabanı sorgularıyla eşleştirilecek. SQL çıktısını etkinleştirin ve hatanızı gözlemleyin - "API katmanı" yoktur, JPA bir ABSTRACTION katmanıdır
uzmanlık

büyük olasılıkla, birçok basamaklı JOIN göreceksiniz - çünkü JPA henüz SQL işlevlerini otomatik olarak oluşturamaz; ama bu bazen değişecek ... muhtemelen JPA 3 ile, bu
uzmanlık

5

Kullandığınız JPA 2 uygulamasına bağlı olarak biraz karmaşıktır, bu EclipseLink 2.4.1 için çalışır, ancak Hibernate için geçerli değildir, burada EclipseLink için genel bir CriteriaQuery sayısı:

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();
  }

Geçen gün EclipseLink'ten Hazırda Bekletme'ye geçtim ve sayma işlevimi aşağıdaki şekilde değiştirmek zorunda kaldım, bu yüzden kullanmaktan çekinmeyin, bu çözülmesi zor bir sorun, sizin durumunuz için işe yaramayabilir, Hazırda Bekletme'den beri kullanılıyor 4.x, kökün hangisi olduğunu tahmin etmeye çalışmadığıma dikkat edin, bunun yerine onu sorgudan geçiriyorum, bu yüzden sorun çözüldü, tahmin etmeye çalışmak için çok fazla belirsiz köşe durumu var:

  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();
  }

ya sorgu birleşim (ler) içeriyorsa?
Dave

Sanırım tehlikeli olabilecek tek durum, sol birleşiminiz olduğunda ve seçilen kök ana varlık olmadığında. Aksi takdirde önemli değildir, çünkü seçilen varlık ne olursa olsun sayı aynı olacaktır. Sol birleştirme varlıklarına gelince, seçimdeki ilk varlığın referans olan olduğundan oldukça eminim, örneğin, katılım kurslarını bırakmış öğrencileriniz varsa, o zaman öğrenci seçmek doğal bir şey olmalıdır çünkü öğrencinin olmadığı dersler olabilir. kaydoldu.
Guido Medina

1
Orijinal sorgu groupBy sorgusuysa, sonuç her grup için bir sayı olacaktır. Bir Alt Sorguya CriteriaQuery yapabilirsek, alt sorguyu sayarsak, her durumda işe yarar. Bunu yapabilir miyiz?
Dave

Merhaba @Dave, sizinle aynı sonuca vardım, gerçek çözüm, sorguları alt sorgulara dönüştürebilmek, bu tüm durumlarda, hatta bir groupBy'den sonraki satırları saymak için bile işe yarayacak. Aslında, CriteriaQuery ve Subquery için farklı sınıfların veya paylaştıkları ortak arayüz olan AbstractQuery'nin bir seçme yöntemi tanımlamamasının nedenine dair bir neden bulamıyorum. Bu nedenle neredeyse her şeyi yeniden kullanmanın bir yolu yok. Satırları saymak için sorguya göre gruplandırılmış bir grubu yeniden kullanmak için temiz bir çözüm buldunuz mu?
Amanda Tarafa Mas

1

Ayrıca Projeksiyonları da kullanabilirsiniz:

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

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

1
Korkarım Projeksiyonlar API'si Hibernate'e özeldir ancak soru JPA 2 hakkında
sorulmaktadır

Yine de faydalı bir ek buluyorum, ama belki de bir yorum olmalıydı. Cevabınızı Hibernate'e özgü tam cevabı içerecek şekilde genişletebilir misiniz?
Benny Bottema

gersonZaragocin aynı fikirdedir, ancak yorumlarda kod blokları yoktur
Pavel Evstigneev

0

Spring Data Jpa ile şu yöntemi kullanabiliriz:

    /*
     * (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()));
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.