Hibernate @TimeZoneStorage Example
1. Introduction
Hibernate 6 introduces TimeZoneStorage annotation that specifies how the time zone information of a persistent property or field should be persisted. It supports six TimeZoneStorageTypes: AUTO, DEFAULT, NATIVE, COLUMN, NORMALIZE, and NORMALIZE_UTC. In this example, I will create a simple Spring boot JPA application that utilizes Hibernate TimeZoneStorage
annotation to store the time zone data into a Database.
2. Setup
In this step, I will create a gradle project along with Spring Data JPA, Spring Web, Lombok, and H2 Database libraries via Spring initializer. See Figure 1 for details.
2.1 Generated Gradle Properties
Importing the generated gradle project into Eclipse IDE. Figure 2 shows that hibernate-core-6.5.3.Final.jar
is included.
No modification is needed for the generated build.gradle
file.
build.gradle
plugins { id 'java' id 'org.springframework.boot' version '3.3.5' id 'io.spring.dependency-management' version '1.1.6' } group = 'org.jcg.zheng' version = '0.0.1-SNAPSHOT' java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { useJUnitPlatform() }
2.2 Generated Spring Boot Application
No modification is needed for the generated DemoTimeZoneStorageApplication.java
file.
DemoTimeZoneStorageApplication.java
package org.jcg.zheng.demo_TimeZoneStorage; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoTimeZoneStorageApplication { public static void main(String[] args) { SpringApplication.run(DemoTimeZoneStorageApplication.class, args); } }
2.3 Application Properties
In this step, I will add several properties in application.properties
to enable the trace log for hibernate binding and the SQL used.
application.properties
spring.application.name=demo-TimeZoneStorage spring.jpa.properties.hibernate.format_sql=true spring.jpa.show-sql=true logging.level.org.hibernate.orm.jdbc.bind=trace spring.h2.console.enabled=true
- Line 3, 4: enable the spring Jpa properties to show formatted SQL statements.
- Line 6: enable the trace level of hibernate binding logging.
- Line 8: enable the H2 console.
3. Hibernate TimeZoneStorage Entity
In this step, I will create an EntityWithDateExample.java
that has eight date fields. Four of them annotate with @TimeZoneStorage
.
EntityWithDateExample.java
package org.jcg.zheng.demo_TimeZoneStorage.entity; import java.time.OffsetDateTime; import java.time.ZonedDateTime; import java.util.Date; import org.hibernate.annotations.TimeZoneColumn; import org.hibernate.annotations.TimeZoneStorage; import org.hibernate.annotations.TimeZoneStorageType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @Entity @Table(name = "TABLE_WITH_DATES") public class EntityWithDateExample { @Column(name = "Column_OffsetTime") @TimeZoneColumn(name = "offset_zone") @TimeZoneStorage(TimeZoneStorageType.COLUMN) public OffsetDateTime columnOffsetTime; @Column(name = "Column_OffsetTime9", columnDefinition = "TIMESTAMP(9)") @TimeZoneColumn(name = "offset_zone9") @TimeZoneStorage(TimeZoneStorageType.COLUMN) private OffsetDateTime columnOffsetTime9; @Column(name = "Normal_date") private Date normalDate; @Id @GeneratedValue private Long id; @Column(name = "Native_OffsetTime") @TimeZoneStorage(TimeZoneStorageType.NATIVE) public OffsetDateTime nativeOffsetTime; @Column(name = "Native_ZonedTime9", columnDefinition = "TIMESTAMP(9) WITH TIME ZONE") @TimeZoneStorage(TimeZoneStorageType.NATIVE) private ZonedDateTime nativeZonedDateTime9; @Column(name = "Normal_OffsetTime") private OffsetDateTime normalOffsetTime; @Column(name = "Normal_ZonedTime") private ZonedDateTime normalTime; @Column(name = "UTC_OffsetTime9", columnDefinition = "TIMESTAMP(9)") @TimeZoneStorage(TimeZoneStorageType.NORMALIZE_UTC) private OffsetDateTime utcOffsetTime9; }
- Line 21, 22:
Entity
andTable
annotations - Line 25-27: defines
columnOffsetTime
from theOffsetDateTime
class. It stores microseconds up to 6 digits. - Line 30-32: defines
columnOffsetTime9
from theOffsetDateTime
class. It stores microseconds up to 9 digits. - Line 35: defines
normalDate
from theDate
class ( default with 6 digits of microseconds ). - Line 42-44: defines
nativeOffsetTime
from theOffsetDateTime
class. It stores microseconds up to 6 digits. - Line 46-48: defines
nativeZonedDateTime9
from theOffsetDateTime
class. It stores microseconds up to 9 digits. - Line 50-51: defines
normalOffsetTime
from theOffsetDateTime
class. - Line 53-54: defines
normalTime
from theZonedDateTime
class. - Line 56-57: defines
utcOffsetTime9
from theOffsetDateTime
class. It stores microseconds up to 9 digits.
4. Repository
In this step, I will create an EntityWithDateExampleRepo.java
that extends from JpaRepository
.
EntityWithDateExampleRepo.java
package org.jcg.zheng.demo_TimeZoneStorage.repo; import org.jcg.zheng.demo_TimeZoneStorage.entity.EntityWithDateExample; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface EntityWithDateExampleRepo extends JpaRepository<EntityWithDateExample, Long> { }
4.1 Repository Test
In this step, I will create an EntityExampleRepoTest.java
to test the save
and findAll
methods.
EntityExampleRepoTest.java
package org.jcg.zheng.demo_TimeZoneStorage.repo; import static org.junit.jupiter.api.Assertions.assertFalse; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Date; import java.util.List; import org.jcg.zheng.demo_TimeZoneStorage.entity.EntityWithDateExample; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class EntityExampleRepoTest { @Autowired private EntityWithDateExampleRepo testClass; @Test void test_save_get() { EntityWithDateExample example = new EntityWithDateExample(); example.setNativeZonedDateTime9(ZonedDateTime.now().plusDays(10)); example.setNormalTime(ZonedDateTime.now()); example.setNormalOffsetTime(OffsetDateTime.now()); example.setColumnOffsetTime(OffsetDateTime.now().minusYears(1)); example.setUtcOffsetTime9(OffsetDateTime.now(ZoneOffset.UTC)); example.setNormalDate(new Date()); example.setColumnOffsetTime(OffsetDateTime.of(1970, 1, 1, 23, 59, 59, 999, ZoneOffset.UTC)); example.setColumnOffsetTime9(OffsetDateTime.of(1980, 1, 1, 23, 59, 59, 999, ZoneOffset.UTC)); testClass.save(example); List<EntityWithDateExample> savedData = testClass.findAll(); assertFalse(savedData.isEmpty()); } }
Run the Junit test and capture the console log.
EntityExampleRepoTest Log
13:29:21.593 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [org.jcg.zheng.demo_TimeZoneStorage.repo.EntityExampleRepoTest]: EntityExampleRepoTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration. 13:29:21.695 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration org.jcg.zheng.demo_TimeZoneStorage.DemoTimeZoneStorageApplication for test class org.jcg.zheng.demo_TimeZoneStorage.repo.EntityExampleRepoTest . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.3.5) 2024-11-03T13:29:22.166-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ main] o.j.z.d.repo.EntityExampleRepoTest : Starting EntityExampleRepoTest using Java 17.0.11 with PID 36984 (started by azpm0 in C:\MaryTools\workspace\demo-TimeZoneStorage) 2024-11-03T13:29:22.167-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ main] o.j.z.d.repo.EntityExampleRepoTest : No active profile set, falling back to 1 default profile: "default" 2024-11-03T13:29:22.836-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. 2024-11-03T13:29:22.908-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 58 ms. Found 1 JPA repository interface. 2024-11-03T13:29:23.437-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2024-11-03T13:29:23.665-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:6f376f8f-f050-47c9-a004-6e9062a5eb5d user=SA 2024-11-03T13:29:23.667-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 2024-11-03T13:29:23.728-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default] 2024-11-03T13:29:23.799-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.5.3.Final 2024-11-03T13:29:23.844-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ main] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled 2024-11-03T13:29:24.204-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer 2024-11-03T13:29:25.159-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) Hibernate: drop table if exists table_with_dates cascade Hibernate: drop sequence if exists table_with_dates_seq Hibernate: create sequence table_with_dates_seq start with 1 increment by 50 Hibernate: create table table_with_dates ( offset_zone9 integer, column_offset_time timestamp(6) with time zone, column_offset_time9 TIMESTAMP(9), id bigint not null, native_offset_time timestamp(6) with time zone, native_zoned_time9 TIMESTAMP(9) WITH TIME ZONE, normal_date timestamp(6), normal_offset_time timestamp(6) with time zone, normal_zoned_time timestamp(6) with time zone, utc_offset_time9 TIMESTAMP(9), primary key (id) ) 2024-11-03T13:29:25.211-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' 2024-11-03T13:29:25.631-06:00 WARN 36984 --- [demo-TimeZoneStorage] [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning 2024-11-03T13:29:26.041-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ main] o.s.b.a.h2.H2ConsoleAutoConfiguration : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:6f376f8f-f050-47c9-a004-6e9062a5eb5d' 2024-11-03T13:29:26.118-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ main] o.j.z.d.repo.EntityExampleRepoTest : Started EntityExampleRepoTest in 4.224 seconds (process running for 5.282) Hibernate: select next value for table_with_dates_seq Hibernate: insert into table_with_dates (column_offset_time, column_offset_time9, offset_zone9, native_offset_time, native_zoned_time9, normal_date, normal_offset_time, normal_zoned_time, utc_offset_time9, id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 2024-11-03T13:29:26.221-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (1:TIMESTAMP_WITH_TIMEZONE) <- [1970-01-01T23:59:59.000000999Z] 2024-11-03T13:29:26.227-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (2:TIMESTAMP_UTC) <- [1980-01-02T05:59:59.000000999Z] 2024-11-03T13:29:26.227-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (3:INTEGER) <- [-06:00] 2024-11-03T13:29:26.228-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (4:TIMESTAMP_WITH_TIMEZONE) <- [2023-11-03T13:29:26.123093400-06:00] 2024-11-03T13:29:26.228-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (5:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-13T13:29:26.123093400-06:00[America/Chicago]] 2024-11-03T13:29:26.228-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (6:TIMESTAMP) <- [2024-11-03 13:29:26.123] 2024-11-03T13:29:26.229-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (7:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-03T13:29:26.123093400-06:00] 2024-11-03T13:29:26.230-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (8:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-03T13:29:26.123093400-06:00[America/Chicago]] 2024-11-03T13:29:26.230-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (9:TIMESTAMP_UTC) <- [2024-11-03T19:29:26.123093400Z] 2024-11-03T13:29:26.230-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (10:BIGINT) <- [1] 2024-11-03T13:29:26.242-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ main] o.j.z.d.LoadSampleData : Preloading EntityWithDateExample(columnOffsetTime=1970-01-01T23:59:59.000000999Z, columnOffsetTime9=1980-01-01T23:59:59.000000999-06:00, normalDate=Sun Nov 03 13:29:26 CST 2024, id=1, nativeOffsetTime=2023-11-03T13:29:26.123093400-06:00, nativeZonedDateTime9=2024-11-13T13:29:26.123093400-06:00[America/Chicago], normalOffsetTime=2024-11-03T13:29:26.123093400-06:00, normalTime=2024-11-03T13:29:26.123093400-06:00[America/Chicago], utcOffsetTime9=2024-11-03T19:29:26.123093400Z) Hibernate: select next value for table_with_dates_seq Hibernate: insert into table_with_dates (column_offset_time, column_offset_time9, offset_zone9, native_offset_time, native_zoned_time9, normal_date, normal_offset_time, normal_zoned_time, utc_offset_time9, id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 2024-11-03T13:29:26.780-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (1:TIMESTAMP_WITH_TIMEZONE) <- [1970-01-01T23:59:59.000000999Z] 2024-11-03T13:29:26.781-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (2:TIMESTAMP_UTC) <- [1980-01-01T23:59:59.000000999Z] 2024-11-03T13:29:26.781-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (3:INTEGER) <- [Z] 2024-11-03T13:29:26.781-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (4:TIMESTAMP_WITH_TIMEZONE) <- [null] 2024-11-03T13:29:26.781-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (5:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-13T13:29:26.778942300-06:00[America/Chicago]] 2024-11-03T13:29:26.781-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (6:TIMESTAMP) <- [2024-11-03 13:29:26.779] 2024-11-03T13:29:26.781-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (7:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-03T13:29:26.778942300-06:00] 2024-11-03T13:29:26.782-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (8:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-03T13:29:26.778942300-06:00[America/Chicago]] 2024-11-03T13:29:26.782-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (9:TIMESTAMP_UTC) <- [2024-11-03T19:29:26.779928100Z] 2024-11-03T13:29:26.782-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (10:BIGINT) <- [2] Hibernate: select ewde1_0.id, ewde1_0.column_offset_time, ewde1_0.column_offset_time9, ewde1_0.offset_zone9, ewde1_0.native_offset_time, ewde1_0.native_zoned_time9, ewde1_0.normal_date, ewde1_0.normal_offset_time, ewde1_0.normal_zoned_time, ewde1_0.utc_offset_time9 from table_with_dates ewde1_0 2024-11-03T13:29:26.933-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' Hibernate: drop table if exists table_with_dates cascade Hibernate: drop sequence if exists table_with_dates_seq 2024-11-03T13:29:26.938-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated... 2024-11-03T13:29:26.940-06:00 INFO 36984 --- [demo-TimeZoneStorage] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
- Line 32-42: the create table statement.
- Line 80-81: the data binding parameters.
5. Hibernate TimeZoneStorage Controller
In this step, I will create an EntityWithDateController.java
that includes a GET method.
EntityWithDateController.java
package org.jcg.zheng.demo_TimeZoneStorage.controller; import java.util.List; import org.jcg.zheng.demo_TimeZoneStorage.entity.EntityWithDateExample; import org.jcg.zheng.demo_TimeZoneStorage.repo.EntityWithDateExampleRepo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class EntityWithDateController { @Autowired private EntityWithDateExampleRepo rep; @GetMapping("/examples") List<EntityWithDateExample> all() { return rep.findAll(); } }
5.1 Load Sample Data
In this step, I will create a LoadSampleData.java
to save testing data.
LoadSampleData.java
package org.jcg.zheng.demo_TimeZoneStorage; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Date; import org.jcg.zheng.demo_TimeZoneStorage.entity.EntityWithDateExample; import org.jcg.zheng.demo_TimeZoneStorage.repo.EntityWithDateExampleRepo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class LoadSampleData { private static final Logger LOGGER = LoggerFactory.getLogger(LoadSampleData.class); @Bean CommandLineRunner initDatabase(EntityWithDateExampleRepo repository) { return args -> { EntityWithDateExample example = new EntityWithDateExample(); example.setNativeZonedDateTime9(ZonedDateTime.now().plusDays(10)); example.setNativeOffsetTime(OffsetDateTime.now().minusYears(1)); example.setNormalTime(ZonedDateTime.now()); example.setNormalOffsetTime(OffsetDateTime.now()); example.setUtcOffsetTime9(OffsetDateTime.now(ZoneOffset.UTC)); example.setNormalDate(new Date()); example.setColumnOffsetTime(OffsetDateTime.of(1970, 1, 1, 23, 59, 59, 999, ZoneOffset.UTC)); example.setColumnOffsetTime9(OffsetDateTime.of(1980, 1, 1, 23, 59, 59, 999, ZoneOffset.of("-06:00"))); LOGGER.info("Preloading " + repository.save(example)); }; } }
- Line 31, 33: the time zone is set as
ZoneOffset.UTC
. - Line 34: the time zone is set as
ZoneOffset.of("-06:00")
.
6. Demo Hibernate TimeZoneStorage
6.1 Start the Spring Boot Application
In this step, I will start the Spring boot application and capture the console log.
Spring Boot Console Log
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.3.5) 2024-11-03T13:30:55.860-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] o.j.z.d.DemoTimeZoneStorageApplication : Starting DemoTimeZoneStorageApplication using Java 17.0.11 with PID 35080 (C:\MaryTools\workspace\demo-TimeZoneStorage\bin\main started by azpm0 in C:\MaryTools\workspace\demo-TimeZoneStorage) 2024-11-03T13:30:55.865-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] o.j.z.d.DemoTimeZoneStorageApplication : No active profile set, falling back to 1 default profile: "default" 2024-11-03T13:30:56.586-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. 2024-11-03T13:30:56.662-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 64 ms. Found 1 JPA repository interface. 2024-11-03T13:30:57.321-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http) 2024-11-03T13:30:57.353-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2024-11-03T13:30:57.354-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.31] 2024-11-03T13:30:57.441-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2024-11-03T13:30:57.445-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1522 ms 2024-11-03T13:30:57.486-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2024-11-03T13:30:57.663-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:8c8060a5-3821-4778-9178-b4050bc94b6a user=SA 2024-11-03T13:30:57.667-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 2024-11-03T13:30:57.681-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] o.s.b.a.h2.H2ConsoleAutoConfiguration : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:8c8060a5-3821-4778-9178-b4050bc94b6a' 2024-11-03T13:30:57.873-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default] 2024-11-03T13:30:57.936-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.5.3.Final 2024-11-03T13:30:57.978-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled 2024-11-03T13:30:58.302-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer 2024-11-03T13:30:59.162-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) Hibernate: drop table if exists table_with_dates cascade Hibernate: drop sequence if exists table_with_dates_seq Hibernate: create sequence table_with_dates_seq start with 1 increment by 50 Hibernate: create table table_with_dates ( offset_zone9 integer, column_offset_time timestamp(6) with time zone, column_offset_time9 TIMESTAMP(9), id bigint not null, native_offset_time timestamp(6) with time zone, native_zoned_time9 TIMESTAMP(9) WITH TIME ZONE, normal_date timestamp(6), normal_offset_time timestamp(6) with time zone, normal_zoned_time timestamp(6) with time zone, utc_offset_time9 TIMESTAMP(9), primary key (id) ) 2024-11-03T13:30:59.197-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' 2024-11-03T13:30:59.503-06:00 WARN 35080 --- [demo-TimeZoneStorage] [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning 2024-11-03T13:30:59.881-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/' 2024-11-03T13:30:59.895-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] o.j.z.d.DemoTimeZoneStorageApplication : Started DemoTimeZoneStorageApplication in 4.457 seconds (process running for 4.907) Hibernate: select next value for table_with_dates_seq Hibernate: insert into table_with_dates (column_offset_time, column_offset_time9, offset_zone9, native_offset_time, native_zoned_time9, normal_date, normal_offset_time, normal_zoned_time, utc_offset_time9, id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 2024-11-03T13:31:00.014-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (1:TIMESTAMP_WITH_TIMEZONE) <- [1970-01-01T23:59:59.000000999Z] 2024-11-03T13:31:00.024-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (2:TIMESTAMP_UTC) <- [1980-01-02T05:59:59.000000999Z] 2024-11-03T13:31:00.025-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (3:INTEGER) <- [-06:00] 2024-11-03T13:31:00.025-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (4:TIMESTAMP_WITH_TIMEZONE) <- [2023-11-03T13:30:59.900076700-06:00] 2024-11-03T13:31:00.025-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (5:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-13T13:30:59.899077700-06:00[America/Chicago]] 2024-11-03T13:31:00.025-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (6:TIMESTAMP) <- [2024-11-03 13:30:59.9] 2024-11-03T13:31:00.027-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (7:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-03T13:30:59.900076700-06:00] 2024-11-03T13:31:00.027-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (8:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-03T13:30:59.900076700-06:00[America/Chicago]] 2024-11-03T13:31:00.027-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (9:TIMESTAMP_UTC) <- [2024-11-03T19:30:59.900076700Z] 2024-11-03T13:31:00.027-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [ main] org.hibernate.orm.jdbc.bind : binding parameter (10:BIGINT) <- [1] 2024-11-03T13:31:00.045-06:00 INFO 35080 --- [demo-TimeZoneStorage] [ main] o.j.z.d.LoadSampleData : Preloading EntityWithDateExample(columnOffsetTime=1970-01-01T23:59:59.000000999Z, columnOffsetTime9=1980-01-01T23:59:59.000000999-06:00, normalDate=Sun Nov 03 13:30:59 CST 2024, id=1, nativeOffsetTime=2023-11-03T13:30:59.900076700-06:00, nativeZonedDateTime9=2024-11-13T13:30:59.899077700-06:00[America/Chicago], normalOffsetTime=2024-11-03T13:30:59.900076700-06:00, normalTime=2024-11-03T13:30:59.900076700-06:00[America/Chicago], utcOffsetTime9=2024-11-03T19:30:59.900076700Z)
6.2 Get Rest Data
In this step, I will call GET Rest API to http://localhost:8080/examples and capture the response JSON. The date fields include the timezone exactly as it’s saved.
http://localhost:8080/examples Response JSON
[ { "columnOffsetTime": "1970-01-01T23:59:59.000001Z", "columnOffsetTime9": "1980-01-01T23:59:59.000000999-06:00", "normalDate": "2024-11-03T19:30:59.900+00:00", "id": 1, "nativeOffsetTime": "2023-11-03T13:30:59.900077-06:00", "nativeZonedDateTime9": "2024-11-13T13:30:59.8990777-06:00", "normalOffsetTime": "2024-11-03T13:30:59.900077-06:00", "normalTime": "2024-11-03T13:30:59.900077-06:00", "utcOffsetTime9": "2024-11-03T19:30:59.9000767Z" } ]
- Line 4:
"columnOffsetTime9": "1980-01-01T23:59:59.000000999-06:00"
. Note: the time zone with-06:00
and9
digits of microseconds are saved into Database table. - Line 7:
"nativeOffsetTime": "2023-11-03T13:30:59.900077-06:00".
The default 6 digits of ms along with-06:00
are saved. - Line 8: similar to above, but the precision is
9
instead of6
. - Line 11: the
Z
letter for UTC.
6.3 H2 Database Table
In this step, I will open H2
console web page and capture the data in TABLE_WITH_DATES
table.
Note: the value in the OFFSET_ZONE9
is the offset value in seconds from UTC. Here is the math formula: -6 (hours) x 3600 (seconds/hour) = -21600 (seconds).
7. Conclusion
In this example, I showed several date fields annotated with @TimeZoneStorage
to store the time zone data. It simplifies the time zone storage in the Database by providing built-in strategies, making it easier to control how time zone or offset information is stored and retrieved. If the applications require historical accuracy in the time zone, for example, the financial systems need to record the original time zone, then using Hibernate TimeZoneStorage
simplifies the solution.
8. Download
This was an example of a gradle project which included Hibernate TimeZoneStorage
annotation.
You can download the full source code of this example here: Hibernate @TimeZoneStorage Example