Trong JPA 2, sử dụng CriteriaQuery, cách đếm kết quả


114

Tôi chưa quen với JPA 2 và đó là API CriteriaBuilder / CriteriaQuery:

CriteriaQuery javadoc

CriteriaQuery trong hướng dẫn Java EE 6

Tôi muốn đếm kết quả của một CriteriaQuery mà không thực sự truy xuất chúng. Có thể không, tôi đã không tìm thấy bất kỳ phương pháp nào như vậy, cách duy nhất sẽ là làm điều này:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();

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

// initialize predicates here

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

Và đó không thể là cách thích hợp để làm điều đó ...

Có một giải pháp?


Sẽ rất hữu ích nếu ai đó có thể giúp đỡ hoặc đưa vào câu trả lời bên dưới. Làm cách nào để đạt được truy vấn đếm sau bằng API tiêu chí JPA? chọn số lượng (col1, col2, col3 riêng biệt) từ my_table;
Bhavesh

tìm câu trả lời bên dưới nhưng thay vì qb.count, hãy sử dụng qb.distinctCount @Bhavesh
Tonino

Câu trả lời:


220

Một truy vấn kiểu MyEntitysẽ trả về MyEntity. Bạn muốn một truy vấn cho một 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();

Rõ ràng là bạn sẽ muốn xây dựng biểu thức của mình với bất kỳ hạn chế và nhóm nào, v.v. bạn đã bỏ qua trong ví dụ.


3
Đó là những gì tôi đã tự nhận ra, cảm ơn. Nhưng điều đó có nghĩa là tôi không thể sử dụng cùng một phiên bản truy vấn để truy vấn số lượng kết quả và kết quả thực tế mà tôi biết là tương tự như SQL, nhưng sẽ làm cho API này giống OOP hơn rất nhiều. Tôi đoán là ít nhất tôi có thể sử dụng lại một số vị từ.
Sean Patrick Floyd

6
@Barett nếu đó là một số lượng khá lớn, bạn có thể không muốn tải một danh sách hàng trăm hoặc hàng nghìn thực thể vào bộ nhớ chỉ để tìm xem có bao nhiêu thực thể!
Affe 10/12/12

@Barett này được sử dụng trong trường hợp phân trang nhiều. Do đó cần có một số tổng và chỉ một tập hợp con của các hàng thực tế.
gkephorus

2
Lưu ý với bạn rằng điều qb.countnày được thực hiện qua Root<MyEntity>truy vấn của bạn ( Root<MyEntity>myEntity = cq.from (MyEntity.class)) và điều này thường đã có trong mã chọn thông thường của bạn và khi bạn quên, bạn sẽ kết thúc với một phép nối.
gkephorus

2
Để sử dụng lại các tiêu chí tương tự cho việc truy xuất đối tượng và số lượng, bạn có thể cần sử dụng bí danh trên gốc, hãy xem forum.hibernate.org/viewtopic.php?p=2471522#p2471522 để làm ví dụ.
Hồ bơi

31

Tôi đã sắp xếp điều này bằng cách sử dụng cb.createQuery () (không có tham số loại kết quả):

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

Hy vọng nó giúp :)


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

Như những câu trả lời khác là đúng, nhưng quá đơn giản, vì vậy để hoàn chỉnh, tôi sẽ trình bày đoạn mã dưới đây để thực hiện SELECT COUNTtrên một truy vấn Tiêu chí JPA phức tạp (với nhiều phép nối, tìm nạp, điều kiện).

Nó được sửa đổi một chút câu trả lời này .

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

Hy vọng nó tiết kiệm thời gian của ai đó.

Bởi vì IMHO JPA Criteria API không trực quan và cũng không dễ đọc.


2
@specializt tất nhiên nó không phải là hoàn hảo - ví dụ như giải pháp trên vẫn còn thiếu một phép nối đệ quy khi tìm nạp. Nhưng bạn có nghĩ rằng chỉ vì điều này mà tôi không nên chia sẻ suy nghĩ của mình không? IMHO chia sẻ kiến ​​thức là ý tưởng chính đằng sau StackOverfow.
G. Demecki

Đệ quy trên cơ sở dữ liệu luôn là giải pháp tồi tệ nhất có thể tưởng tượng được ... đó là một sai lầm của người mới bắt đầu.
specializt

