4.2 KiB
21 — Aggregate functions
← Previous: 20 — Hibernate Validator CDI integration | Next: 22 — Sorting → | Back to README →
Backs the rewrite of ankurm.com post 4888 (aggregate functions).
What an aggregate query returns over zero rows
count, sum, avg, min, max all still return exactly one result row when the underlying
table (or the filtered subset) has zero matching rows — SQL's aggregate functions are defined
over the whole result set, not per-row, so there is always one row to return. count(*) on an
empty set is 0; every other aggregate is NULL. Neither case throws
NoResultException — getSingleResult() on an aggregate query only throws that when the query
itself is malformed, never because the aggregate happened to be computed over nothing.
Long count = em.createQuery("select count(p) from Product p where p.category = :c", Long.class)
.setParameter("c", "no-such-category-zzz").getSingleResult();
Double sum = em.createQuery("select sum(p.price) from Product p where p.category = :c", Double.class)
.setParameter("c", "no-such-category-zzz").getSingleResult();
AggregateFunctionsTest.java
— captured output
select new with a Java record
Hibernate 7 accepts a Java record's canonical constructor as a select new target exactly like
it accepts a class constructor — no special annotation, no adapter, just a record whose
constructor parameter types and order match the query's projection:
public record CategorySummary(String category, long productCount, double averagePrice) {}
select new com.ankurm.hibernatedemo.aggregate.CategorySummary(p.category, count(p), avg(p.price))
from Product p group by p.category having count(p) > 1 order by p.category
HAVING count(p) > 1 filters on the aggregated group, after GROUP BY has collapsed the rows
— a category with exactly one product is excluded by HAVING, not merely left ungrouped.
Test —
output
The Criteria API equivalent
CriteriaBuilder.avg(...), .sum(...), .count(...) and friends build the same aggregate SQL
without a string query:
cq.multiselect(root.get("category"), cb.avg(root.get("price")))
.where(cb.equal(root.get("category"), "Cables"))
.groupBy(root.get("category"));
Window functions — not a Hibernate 7 feature
HQL's OVER clause (row_number(), rank(), lead(), lag(), and the ordered-set / inverse
distribution aggregates) is real and works exactly as SQL's window functions do. It is not
new in Hibernate 7, though it is sometimes described that way: javap against
hibernate-core-7.4.5.Final.jar's CommonFunctionFactory shows a windowFunctions()
registration method that has existed since Hibernate 6.2, when HQL's window-function support was
first added. Hibernate 7 continues to support it; it did not introduce it.
select p.name, p.price, row_number() over (partition by p.category order by p.price desc)
from Product p where p.category = 'Mice' order by p.price desc
Going deeper
getSingleResult()vsgetResultList()on an aggregate query: prefergetSingleResult()only when the query has noGROUP BY— a grouped aggregate can legitimately return many rows.StatelessSessionskips the persistence context entirely for a pure read-and-aggregate workload, avoiding the memory overhead of first-level cache entries the aggregate result itself never needs.- Hibernate ORM Criteria API documentation