Di JPA 2, menggunakan CriteriaQuery, cara menghitung hasil

114

Saya agak baru mengenal JPA 2 dan itu API CriteriaBuilder / CriteriaQuery:

CriteriaQuery javadoc.dll

CriteriaQuery dalam tutorial Java EE 6

Saya ingin menghitung hasil dari CriteriaQuery tanpa benar-benar mengambilnya. Apakah itu mungkin, saya tidak menemukan metode seperti itu, satu-satunya cara adalah melakukan ini:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();

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

// initialize predicates here

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

Dan itu bukanlah cara yang tepat untuk melakukannya ...

Apakah ada solusinya?

Sean Patrick Floyd
sumber
Akan sangat berguna jika seseorang dapat membantu atau memasukkan jawaban di bawah ini. Bagaimana cara mencapai kueri hitungan berikut menggunakan API kriteria JPA? pilih hitungan (col1 berbeda, col2, col3) dari my_table;
Bhavesh
mencari jawaban di bawah ini tetapi alih-alih qb.count gunakan qb.distinctCount @Bhavesh
Tonino

Jawaban:

220

Jenis kueri MyEntityakan kembali MyEntity. Anda menginginkan kueri untuk file 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();

Jelas Anda ingin membangun ekspresi Anda dengan batasan dan pengelompokan apa pun, dll. Yang Anda lewati dalam contoh.

Affe
sumber
3
Itulah yang kupikirkan sendiri, terima kasih. Tetapi itu berarti saya tidak dapat menggunakan contoh kueri yang sama untuk menanyakan jumlah hasil dan hasil aktual yang saya tahu analog dengan SQL, tetapi yang akan membuat API ini lebih mirip OOP. Yah, setidaknya aku bisa menggunakan kembali beberapa predikat, kurasa.
Sean Patrick Floyd
6
@Barett jika itu jumlah yang agak besar, Anda mungkin tidak ingin memuat daftar ratusan atau ribuan entitas ke dalam memori hanya untuk mengetahui berapa banyak!
Affe
@Barett ini digunakan jika banyak penomoran halaman. Karenanya kebutuhan untuk jumlah total dan hanya sebagian dari baris yang sebenarnya.
gkephorus
2
Ingatlah bahwa qb.countini sudah selesai atas Root<MyEntity>kueri Anda ( Root<MyEntity>myEntity = cq.from (MyEntity.class)) dan ini sering kali sudah ada dalam kode pilih normal Anda dan ketika Anda lupa Anda berakhir dengan join to self.
gkephorus
2
Untuk menggunakan kembali kriteria yang sama untuk pengambilan objek dan hitungan, Anda mungkin perlu menggunakan alias di root, lihat forum.hibernate.org/viewtopic.php?p=2471522#p2471522 sebagai contoh.
Kolam renang
31

Saya telah memilah ini menggunakan cb.createQuery () (tanpa parameter tipe hasil):

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

Semoga membantu :)

reyiyo
sumber
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();
axtavt
sumber
12

Seperti orang lain jawaban benar, tapi terlalu sederhana, jadi untuk kelengkapan aku presentasi di bawah ini potongan kode untuk melakukan SELECT COUNTpada canggih permintaan Kriteria JPA (dengan beberapa bergabung, fetch, kondisi).

Ini sedikit mengubah jawaban ini .

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

Semoga menghemat waktu seseorang.

Karena IMHO JPA Criteria API tidak intuitif atau cukup mudah dibaca.