@specializt recursion on databases? Tôi đã nói về đệ quy ở cấp API. Đừng nhầm lẫn các khái niệm này :-) JPA đi kèm với API rất mạnh mẽ / phức tạp cho phép bạn thực hiện nhiều phép nối / tìm nạp / tổng hợp / bí danh, v.v. trong một truy vấn duy nhất. Bạn phải đối phó với nó trong khi đếm.
G. Demecki 14/09/15

1
Rõ ràng bạn vẫn chưa hiểu cách hoạt động của JPA - phần lớn các tiêu chí của bạn sẽ được ánh xạ vào các truy vấn cơ sở dữ liệu thích hợp, bao gồm cả các phép nối (cực kỳ lạ) này. Kích hoạt đầu ra SQL và quan sát lỗi của bạn - không có "lớp API", JPA là lớp TÓM TẮT
chuyên trang

rất có thể, bạn sẽ thấy nhiều JOIN theo tầng - bởi vì JPA chưa thể tự động tạo các hàm SQL; nhưng điều đó sẽ thay đổi một lúc nào đó ... có lẽ với JPA 3, tôi nhớ lại các cuộc thảo luận về những điều này
chuyên trang

5

Nó hơi phức tạp, tùy thuộc vào việc triển khai JPA 2 mà bạn sử dụng, cái này hoạt động cho EclipseLink 2.4.1, nhưng không cho Hibernate, đây là số lượng CriteriaQuery chung cho 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();
  }

Hôm nọ, tôi đã di chuyển từ EclipseLink sang Hibernate và phải thay đổi hàm đếm của mình thành như sau, vì vậy hãy thoải mái sử dụng vì đây là một vấn đề khó giải quyết, nó có thể không hoạt động với trường hợp của bạn, nó đã được sử dụng từ Hibernate 4.x, lưu ý rằng tôi không cố gắng đoán cái nào là gốc, thay vào đó tôi chuyển nó từ truy vấn để vấn đề được giải quyết, quá nhiều trường hợp góc không rõ ràng để cố gắng đoán:

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

điều gì sẽ xảy ra nếu truy vấn có (các) tham gia?
Dave

Tôi nghĩ rằng trường hợp duy nhất sẽ nguy hiểm là khi bạn có một phép nối bên trái và gốc được chọn không phải là thực thể chính. Nếu không thì không thành vấn đề, vì số lượng sẽ giống nhau bất kể thực thể được chọn. Đối với các thực thể tham gia bên trái, tôi khá chắc chắn thực thể đầu tiên trong lựa chọn là đối tượng tham chiếu, ví dụ: nếu bạn có sinh viên rời khỏi các khóa học tham gia thì việc chọn sinh viên nên là điều tự nhiên vì có thể có các khóa học mà sinh viên đó không đã ghi danh.
Guido Medina

1
Nếu truy vấn ban đầu là groupBy query, kết quả sẽ là một số cho mỗi nhóm. Nếu chúng ta có thể tạo một CriteriaQuery thành một SubQuery, sau đó đếm truy vấn con, nó sẽ hoạt động trong mọi trường hợp. Chúng ta có thể làm điều đó?
Dave

Xin chào @Dave, tôi đã đi đến kết luận giống như bạn, giải pháp thực sự là có thể chuyển đổi các truy vấn thành truy vấn con, điều này sẽ hoạt động cho mọi trường hợp, ngay cả khi đếm các hàng sau một nhómBy. Trên thực tế, tôi dường như không thể tìm ra lý do tại sao các cơ sở khác nhau cho CriteriaQuery và Subquery, hoặc thực tế là giao diện chung mà chúng chia sẻ, AbstractQuery, không xác định một phương thức chọn. Bởi vì điều này, không có cách nào để sử dụng lại hầu hết mọi thứ. Bạn đã tìm thấy một giải pháp rõ ràng để sử dụng lại một truy vấn bị thiếu bởi truy vấn để đếm các hàng chưa?
Amanda Tarafa Mas

1

Bạn cũng có thể sử dụng Phép chiếu:

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

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

1
Tôi e rằng API dự báo là Hibernate cụ thể nhưng câu hỏi đang hỏi về JPA 2.
gersonZaragocin

Tuy nhiên, tôi thấy nó là một bổ sung hữu ích, nhưng có lẽ nó nên là một nhận xét. Bạn có thể mở rộng câu trả lời của mình để bao gồm câu trả lời đầy đủ dành riêng cho Hibernate không?
Benny Bottema

gersonZaragocin đồng ý, nhưng không có khối mã trong ý kiến
Pavel Evstigneev

0

Với Spring Data Jpa, chúng ta có thể sử dụng phương pháp này:

    /*
     * (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()));
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.