Core Java

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.

Hibernate TimeZoneStorage Project
Figure 1 Hibernate TimeZoneStorage Project

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.

Hibernate TimeZoneStorage Jar
Figure 2. Hibernate Jar

No modification is needed for the generated build.gradle file.

build.gradle

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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

01
02
03
04
05
06
07
08
09
10
11
12
13
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

1
2
3
4
5
6
7
8
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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 and Table annotations
  • Line 25-27: defines columnOffsetTime from the OffsetDateTime class. It stores microseconds up to 6 digits.
  • Line 30-32: defines columnOffsetTime9 from the OffsetDateTime class. It stores microseconds up to 9 digits.
  • Line 35: defines normalDate from the Date class ( default with 6 digits of microseconds ).
  • Line 42-44: defines nativeOffsetTime from the OffsetDateTime class. It stores microseconds up to 6 digits.
  • Line 46-48: defines nativeZonedDateTime9 from the OffsetDateTime class. It stores microseconds up to 9 digits.
  • Line 50-51: defines normalOffsetTime from the OffsetDateTime class.
  • Line 53-54: defines normalTime from the ZonedDateTime class.
  • Line 56-57: defines utcOffsetTime9 from the OffsetDateTime 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

01
02
03
04
05
06
07
08
09
10
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 
 :: 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

01
02
03
04
05
06
07
08
09
10
11
12
13
[
  {
    "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 and 9 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 of 6.
  • 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.

Figure 3. H2 Database

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.

Download
You can download the full source code of this example here: Hibernate @TimeZoneStorage Example
Do you want to know how to develop your skillset to become a Java Rockstar?
Subscribe to our newsletter to start Rocking right now!
To get you started we give you our best selling eBooks for FREE!
1. JPA Mini Book
2. JVM Troubleshooting Guide
3. JUnit Tutorial for Unit Testing
4. Java Annotations Tutorial
5. Java Interview Questions
6. Spring Interview Questions
7. Android UI Design
and many more ....
I agree to the Terms and Privacy Policy

Mary Zheng

Mary graduated from the Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She worked as a lead Software Engineer where she led and worked with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest


This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button