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:
+159
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user