Hibernate with Java 8 LocalDate&数据库中的LocalDateTime

时间:2017-04-18 15:30:38

标签: java hibernate utc java-time

我的要求是存储所有日期&数据库中UTC时区的日期时间。我正在使用Java 8' LocalDate&我的Hibernate实体中的LocalDateTime

这是正确的LocalDate& LocalDateTime没有与之相关的时区吗?

如果没有,我是否应该回到使用旧的(或传统的?)Date& Timestamp

或者我应该使用Java 8' Instant?如果使用Instant,是否有可能只存储日期部分,没有时间?

数据库是MySQL& SQL Server,这是一个Spring Boot应用程序。

3 个答案:

答案 0 :(得分:37)

“Local ...”类型故意没有时区概念。所以他们代表时间轴上的一个时刻。 LocalDateTime表示可能时刻的模糊范围,但在指定偏移或时区之前没有实际意义。这意味着应用ZoneId来获取ZonedDateTime

例如,假设今年圣诞节从12月25日的第一个时刻开始,我们说:

LocalDateTime ldt = LocalDateTime.of( 2017 , 12 , 25 , 0 , 0 , 0 , 0 );
但是,午夜时分发生在东部早于西部。

这就是为什么精灵的后勤部门在太平洋地区的Kiribati开始绘制圣诞老人的路线,这是世界上最早的时区,比UTC早14小时。在那里交付之后,他们将圣诞老人向西行驶到新西兰等地的午夜。然后去亚洲过夜。然后是印度,等等,几个小时后到达欧洲的午夜,然后是北美东海岸的午夜几个小时之后。所有这些地方在不同的时刻都经历了相同的 LocalDateTime,每个交付都由不同的 ZonedDateTime对象代表。

因此...

  • 如果您想在25日午夜之后记录圣诞节的概念,请使用LocalDateTime并写入TIMESTAMP WITHOUT TIME ZONE类型的数据库列。
  • 如果您想记录Santa发送的每次投放的确切时刻,请使用ZonedDateTime并写入TIMESTAMP WITH TIME ZONE类型的数据库列。

关于第二个项目符号,请注意几乎每个数据库系统都将使用区域信息将日期时间调整为UTC并存储该UTC值。有些人也会保存区域信息,但有一些例如Postgres在使用它调整为UTC后会丢弃区域信息。因此,“带有时区”是一种误称,实际上意味着“与时区的尊重”。如果您关心记住该原始区域,则可能需要将其名称存储在一个单独的列中。

使用Local…类型的另一个原因是将来的约会。政客们经常喜欢改变其管辖范围的时区。他们喜欢采用夏令时(DST)。更改DST转换日期的类似内容。他们喜欢放弃采用DST。他们喜欢重新定义他们的时区,改变边界。他们喜欢重新定义他们的UTC偏移量,有时候是15分钟。他们很少提前通知,只需要一两个月的警告即可进行此类更改。

因此,为了明年或六个月进行体检,无法预测时区定义。因此,如果您希望预约上午9点,则应使用LocalTimeLocalDateTime记录在TIMESTAMP WITHOUT TIME ZONE类型的数据库列中。否则上午9点的预约,如果划分DST转换推迟的地方,可能会显示为上午8点或上午10点。

生成预计的计划时,您可以将时区(ZoneId)应用于这些“本地”(未分区)值以创建ZonedDateTime个对象。但是,当政治家可能通过改变区域破坏其意义时,不要依赖那些太远的东西。

提示:这些对夏令时和时区的频繁更改意味着您必须使您的时区tzdata数据库保持最新。您的主机操作系统,JVM以及Postgres等数据库系统中都有一个tzdata。这三个都应该经常更新。有时这些区域的变化速度比那些产品的计划更新周期要快,例如去年土耳其决定只用几个星期通知DST。因此,您可能偶尔需要手动更新这些tzdata文件。 Oracle提供了一个用于更新其Java实现的tzdata的工具。

