Add Hibernate 7 batches 2-6, batch 7, and batch 8: mapping styles, JPA annotations, natural IDs, @Immutable, stored procedures, in-memory test databases, JNDI mocking, proxies, associations, temporal mapping, named queries, HQL, Criteria API, EntityManager bootstrapping, Ehcache 3 L2 cache configuration, HikariCP connection pooling, Hibernate Validator CDI integration, aggregate functions, sorting, pagination, interceptors, and Hibernate Search 8 (Hibernate 7.4.5.Final + Spring Boot 4.1.1 + JDK 25)

This commit is contained in:
2026-09-20 06:06:42 +00:00
committed by Claude
commit 8568c0ce6c
330 changed files with 23668 additions and 0 deletions
@@ -0,0 +1,156 @@
package com.ankurm.hibernatedemo.aggregate;
import static org.assertj.core.api.Assertions.assertThat;
import com.ankurm.hibernatedemo.HibernateDemoApplication;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* Proves the article's aggregate-function claims against a real H2 database: what an aggregate
* query returns over an empty table, {@code select new} record construction under GROUP BY /
* HAVING, the Criteria API equivalent, and HQL's {@code row_number()} window function.
*
* <p>Docs: docs/21-aggregate-functions.md.
*/
@SpringBootTest(classes = HibernateDemoApplication.class)
class AggregateFunctionsTest {
@Autowired
private EntityManagerFactory emf;
@Test
void emptyResultSet_countIsZero_sumAndAvgAreNull_neitherThrows() {
// Scoped to a category that matches zero rows -- rather than relying on the whole table
// being empty, which would make this test's outcome depend on execution order against
// the other test methods in this class that share the same EntityManagerFactory/database.
String noSuchCategory = "no-such-category-zzz";
EntityManager em = emf.createEntityManager();
Long count = em.createQuery(
"select count(p) from Product p where p.category = :c", Long.class)
.setParameter("c", noSuchCategory)
.getSingleResult();
Double sum = em.createQuery(
"select sum(p.price) from Product p where p.category = :c", Double.class)
.setParameter("c", noSuchCategory)
.getSingleResult();
Double avg = em.createQuery(
"select avg(p.price) from Product p where p.category = :c", Double.class)
.setParameter("c", noSuchCategory)
.getSingleResult();
em.close();
System.out.println("RESULT[aggregate-empty-result-set]: over 0 matching rows -- "
+ "count(p)=" + count + " (never null) | sum(p.price)=" + sum + " | avg(p.price)="
+ avg + " -- getSingleResult() returned normally for all three, no "
+ "NoResultException, because SQL's aggregate functions over zero rows still "
+ "produce exactly one result row.");
assertThat(count).isZero();
assertThat(sum).isNull();
assertThat(avg).isNull();
}
@Test
void groupByHaving_selectNewRecord_producesTypedSummaries() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(new Product("Keyboards", "Compact 65%", 89.0, 40));
em.persist(new Product("Keyboards", "Full-size", 59.0, 15));
em.persist(new Product("Keyboards", "Ergonomic split", 149.0, 5));
em.persist(new Product("Monitors", "27in 4K", 399.0, 8));
em.getTransaction().commit();
// Scoped to this test's own two categories -- the entity manager factory's H2 database is
// shared across every test method in this class, so an unscoped query here would also
// pick up Cables/Mice from the other test methods and make this assertion order-dependent.
List<CategorySummary> summaries = em.createQuery(
"select new com.ankurm.hibernatedemo.aggregate.CategorySummary("
+ "p.category, count(p), avg(p.price)) "
+ "from Product p where p.category in ('Keyboards', 'Monitors') "
+ "group by p.category having count(p) > 1 "
+ "order by p.category",
CategorySummary.class).getResultList();
em.close();
String rendered = summaries.stream()
.map(s -> s.category() + "(count=" + s.productCount() + ", avg=" + s.averagePrice() + ")")
.reduce((a, b) -> a + ", " + b)
.orElse("<none>");
System.out.println("RESULT[aggregate-groupby-having-record]: HAVING count(p) > 1 kept only "
+ "categories with more than one product -- " + rendered
+ " -- Monitors (1 product) was correctly excluded by HAVING, not just by GROUP BY.");
assertThat(summaries).hasSize(1);
assertThat(summaries.get(0).category()).isEqualTo("Keyboards");
assertThat(summaries.get(0).productCount()).isEqualTo(3);
}
@Test
void criteriaApi_avgWithGroupBy_matchesHqlEquivalent() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(new Product("Cables", "USB-C 1m", 9.0, 200));
em.persist(new Product("Cables", "USB-C 2m", 13.0, 120));
em.getTransaction().commit();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Object[]> cq = cb.createQuery(Object[].class);
Root<Product> root = cq.from(Product.class);
cq.multiselect(root.get("category"), cb.avg(root.get("price")))
.where(cb.equal(root.get("category"), "Cables"))
.groupBy(root.get("category"));
TypedQuery<Object[]> query = em.createQuery(cq);
Object[] row = query.getSingleResult();
em.close();
System.out.println("RESULT[aggregate-criteria-avg]: Criteria API cb.avg(root.get(\"price\")) "
+ "for category='" + row[0] + "' -- average=" + row[1]
+ " -- same numeric result as the equivalent HQL avg(p.price), just built without a"
+ " string query.");
assertThat((Double) row[1]).isEqualTo(11.0);
}
@Test
void windowFunction_rowNumberOverPartitionByCategory() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(new Product("Mice", "Wireless A", 49.0, 30));
em.persist(new Product("Mice", "Wireless B", 79.0, 12));
em.persist(new Product("Mice", "Wired C", 19.0, 60));
em.getTransaction().commit();
List<Object[]> ranked = em.createQuery(
"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",
Object[].class).getResultList();
em.close();
StringBuilder rendered = new StringBuilder();
for (Object[] row : ranked) {
rendered.append(row[0]).append("=rank").append(row[2]).append(" ");
}
System.out.println("RESULT[aggregate-window-row-number]: row_number() over "
+ "(partition by category order by price desc) for the Mice category -- "
+ rendered.toString().trim()
+ " -- HQL's window-function support (the OVER clause), present since Hibernate "
+ "6.2 and still current in 7.4.5.Final, not a Hibernate-7-only feature.");
assertThat(ranked).hasSize(3);
assertThat(((Number) ranked.get(0)[2]).intValue()).isEqualTo(1);
assertThat(ranked.get(0)[0]).isEqualTo("Wireless B");
}
}