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=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하여 정교한 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. Demecki 2015 년

데이터베이스에 대한 재귀는 항상 상상할 수있는 최악의 솔루션입니다. 그것은 초보자의 실수입니다.
— specializt

@specializt recursion on databases? API 수준에서 재귀에 대해 이야기했습니다. 이러한 개념을 혼동하지 마십시오. JPA에는 단일 쿼리에서 여러 조인 / 인출 / 집계 / 별칭 등을 수행 할 수 있는 매우 강력하고 복잡한 API가 제공됩니다. 세는 동안 처리해야합니다.
— G. Demecki

1
분명히 JPA가 어떻게 작동하는지 아직 이해하지 못했을 것입니다. 대부분의 기준은 이러한 (매우 이상한) 조인을 포함하여 적절한 데이터베이스 쿼리에 매핑됩니다. SQL 출력을 활성화하고 실수를 관찰하십시오. "API 계층"이없고 JPA는 ABSTRACTION 계층입니다
— specializt

JPA는 아직 SQL 함수를 자동으로 생성 할 수 없기 때문에 대부분의 계단식 JOIN을 볼 수 있습니다. 그러나 그것은 언젠가 바뀔 것입니다 ... 아마도 JPA 3에서는 이러한 것들에 대한 논의를 기억합니다
— specializt

5

사용하는 JPA 2 구현에 따라 약간 까다 롭습니다. 이것은 EclipseLink 2.4.1에서 작동하지만 Hibernate에서는 작동하지 않습니다. 여기 EclipseLink에 대한 일반적인 CriteriaQuery 개수는 다음과 같습니다.

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

위험한 유일한 경우는 왼쪽 조인이 있고 선택한 루트가 주 엔티티가 아닌 경우입니다. 그렇지 않으면 선택한 엔티티에 관계없이 개수가 동일하기 때문에 중요하지 않습니다. 왼쪽 조인 엔터티의 경우 선택 항목의 첫 번째 엔터티가 참조 항목이라고 확신합니다. 예를 들어 학생이 참가 코스를 떠난 경우 학생을 선택하는 것은 당연한 일입니다. 등록.
— Guido Medina

1
원래 쿼리가 groupBy 쿼리 인 경우 결과는 각 그룹에 대해 하나의 개수가됩니다. CriteriaQuery를 하위 쿼리로 만들고 하위 쿼리를 세면 모든 경우에 작동합니다. 우리가 할 수 있습니까?
— Dave

안녕하세요 @Dave, 저는 여러분과 동일한 결론에 도달했습니다. 실제 솔루션은 쿼리를 하위 쿼리로 변환 할 수있는 것입니다. 이는 groupBy 이후 행을 계산하는 경우에도 모든 경우에 작동합니다. 실제로 CriteriaQuery 및 Subquery에 대한 다른 클래스가 왜 다른지 또는 그들이 공유하는 공통 인터페이스 인 AbstractQuery가 선택 메서드를 정의하지 않는다는 사실에 대한 이유를 찾을 수없는 것 같습니다. 이 때문에 거의 모든 것을 재사용 할 수있는 방법이 없습니다. 행 계산을 위해 그룹화 된 쿼리를 재사용하는 깨끗한 솔루션을 찾았습니까?
— Amanda Tarafa Mas 2015

1

투영을 사용할 수도 있습니다.

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

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

1
나는 두려워 계획의 API가 최대 절전 모드 특정하지만 문제는 JPA 2에 대한 질문입니다입니다
— gersonZaragocin

그래도 유용한 추가 기능이라고 생각하지만 아마도 주석이었을 것입니다. 전체 Hibernate 관련 답변을 포함하도록 답변을 확장 할 수 있습니까?
— Benny Bottema 2018

gersonZaragocin는 동의하지만 코드 블록 의견이 없습니다
— 파벨 Evstigneev

0

SpringData 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.