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.