160 lines
6.7 KiB
Java
Executable File
160 lines
6.7 KiB
Java
Executable File
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();
|
|
}
|
|
}
|