G. Demecki
sumber
2
@specializt tentu saja itu tidak sempurna - misalnya solusi di atas masih kehilangan gabungan rekursif saat pengambilan. Tetapi apakah Anda berpikir bahwa hanya karena ini saya tidak boleh berbagi pemikiran saya? Berbagi pengetahuan IMHO adalah ide utama di balik StackOverfow.
G. Demecki
Rekursi pada database selalu merupakan solusi terburuk yang bisa dibayangkan ... itu adalah kesalahan pemula.
Spesialisasi
@specialt recursion on databases? Saya berbicara tentang rekursi pada level API. Jangan bingung dengan konsep-konsep ini :-) JPA hadir dengan API yang sangat kuat / kompleks yang memungkinkan Anda melakukan banyak penggabungan / pengambilan / agregasi / alias dll dalam satu kueri. Anda harus menghadapinya sambil menghitung.
G. Demecki
1
Tampaknya Anda belum memahami cara kerja JPA - sebagian besar kriteria Anda akan dipetakan ke kueri database yang sesuai, termasuk gabungan (sangat aneh) ini. Aktifkan keluaran SQL dan amati kesalahan Anda - tidak ada "lapisan API", JPA adalah lapisan ABSTRAKSI
spesialisasi
kemungkinan besar, Anda akan melihat banyak JOIN bertingkat - karena JPA belum dapat membuat fungsi SQL secara otomatis; tetapi itu akan berubah sewaktu-waktu ... mungkin dengan JPA 3, saya ingat diskusi tentang hal-hal ini
spesialisasi
5

Agak rumit, tergantung pada implementasi JPA 2 yang Anda gunakan, yang ini berfungsi untuk EclipseLink 2.4.1, tetapi tidak untuk Hibernate, berikut jumlah CriteriaQuery generik untuk 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();
  }

Beberapa hari yang lalu saya bermigrasi dari EclipseLink ke Hibernate dan harus mengubah fungsi penghitungan saya ke yang berikut, jadi silakan gunakan karena ini adalah masalah yang sulit dipecahkan, mungkin tidak berfungsi untuk kasus Anda, itu telah digunakan sejak Hibernate 4.x, perhatikan bahwa saya tidak mencoba menebak mana yang merupakan root, sebaliknya saya meneruskannya dari kueri sehingga masalah terpecahkan, terlalu banyak kasus sudut yang ambigu untuk ditebak:

  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();
  }
Guido Medina
sumber
bagaimana jika kueri telah bergabung?
Dave
Saya pikir satu-satunya kasus yang akan berbahaya adalah ketika Anda memiliki gabungan kiri dan root yang dipilih bukan entitas utama. Jika tidak, tidak masalah, karena jumlahnya akan sama terlepas dari entitas yang dipilih. Sedangkan untuk entitas left join, saya cukup yakin entitas pertama yang di pilih adalah yang referensinya, misalnya jika ada siswa yang keluar dari mata kuliah maka memilih mahasiswa harusnya hal yang wajar karena bisa jadi ada mata kuliah yang mahasiswa tersebut tidak. terdaftar.
Guido Medina
1
Jika kueri asli adalah kueri groupBy, hasilnya adalah satu hitungan untuk setiap grup. Jika kita bisa membuat CriteriaQuery menjadi SubQuery, lalu hitung subkueri, itu akan berfungsi di semua kasus. Bisakah kita melakukan itu?
Dave
Hai @ Dave, Saya sampai pada kesimpulan yang sama dengan Anda, solusi sebenarnya adalah dapat mengubah kueri menjadi subkueri, yang akan berfungsi untuk semua kasus, bahkan untuk menghitung baris setelah groupBy. Sebenarnya saya tidak bisa menemukan alasan mengapa klas berbeda untuk CriteriaQuery dan Subquery, atau setidaknya fakta bahwa antarmuka umum yang mereka bagikan, AbstractQuery, tidak mendefinisikan metode pemilihan. Karena itu, hampir tidak ada cara untuk menggunakan kembali apa pun. Sudahkah Anda menemukan solusi bersih untuk menggunakan kembali grup yang dikelompokkan menurut kueri untuk menghitung baris?
Amanda Tarafa Mas
1

Anda juga dapat menggunakan Proyeksi:

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

Long totalRows = (Long) criteria.list().get(0);
Pavel Evstigneev
sumber
1
Saya khawatir Projections API spesifik Hibernate tetapi pertanyaannya adalah menanyakan tentang JPA 2.
gersonZaragocin
Tetap saja, saya menganggapnya sebagai tambahan yang berguna, tetapi mungkin itu seharusnya menjadi komentar. Dapatkah Anda memperluas jawaban Anda dengan menyertakan jawaban lengkap khusus Hibernasi?
Benny Bottema
gersonZaragocin setuju, tetapi tidak ada blok kode dalam komentar
Pavel Evstigneev
0

Dengan Spring Data Jpa, kita dapat menggunakan metode ini:

    /*
     * (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()));
    }
kafka
sumber