简体   繁体   中英

hibernate + spring boot store date on UTC time zone troubles

I'm making some tests to define UTC as default time zone for my application. First of all I want my datetime values to be stored with the UTC one.

According to VLAD MIHALCEA ( https://vladmihalcea.com/how-to-store-date-time-and-timestamps-in-utc-time-zone-with-jdbc-and-hibernate/ ) and https://moelholm.com/2016/11/09/spring-boot-controlling-timezones-with-hibernate/ I have set in my properties file:

spring.jpa.properties.hibernate.jdbc.time_zone= UTC

For testing I'm using h2 database, I've created a sample entity with all java 8 dateTime Type.

In my liquibase config they are defined like this:

<column name="instant" type="timestamp"/>
<column name="local_date" type="date"/>
<column name="local_time" type="time"/>
<column name="offset_time" type="time"/>
<column name="local_date_time" type="timestamp"/>
<column name="offset_date_time" type="timestamp"/>
<column name="zoned_date_time" type="timestamp"/>

I think I'm using the good type for every fields. It's work for all fields except for "local_time" "offset_time" which are Time sql types not timestamp.

在此处输入图片说明

As you can see i added this row at 8:39am (Paris GMT+2) and timestamps have the good UTC value (6:38am). BUT both "local_time" and "offset_time" have a strange value (7:39am).

I wonder why this behaviour, if some of you have an idea why my two time fields not store values correctly.

PS: version:

  • Hibernate: 5.2.17.Final
  • Spring boot: 2.0.4.RELEASE

My sample Entity use to insert data :

import javax.persistence.*;
import java.io.Serializable;
import java.time.*;
import java.util.Objects;

@Entity
@Table(name = "avdev_myData")
public class MyData implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
    @SequenceGenerator(name = "sequenceGenerator")
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "instant")
    private Instant instant;

    @Column(name = "local_date")
    private LocalDate localDate;

    @Column(name = "local_time")
    private LocalTime localTime;

    @Column(name = "offset_time")
    private OffsetTime offsetTime;

    @Column(name = "local_date_time")
    private LocalDateTime localDateTime;

    @Column(name = "offset_date_time")
    private OffsetDateTime offsetDateTime;

    @Column(name = "zoned_date_time")
    private ZonedDateTime zonedDateTime;

Have a try:

@SpringBootApplication
public class YourApplication {

    @PostConstruct
    void started() {
        // set JVM timezone as UTC
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
    }
}

In case decide using MySQL in my case works properly with

spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL57InnoDBDialect

spring.datasource.url=jdbc:mysql://DBHOST:3306/DBNAME?useLegacyDatetimeCode=false&serverTimezone=UTC

spring.datasource.url=jdbc:mysql://...?serverTimezone=Asia/Shanghai

works for me.

  1. Hibernate SHOULD reflect mysql timezone.
  2. mysql jdbc should reflect mysql timezone.

I opened an issue in the hibernate bug tracker and had an answer of my problem.

For LocalTime the tranformation is relative to the 1st january 1970 not the day i had run the test. So the DST is not handled.

According to Vlad Mihalcea we have to use LocalDateTime instead because we know the date and of course if it's on DST period or not.

there is the whole response here: https://hibernate.atlassian.net/browse/HHH-12988?focusedCommentId=103750&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-103750

regards

Once the spring context is initialized ....

import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;


@Component
public class ApplicationStartUp {

    @EventListener(ContextRefreshedEvent.class)
    public void contextRefreshedEvent() {

        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

    }   

}

OR

@Component
public class InitializingContextBean implements InitializingBean {

    private static final Logger LOG  = Logger.getLogger(InitializingContextBean.class);

    @Autowired
    private Environment environment;

    @Override
    public void afterPropertiesSet() throws Exception {         
            TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
    }
}

Setting the default timezone as UTC didn't resolve my problem completely. It brought another problem, when I recorded an entity with OffsetTime property, it swallowed the offset info and data saved data in wrong way. For example, 18:25+03:00 turns into 18:25+00:00. I think this is the worst scenario, because data becomes corrupted.

To overcome this issue and not lose offset info, I used withOffsetSameInstant method of the OffsetTime class and record my entity like that.

ZoneOffset systemZoneOffset = ZoneId.systemDefault().getRules().getOffset(Instant.now());
OffsetTime offsetTime = clientOffsetTime.withOffsetSameInstant(systemZoneOffset);

Lastly, this will work for any timezone that your computer is using. This should also work for OffsetDateTime type of properties.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM