JPA 2では、CriteriaQueryを使用して、結果をカウントする方法


114

私はJPA 2の初心者で、CriteriaBuilder / CriteriaQuery APIです。

CriteriaQuery javadoc

CriteriaQuery Java EE 6チュートリアル

CriteriaQueryの結果を実際に取得せずにカウントしたいと思います。それは可能ですか、そのような方法は見つかりませんでした。これを行う唯一の方法は次のとおりです。

CriteriaBuilder cb = entityManager.getCriteriaBuilder();

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

// initialize predicates here

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

そしてそれはそれを行う適切な方法ではありません...

解決策はありますか?


誰かが助けたり、以下の回答に含めたりできれば非常に役立ちます。JPA基準APIを使用して次のカウントクエリを実現するにはどうすればよいですか?my_tableからcount(distinct col1、col2、col3)を選択します。
Bhavesh

以下の答えを見て、qb.countの代わりにqb.distinctCount @Bhaveshを使用する
Tonino

回答:


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
それは私が自分で考えたものです、ありがとう。しかし、これは、同じクエリインスタンスを使用して結果の数をクエリすることはできないことを意味します。実際の結果はSQLに類似していることがわかっていますが、これにより、このAPIはOOPのようになります。まあ、少なくとも私はいくつかの述語を再利用できると思います。
Sean Patrick Floyd、

6
@Barettがかなり大きい場合、エンティティの数を確認するためだけに、数百または数千のエンティティのリストをメモリにロードしたくないでしょう。
Affe

@Barettこれはページネーションの際によく使われます。したがって、実際の行の総数とサブセットのみが必要です。
gkephorus 2013年

2
ことを断っておくqb.count上で行われますRoot<MyEntity>(クエリのRoot<MyEntity>myEntity = cq.from(MyEntity.class))、これはあなたの通常の選択コードで、すでに多くの場合、あなたが忘れてしまったときは、自己に参加して終わります。
gkephorus 2013年

2
オブジェクトの取得とカウントに同じ基準を再利用するには、ルートでエイリアスを使用する必要がある場合があります。例については、forum.hibernate.org / viewtopic.php?p = 2471522p2471522を参照してください。
プール

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に、洗練されたコードで実行するコードスニペットを以下に示します。 JPA基準クエリ(複数の結合、フェッチ、条件を使用)で。

この回答は少し変更されています

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 Criteria APIは直感的でも読みやすいものでもないからです。


2
@specializtはもちろん完璧ではありません。たとえば、上記の解決策では、フェッチ時に再帰的な結合が欠落しています。しかし、それだけで私は自分の考えを共有するべきではないと思いますか?IMHOの知識の共有は、StackOverfowの背後にある主なアイデアです。
G.デメッキ

データベースの再帰は、常に考えられる最悪の解決策です...それは初心者の間違いです。
Specializ、

@specializt recursion on databases?APIレベルでの再帰について話していました。これらの概念を混同しないでください:-) JPAには、単一のクエリで複数の結合、フェッチ、集計、エイリアスなどを実行できる非常に強力な複雑な APIが付属しています。数えながら、あなたはそれに対処しなければなりません。
G. Demecki

1
どうやらJPAの仕組みをまだ理解していないようです。基準の大部分は、これらの(非常に奇妙な)結合を含む適切なデータベースクエリにマッピングされます。SQL出力をアクティブにして、間違いを確認します-「APIレイヤー」はありません。JPAは抽象レイヤーです
特化した

ほとんどの場合、カスケードされたJOINが多数表示されます。JPAはまだSQL関数を自動的に作成できないためです。しかし、それはいつか変わるでしょう...おそらくJPA 3で、私はこれらのことについての議論を思い出します
専門家

5

これは少しトリッキーです。使用するJPA 2実装によっては、これはEclipseLink 2.4.1では機能しますが、Hibernateでは機能しません。

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

先日、EclipseLinkからHibernateに移行し、カウント関数を次のように変更する必要があったので、これは解決するのが難しい問題であり、Hibernateから使用されているため、問題を解決できない可能性があるため、どちらでも使用してください。 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();
  }

クエリに結合がある場合はどうなりますか?
Dave

危険なのは、左結合があり、選択したルートがメインエンティティではない場合だけです。それ以外の場合、カウントは選択したエンティティに関係なく同じになるため、問題ではありません。左結合エンティティについては、選択の最初のエンティティが参照エンティティであると確信しています。たとえば、学生が結合コースを残している場合、学生がいないコースが存在する可能性があるため、学生を選ぶことは自然なことです。在籍。
グイドメディナ2014年

1
元のクエリがgroupByクエリの場合、結果はグループごとに1つのカウントになります。CriteriaQueryをサブクエリにしてサブクエリを数えることができれば、すべてのケースで機能します。できますか?
デイブ

こんにちは@Dave、私はあなたと同じ結論に達しました。本当の解決策は、クエリをサブクエリに変換でき、groupByの後に行をカウントする場合でも、すべてのケースで機能することです。実際、CriteriaQueryとSubqueryのクラスが異なる理由、または少なくともそれらが共有する共通インターフェイスであるAbstractQueryがselectメソッドを定義していないという理由を見つけることができないようです。このため、ほとんど何でも再利用する方法はありません。行をカウントするためにgrouped byクエリを再利用するためのクリーンなソリューションを見つけましたか?
アマンダタラファMas

1

プロジェクションを使用することもできます。

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

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

1
私は恐れて突起部APIはHibernateの特定のですが、質問はJPA 2について質問されています
gersonZaragocin

それでも、私はそれが有用な追加だと思います、しかし、おそらくそれはコメントであったはずです。回答を拡張して、Hibernate固有の完全な回答を含めることができますか?
Benny Bottema

gersonZaragocinは同意しますが、コメントにコードブロックはありません
Pavel Evstigneev

0

Spring Data Jpaでは、このメソッドを使用できます。

    /*
     * (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()));
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.