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,83 @@
package com.ankurm.hibernatedemo.datetime;
import static org.assertj.core.api.Assertions.assertThat;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
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;
/**
* Backs ankurm.com post 4876, docs/13-date-and-time-mapping.md chapter "Basic temporal types round trip".
* The generated DDL for {@code temporal_types} (visible in the captured log because
* {@code show_sql=true}) is the primary artifact here.
*/
@SpringBootTest
class BasicTemporalTypesTest {
private static final Logger DEMO = LoggerFactory.getLogger("DEMO");
@Autowired
private EntityManagerFactory emf;
@Test
void roundTripEveryBasicTemporalType() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
TemporalTypesEntity e = new TemporalTypesEntity();
e.setLocalDate(LocalDate.of(2026, 3, 15));
e.setLocalDateTime(LocalDateTime.of(2026, 3, 15, 10, 30, 45));
e.setLocalTime(LocalTime.of(10, 30, 45));
e.setInstant(Instant.parse("2026-03-15T10:30:45Z"));
e.setOffsetDateTime(OffsetDateTime.of(2026, 3, 15, 10, 30, 45, 0, ZoneOffset.ofHoursMinutes(5, 30)));
e.setZonedDateTime(ZonedDateTime.of(2026, 3, 15, 10, 30, 45, 0, java.time.ZoneId.of("Europe/Paris")));
Calendar cal = new GregorianCalendar(2026, Calendar.MARCH, 15, 10, 30, 45);
Date legacyDate = cal.getTime();
e.setLegacyDateAsDate(legacyDate);
e.setLegacyDateAsTimestamp(legacyDate);
e.setLegacyDateNoTemporal(legacyDate);
e.setLegacyCalendar(cal);
em.persist(e);
em.getTransaction().commit();
Long id = e.getId();
em.close();
EntityManager em2 = emf.createEntityManager();
TemporalTypesEntity loaded = em2.find(TemporalTypesEntity.class, id);
DEMO.info("ROUNDTRIP localDate = {}", loaded.getLocalDate());
DEMO.info("ROUNDTRIP localDateTime = {}", loaded.getLocalDateTime());
DEMO.info("ROUNDTRIP localTime = {}", loaded.getLocalTime());
DEMO.info("ROUNDTRIP instant = {}", loaded.getInstant());
DEMO.info("ROUNDTRIP offsetDateTime = {}", loaded.getOffsetDateTime());
DEMO.info("ROUNDTRIP zonedDateTime = {}", loaded.getZonedDateTime());
DEMO.info("ROUNDTRIP legacyDateAsDate = {}", loaded.getLegacyDateAsDate());
DEMO.info("ROUNDTRIP legacyDateAsTimestamp = {}", loaded.getLegacyDateAsTimestamp());
DEMO.info("ROUNDTRIP legacyDateNoTemporal = {} (class={})", loaded.getLegacyDateNoTemporal(), loaded.getLegacyDateNoTemporal().getClass());
DEMO.info("ROUNDTRIP legacyCalendar = {}", loaded.getLegacyCalendar() == null ? null : loaded.getLegacyCalendar().getTime());
DEMO.info("JVM default timezone during this run = {}", TimeZone.getDefault().getID());
assertThat(loaded.getLocalDate()).isEqualTo(LocalDate.of(2026, 3, 15));
assertThat(loaded.getLocalTime()).isEqualTo(LocalTime.of(10, 30, 45));
assertThat(loaded.getInstant()).isEqualTo(Instant.parse("2026-03-15T10:30:45Z"));
assertThat(loaded.getZonedDateTime().toInstant()).isEqualTo(e.getZonedDateTime().toInstant());
em2.close();
}
}
@@ -0,0 +1,74 @@
package com.ankurm.hibernatedemo.datetime;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
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 4876, docs/13-date-and-time-mapping.md chapter "hibernate.jdbc.time_zone".
* Sets {@code hibernate.jdbc.time_zone=America/New_York} (JVM default left at whatever the
* build runs under) and observes: (1) what it does to a {@code LocalDateTime} (no offset of
* its own -- the JDBC time zone controls what wall-clock value is actually sent to the driver),
* and (2) whether it changes anything for a {@code TimeZoneStorage.NATIVE} OffsetDateTime,
* which already carries its own explicit offset.
*/
@SpringBootTest
@TestPropertySource(properties = {
"spring.jpa.properties.hibernate.jdbc.time_zone=America/New_York"
})
class JdbcTimeZoneTest {
private static final Logger DEMO = LoggerFactory.getLogger("DEMO");
@Autowired
private EntityManagerFactory emf;
@Test
void jdbcTimeZone_affectsLocalDateTime_doesNotAffectExplicitOffset() {
LocalDateTime localDateTime = LocalDateTime.of(2026, 7, 4, 9, 0, 0);
OffsetDateTime explicitOffset = OffsetDateTime.of(2026, 7, 4, 9, 0, 0, 0, ZoneOffset.ofHoursMinutes(5, 30));
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
NanoPrecisionEntity np = new NanoPrecisionEntity();
np.setPlainLocalDateTime(localDateTime);
em.persist(np);
TimeZoneStorageEntity tz = new TimeZoneStorageEntity();
tz.setNativeMode(explicitOffset);
em.persist(tz);
em.getTransaction().commit();
Long npId = np.getId();
Long tzId = tz.getId();
em.close();
// Read back the raw stored value via native SQL to see the literal DB representation.
EntityManager em2 = emf.createEntityManager();
Object rawLocalDateTime = em2.createNativeQuery(
"SELECT CAST(plain_local_date_time AS VARCHAR) FROM nano_precision WHERE id = :id")
.setParameter("id", npId).getSingleResult();
Object rawOffset = em2.createNativeQuery(
"SELECT CAST(native_col AS VARCHAR) FROM tz_storage WHERE id = :id")
.setParameter("id", tzId).getSingleResult();
NanoPrecisionEntity npLoaded = em2.find(NanoPrecisionEntity.class, npId);
TimeZoneStorageEntity tzLoaded = em2.find(TimeZoneStorageEntity.class, tzId);
DEMO.info("hibernate.jdbc.time_zone=America/New_York -- original LocalDateTime = {}", localDateTime);
DEMO.info("hibernate.jdbc.time_zone=America/New_York -- raw DB value for LocalDateTime column = {}", rawLocalDateTime);
DEMO.info("hibernate.jdbc.time_zone=America/New_York -- round-tripped LocalDateTime = {}", npLoaded.getPlainLocalDateTime());
DEMO.info("hibernate.jdbc.time_zone=America/New_York -- original OffsetDateTime (NATIVE) = {}", explicitOffset);
DEMO.info("hibernate.jdbc.time_zone=America/New_York -- raw DB value for NATIVE offset column = {}", rawOffset);
DEMO.info("hibernate.jdbc.time_zone=America/New_York -- round-tripped OffsetDateTime (NATIVE) = {}", tzLoaded.getNativeMode());
em2.close();
}
}
@@ -0,0 +1,62 @@
package com.ankurm.hibernatedemo.datetime;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import java.time.Instant;
import java.time.LocalDateTime;
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;
/**
* Same experiment as {@link NanosecondTruncationTest} but against HSQLDB 2.7.3 in-memory,
* to see whether the microsecond rounding is H2-specific or shared. Docs: 13-date-and-time-mapping.md.
*/
@SpringBootTest
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:hsqldb:mem:nanotest;shutdown=true",
"spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver",
"spring.datasource.username=SA",
"spring.datasource.password=",
"spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect"
})
class NanosecondTruncationHsqldbTest {
private static final Logger DEMO = LoggerFactory.getLogger("DEMO");
@Autowired
private EntityManagerFactory emf;
@Test
void nanosecondsOnHsqldb() {
LocalDateTime withNanos = LocalDateTime.of(2026, 1, 1, 12, 0, 0, 123_456_789);
Instant instantWithNanos = Instant.ofEpochSecond(1_800_000_000L, 123_456_789);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
NanoPrecisionEntity e = new NanoPrecisionEntity();
e.setPlainLocalDateTime(withNanos);
e.setHighPrecisionLocalDateTime(withNanos);
e.setPlainInstant(instantWithNanos);
em.persist(e);
em.getTransaction().commit();
Long id = e.getId();
em.close();
EntityManager em2 = emf.createEntityManager();
NanoPrecisionEntity loaded = em2.find(NanoPrecisionEntity.class, id);
DEMO.info("HSQLDB 2.7.3: original LocalDateTime nanos = {}", withNanos.getNano());
DEMO.info("HSQLDB 2.7.3: plain column (precision default) nanos = {} (value={})",
loaded.getPlainLocalDateTime().getNano(), loaded.getPlainLocalDateTime());
DEMO.info("HSQLDB 2.7.3: @Column(precision=9) column nanos = {} (value={})",
loaded.getHighPrecisionLocalDateTime().getNano(), loaded.getHighPrecisionLocalDateTime());
DEMO.info("HSQLDB 2.7.3: original Instant nanos = {}", instantWithNanos.getNano());
DEMO.info("HSQLDB 2.7.3: plain Instant column nanos = {} (value={})",
loaded.getPlainInstant().getNano(), loaded.getPlainInstant());
em2.close();
}
}
@@ -0,0 +1,54 @@
package com.ankurm.hibernatedemo.datetime;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import java.time.Instant;
import java.time.LocalDateTime;
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;
/**
* Backs ankurm.com post 4876, docs/13-date-and-time-mapping.md chapter "Second-precision / truncation".
* Default (H2) profile here; see {@link NanosecondTruncationHsqldbTest} for the HSQLDB variant.
*/
@SpringBootTest
class NanosecondTruncationTest {
private static final Logger DEMO = LoggerFactory.getLogger("DEMO");
@Autowired
private EntityManagerFactory emf;
@Test
void nanosecondsOnH2() {
LocalDateTime withNanos = LocalDateTime.of(2026, 1, 1, 12, 0, 0, 123_456_789);
Instant instantWithNanos = Instant.ofEpochSecond(1_800_000_000L, 123_456_789);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
NanoPrecisionEntity e = new NanoPrecisionEntity();
e.setPlainLocalDateTime(withNanos);
e.setHighPrecisionLocalDateTime(withNanos);
e.setPlainInstant(instantWithNanos);
em.persist(e);
em.getTransaction().commit();
Long id = e.getId();
em.close();
EntityManager em2 = emf.createEntityManager();
NanoPrecisionEntity loaded = em2.find(NanoPrecisionEntity.class, id);
DEMO.info("H2 2.4.240: original LocalDateTime nanos = {}", withNanos.getNano());
DEMO.info("H2 2.4.240: plain column (precision default) nanos = {} (value={})",
loaded.getPlainLocalDateTime().getNano(), loaded.getPlainLocalDateTime());
DEMO.info("H2 2.4.240: @Column(precision=9) column nanos = {} (value={})",
loaded.getHighPrecisionLocalDateTime().getNano(), loaded.getHighPrecisionLocalDateTime());
DEMO.info("H2 2.4.240: original Instant nanos = {}", instantWithNanos.getNano());
DEMO.info("H2 2.4.240: plain Instant column nanos = {} (value={})",
loaded.getPlainInstant().getNano(), loaded.getPlainInstant());
em2.close();
}
}
@@ -0,0 +1,45 @@
package com.ankurm.hibernatedemo.datetime;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import java.time.Instant;
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;
/**
* Backs ankurm.com post 4876, docs/13-date-and-time-mapping.md chapter "@Temporal verified".
* The application context bootstrapping successfully with {@link TemporalOnJavaTimeEntity} on
* the classpath is itself the finding: Hibernate 7.4.5 does NOT reject the misuse at boot.
*/
@SpringBootTest
class TemporalAnnotationTest {
private static final Logger DEMO = LoggerFactory.getLogger("DEMO");
@Autowired
private EntityManagerFactory emf;
@Test
void temporalAnnotationOnInstantIsSilentlyIgnored_bootSucceedsAndValueRoundTrips() {
Instant now = Instant.parse("2026-05-20T09:15:30Z");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
TemporalOnJavaTimeEntity e = new TemporalOnJavaTimeEntity();
e.setInstantWithTemporalAnnotation(now);
em.persist(e);
em.getTransaction().commit();
Long id = e.getId();
em.close();
EntityManager em2 = emf.createEntityManager();
TemporalOnJavaTimeEntity loaded = em2.find(TemporalOnJavaTimeEntity.class, id);
DEMO.info("@Temporal(TIMESTAMP) on Instant field: boot succeeded, round-tripped value = {} (expected {})",
loaded.getInstantWithTemporalAnnotation(), now);
em2.close();
org.assertj.core.api.Assertions.assertThat(loaded.getInstantWithTemporalAnnotation()).isEqualTo(now);
}
}
@@ -0,0 +1,61 @@
package com.ankurm.hibernatedemo.datetime;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.TimeZone;
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;
/**
* Backs ankurm.com post 4876, docs/13-date-and-time-mapping.md chapter "The central experiment".
* Stores an {@code OffsetDateTime} with offset {@code +05:30} through every
* {@code @TimeZoneStorage} mode and logs exactly what offset comes back. Run this same test
* under different {@code -Duser.timezone} values (see docs/output capture commands) to observe
* the JVM-default-timezone hazard.
*/
@SpringBootTest
class TimeZoneStorageTest {
private static final Logger DEMO = LoggerFactory.getLogger("DEMO");
@Autowired
private EntityManagerFactory emf;
@Test
void storeNonUtcOffsetAndReadBackUnderEveryMode() {
OffsetDateTime original = OffsetDateTime.of(2026, 6, 15, 14, 0, 0, 0, ZoneOffset.ofHoursMinutes(5, 30));
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
TimeZoneStorageEntity e = new TimeZoneStorageEntity();
e.setNoAnnotation(original);
e.setNativeMode(original);
e.setNormalizeMode(original);
e.setNormalizeUtcMode(original);
e.setColumnMode(original);
e.setAutoMode(original);
em.persist(e);
em.getTransaction().commit();
Long id = e.getId();
em.close();
EntityManager em2 = emf.createEntityManager();
TimeZoneStorageEntity loaded = em2.find(TimeZoneStorageEntity.class, id);
DEMO.info("JVM user.timezone system property = {}", System.getProperty("user.timezone"));
DEMO.info("JVM TimeZone.getDefault() = {}", TimeZone.getDefault().getID());
DEMO.info("ORIGINAL stored = {}", original);
DEMO.info("TZ_MODE no-annotation (default) = {}", loaded.getNoAnnotation());
DEMO.info("TZ_MODE NATIVE = {}", loaded.getNativeMode());
DEMO.info("TZ_MODE NORMALIZE = {}", loaded.getNormalizeMode());
DEMO.info("TZ_MODE NORMALIZE_UTC = {}", loaded.getNormalizeUtcMode());
DEMO.info("TZ_MODE COLUMN = {}", loaded.getColumnMode());
DEMO.info("TZ_MODE AUTO = {}", loaded.getAutoMode());
em2.close();
}
}