【问题标题】:hibernate + spring boot store date on UTC time zone troublesUTC 时区问题上的休眠 + spring boot 存储日期
【发布时间】:2018-09-17 08:38:51
【问题描述】:

我正在进行一些测试以将 UTC 定义为我的应用程序的默认时区。首先,我希望我的日期时间值与 UTC 值一起存储。

根据 VLAD MIHALCEA (https://vladmihalcea.com/how-to-store-date-time-and-timestamps-in-utc-time-zone-with-jdbc-and-hibernate/) 和 https://moelholm.com/2016/11/09/spring-boot-controlling-timezones-with-hibernate/ 我已在属性文件中设置:

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

为了测试我使用的是 h2 数据库,我创建了一个包含所有 java 8 dateTime 类型的示例实体。

在我的 liquibase 配置中,它们的定义如下:

<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"/>

我认为我对每个字段都使用了好的类型。它适用于除“local_time”“offset_time”之外的所有字段,它们是时间 sql 类型而不是时间戳。

如您所见,我在上午 8:39(巴黎 GMT+2)添加了这一行,并且时间戳具有良好的 UTC 值(上午 6:38)。 但是“local_time”和“offset_time”都有一个奇怪的值(早上 7:39)。

我想知道为什么会出现这种行为,如果你们中的一些人知道为什么我的两个时间字段没有正确存储值。

PS:版本:

  • 休眠:5.2.17.Final
  • 春季启动:2.0.4.RELEASE

我的示例实体用于插入数据:

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;

【问题讨论】:

  • jpa.properties.hibernate.jdbc.time_zone= UTC 必须是 spring.jpa.properties.hibernate.jdbc.time_zone= UTC。否则它不会被映射。还有你是如何插入这些数据的?
  • 我的错。我正确设置了“spring.jpa.properties.hibernate.jdbc.time_zone”。我使用“MyData”实体通过休息调用和弹簧数据存储库插入数据
  • 请修复该属性并添加一些代码并说明您使用的是哪些类型(Joda Time 或 JDK8)。
  • 你说得对,我必须添加我的实体才能更清晰。正如我在文章开头所说的,我只使用 JDK8 Time API。我想测试所有 JDK8 类型以确保它们可以使用 UTC 配置选项正确处理。

标签: java spring hibernate datetime spring-boot


【解决方案1】:

试一试:

@SpringBootApplication
public class YourApplication {

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

【讨论】:

  • 此解决方案有效,但根据:moelholm.com/2016/11/09/…“此解决方案有效。但它非常具有侵入性并且有一些潜在的缺点[...]”这就是我更喜欢休眠调整解决方案的原因。
  • 不会更好地使用 org.springframework.beans.factory.InitializingBean 甚至 org.springframework.context.event.ContextRefreshedEvent; org.springframework.context.event.EventListener;既然都是基于spring框架的?
【解决方案2】:
spring.datasource.url=jdbc:mysql://...?serverTimezone=Asia/Shanghai

为我工作。

  1. Hibernate 应该反映 mysql 时区。
  2. mysql jdbc 应该反映 mysql 时区。

【讨论】:

    【解决方案3】:

    如果决定在我的情况下使用 MySQL 可以正常工作

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

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

    【讨论】:

    • 它是否与“时间” sql 列一起使用?因为这是我的问题。我将使用 mysql 进行测试,但使用 h2 和 postgres 都不起作用。
    【解决方案4】:

    我在休眠错误跟踪器中打开了一个问题,并得到了我的问题的答案。

    对于 LocalTime,转换是相对于 1970 年 1 月 1 日,而不是我运行测试的那一天。所以不处理夏令时。

    根据 Vlad Mihalcea 的说法,我们必须改用 LocalDateTime,因为我们知道日期,当然也知道它是否在 DST 期间。

    这里有完整的回应: https://hibernate.atlassian.net/browse/HHH-12988?focusedCommentId=103750&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-103750

    问候

    【讨论】:

      【解决方案5】:

      一旦 spring 上下文被初始化 ....

      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"));
      
          }   
      
      }
      

      @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"));
          }
      }
      

      【讨论】:

      • 我想这样做会丢失一些不错的功能,比如从系统默认时区转换为 UTC。然后每次你需要告诉哪个时区有你正在转换为 UTC 的 LocalDateTime
      【解决方案6】:

      将默认时区设置为 UTC 并不能完全解决我的问题。它带来了另一个问题,当我记录一个具有 OffsetTime 属性的实体时,它以错误的方式吞噬了偏移量信息和数据保存数据。例如,18:25+03:00 变为 18:25+00:00。我认为这是最糟糕的情况,因为数据已损坏。

      为了克服这个问题并且不丢失偏移信息,我使用了 OffsetTime 类的 withOffsetSameInstant 方法并像这样记录我的实体。

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

      最后,这适用于您的计算机使用的任何时区。这也适用于 OffsetDateTime 类型的属性。

      【讨论】:

        猜你喜欢
        • 2016-09-04
        • 2015-07-28
        • 1970-01-01
        • 2014-08-14
        • 1970-01-01
        • 2019-03-01
        • 1970-01-01
        • 2013-08-19
        • 1970-01-01
        相关资源
        最近更新 更多