Add Hibernate 7 batches 2-5: mapping styles, JPA annotations, natural IDs, @Immutable, stored procedures, in-memory test databases, JNDI mocking, proxies, associations, temporal mapping and named queries (Hibernate 7.4.5.Final + Spring Boot 4.1.1 + JDK 25)

This commit is contained in:
2026-09-06 10:30:19 +05:30
commit 5b97a85fe7
229 changed files with 17349 additions and 0 deletions
@@ -0,0 +1,159 @@
package com.ankurm.hibernatedemo.namedquery;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.NoResultException;
import jakarta.persistence.Query;
import java.util.List;
import org.hibernate.SessionFactory;
import org.hibernate.stat.Statistics;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
/**
* Backs ankurm.com post 4877, docs/14-named-queries.md. Covers: jakarta @NamedQuery execution,
* org.hibernate.annotations.NamedQuery's cacheable extra actually taking effect,
* @NamedNativeQuery + @SqlResultSetMapping DTO projection, record constructor-result (JPA 3.2),
* and getSingleResultOrNull() vs getSingleResult().
*/
@SpringBootTest(properties = {
"spring.jpa.properties.hibernate.cache.use_second_level_cache=true",
"spring.jpa.properties.hibernate.cache.use_query_cache=true",
"spring.jpa.properties.hibernate.cache.region.factory_class=jcache",
"spring.jpa.properties.hibernate.javax.cache.provider=org.ehcache.jsr107.EhcacheCachingProvider"
})
class NamedQueryExecutionTest {
private static final Logger DEMO = LoggerFactory.getLogger("DEMO");
@Autowired
private EntityManagerFactory emf;
@BeforeEach
void cleanTables() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.createQuery("DELETE FROM NqEmployee").executeUpdate();
em.createQuery("DELETE FROM HibernateExtraEmployee").executeUpdate();
em.getTransaction().commit();
em.close();
}
private Statistics stats() {
return emf.unwrap(SessionFactory.class).getStatistics();
}
@Test
void jakartaNamedQuery_executesAndReturnsExpectedRows() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(new NqEmployee("Ankur", "ACTIVE"));
em.persist(new NqEmployee("Priya", "ACTIVE"));
em.getTransaction().commit();
List<NqEmployee> found = em.createNamedQuery("Employee.findByName", NqEmployee.class)
.setParameter("name", "Ankur")
.getResultList();
DEMO.info("Employee.findByName(Ankur): {} rows", found.size());
assertThat(found).hasSize(1);
em.close();
}
@Test
void hibernateNamedQuery_cacheableExtra_actuallyMarksTheQueryCacheable() {
// org.hibernate.annotations.NamedQuery offers `cacheable` -- jakarta.persistence.NamedQuery
// does not. Verify it actually takes effect using Statistics' query-cache put count.
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(new HibernateExtraEmployee("A"));
em.persist(new HibernateExtraEmployee("B"));
em.getTransaction().commit();
em.close();
stats().clear();
EntityManager em1 = emf.createEntityManager();
List<HibernateExtraEmployee> first = em1.createNamedQuery("HibernateExtraEmployee.cacheableFindAll", HibernateExtraEmployee.class)
.getResultList();
long putsAfterFirst = stats().getQueryCachePutCount();
em1.close();
EntityManager em2 = emf.createEntityManager();
List<HibernateExtraEmployee> second = em2.createNamedQuery("HibernateExtraEmployee.cacheableFindAll", HibernateExtraEmployee.class)
.getResultList();
long hitsAfterSecond = stats().getQueryCacheHitCount();
em2.close();
DEMO.info("cacheable=true named query: puts after 1st run = {}, cache hits after 2nd run = {}", putsAfterFirst, hitsAfterSecond);
assertThat(first).hasSize(2);
assertThat(second).hasSize(2);
assertThat(putsAfterFirst).as("cacheable=true on org.hibernate.annotations.NamedQuery must populate the query cache").isGreaterThan(0);
assertThat(hitsAfterSecond).as("second identical call must hit the query cache").isGreaterThan(0);
}
@Test
void namedNativeQuery_withSqlResultSetMapping_projectsToDto() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(new NqEmployee("Native1", "ACTIVE"));
em.persist(new NqEmployee("Native2", "INACTIVE"));
em.getTransaction().commit();
@SuppressWarnings("unchecked")
List<EmployeeDto> dtos = (List<EmployeeDto>) (List<?>) em.createNamedQuery("Employee.byNativeDto")
.setParameter("status", "ACTIVE")
.getResultList();
DEMO.info("Employee.byNativeDto(ACTIVE): {}", dtos);
assertThat(dtos).hasSize(1);
assertThat(dtos.get(0).getFirstName()).isEqualTo("Native1");
em.close();
}
@Test
void jpql31ConstructorExpression_worksWithARecordAsTheTarget() {
// Jakarta Persistence 3.2 alternative to @SqlResultSetMapping: a plain JPQL constructor
// expression targeting a java `record`.
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(new NqEmployee("RecordTest", "ACTIVE"));
em.getTransaction().commit();
List<EmployeeRecordDto> records = em.createQuery(
"SELECT NEW com.ankurm.hibernatedemo.namedquery.EmployeeRecordDto(e.id, e.firstName) FROM NqEmployee e WHERE e.firstName = :name",
EmployeeRecordDto.class)
.setParameter("name", "RecordTest")
.getResultList();
DEMO.info("JPQL constructor expression into a record: {}", records);
assertThat(records).hasSize(1);
assertThat(records.get(0).firstName()).isEqualTo("RecordTest");
em.close();
}
@Test
void getSingleResultOrNull_vs_getSingleResult_onNoRows() {
EntityManager em = emf.createEntityManager();
Object viaOrNull = em.createQuery("SELECT e FROM NqEmployee e WHERE e.firstName = :n")
.setParameter("n", "NoSuchPersonAtAll")
.getSingleResultOrNull();
DEMO.info("getSingleResultOrNull() on zero rows returned: {}", viaOrNull);
assertThat(viaOrNull).isNull();
Query q = em.createQuery("SELECT e FROM NqEmployee e WHERE e.firstName = :n")
.setParameter("n", "NoSuchPersonAtAll");
NoResultException ex = assertThrows(NoResultException.class, q::getSingleResult);
DEMO.info("getSingleResult() on zero rows threw: {}: {}", ex.getClass().getName(), ex.getMessage());
em.close();
}
}