处理确切时刻的一般最佳做法是以UTC格式跟踪它们。仅在必要时应用时区,例如向用户展示他们希望在自己的教区时区中看到值的位置。在java.time中,Instant类表示时间轴中的一个时刻。在UTC中,分辨率为纳秒。

Instant instant = Instant.now() ;  // Current moment on the timeline in UTC.
ZonedDateTime zdt = instant.atZone( z ) ;  // Assign a time zone to view the same moment through the lens of a particular region’s wall-clock time.
Instant instant = zdt.toInstant();  // revert back to UTC, stripping away the time zone. But still the same moment in the timeline.

顺便说一句,符合JDBC 4.2及更高版本的驱动程序可以通过以下方式直接处理java.time类型:

  • PreparedStatement::setObject
  • ResultSet::getObject

尽可能避免使用旧的旧版数据类型,例如java.util.Datejava.sql.Timestamp。它们设计糟糕,容易混淆,有缺陷。

了解所有这四个都是UTC时间轴上片刻的表示形式:

  • 现代
    • java.time.Instant
    • java.time.OffsetDateTime,指定的偏移量为ZoneOffset.UTC
    • java.util.Date
    • java.sql.Timestamp

如果您想要一个没有时间且没有时区的仅限日期的值,请使用java.time.LocalDate。该课程取代java.sql.Date

对于特定数据库,请注意SQL标准几乎没有涉及日期时间类型及其处理的主题。此外,各种数据库差异很大,我的意思是广泛,支持日期时间功能。有些人几乎没有支持。有些将SQL标准类型与专有类型混合在一起,这些类型要么早于标准类型,要么作为标准类型的替代品。此外,JDBC驱动程序的行为与编组数据库的日期时间值或来自数据库的日期时间值不同。务必学习文档和练习,练习,练习。

Table of date-time types in Java (both legacy and modern) and in the SQL standard.

答案 1 :(得分:7)

  

或者我应该使用Java 8的Instant吗?如果使用Instant,会有   是否有可能只存储日期部分,没有时间?

Instant应该适合大多数操作。

hibernate-java8添加到pom.xml以支持Java 8时间API:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-java8</artifactId>
    <version>${version.hibernate}</version>
</dependency>

然后,您可以将LocalDateLocalDateTimeInstant用于Hibernate实体字段。您需要删除@Temporal(TemporalType.TIMESTAMP)

  

我的要求是存储所有日期&amp;以UTC时区为单位的日期时间   数据库。我正在使用Java 8的LocalDate&amp;我的LocalDateTime   Hibernate实体。

     

这是正确的LocalDate&amp; LocalDateTime没有时区   与他们相关?

您可以在配置代码中的某处设置默认JVM时区:

@PostConstruct
void setUTCTimezone() {
    TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}

然后你将在你的代码中运行UTC时间。

要在DTO中使用Java 8日期类型,您需要添加Jsr310JpaConverters

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

@EntityScan(basePackageClasses = { Application.class,    Jsr310JpaConverters.class })
SpringBootApplication
public class Application { … }

更多选项:

答案 2 :(得分:-4)

新的Date API的一部分,他们将日期类型分开。包含时区的正确类是ZonedDateTime

// Get the current date and time
      ZonedDateTime date1 = ZonedDateTime.parse("2007-12-03T10:15:30+05:30[Asia/Karachi]");
      System.out.println("date1: " + date1);

      ZonedDateTime zonedDateTime = ZonedDateTime.now();
      System.out.println("Zoned Date Time: " + zonedDateTime);

      ZoneId id = ZoneId.of("Europe/Paris");
      System.out.println("ZoneId: " + id);

      ZoneId currentZone = ZoneId.systemDefault();
      System.out.println("CurrentZone: " + currentZone);

打印:

date1: 2007-12-03T10:15:30+05:00[Asia/Karachi]
Zoned Date Time: 2017-04-18T11:36:09.126-04:00[America/New_York]
ZoneId: Europe/Paris
CurrentZone: America/New_York