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