使用数据库中的 Java 8 LocalDate 和 LocalDateTime 休眠

Hibernate with Java 8 LocalDate & LocalDateTime in Database

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

这是否正确,因为 LocalDateLocalDateTime 没有与之关联的时区?

如果不是,我是否应该退回到使用旧的(或遗留的?)Date & Timestamp

或者我应该使用 Java 8 的 Instant?如果使用Instant,是否可以只存储日期部分,而不存储时间?

数据库是 MySQL & SQL 服务器,这是一个 Spring 引导应用程序。

新日期的一部分 API 他们将日期类型分开。包含时区的正确 class 是 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

“本地...”类型故意没有时区概念。所以他们 而不是 代表时间轴上的一个时刻。 LocalDateTime 表示可能时刻的模糊范围,但在分配偏移量或时区之前没有实际意义。这意味着应用 ZoneId 得到 ZonedDateTime

比如说今年的圣诞节从12月25日的第一刻开始,我们说:

LocalDateTime ldt = LocalDateTime.of( 2017 , 12 , 25 , 0 , 0 , 0 , 0 );

但午夜那声钟声,东比西早。

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

所以……

  • 如果要记录 25 日午夜后开始的圣诞节的 概念,请使用 LocalDateTime 并写入类型为 [=18= 的数据库列].
  • 如果您想记录圣诞老人每次送货的确切时间,请使用 ZonedDateTime 并写入 TIMESTAMP WITH TIME ZONE.
  • 类型的数据库列

关于第二个项目符号,请注意几乎每个数据库系统都会使用区域信息将日期时间调整为 UTC 并存储该 UTC 值。有些也保存时区信息,但有些如 Postgres 在使用它调整为 UTC 后丢弃时区信息。所以“with time zone”有点用词不当,真正的意思是“with respect for time zone”。如果您想记住那个原始区域,您可能需要将其名称存储在旁边的单独列中。

使用 Local… 类型的另一个原因是为了将来的约会。政客们喜欢经常改变他们管辖范围内的时区。他们喜欢采用夏令时 (DST)。他们喜欢更改 DST 转换的日期。他们喜欢放弃采用 DST。他们喜欢重新定义时区,改变界限。他们有时喜欢重新定义他们与 UTC 的偏移量,例如 15 分钟。而且他们很少提前通知,在一两个月的警告前就做出了这样的改变。

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

生成预计时间表时,您可以将时区 (ZoneId) 应用于那些“本地”(未分区)值以创建 ZonedDateTime 对象。但是,当政客们可能会通过改变区域来破坏他们的意义时,不要依赖那些太晚的时间。

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

处理精确时刻的一般最佳做法是在 UTC 中跟踪它们。仅在必要时应用时区,例如在向用户展示时,他们希望在他们自己的狭隘时区中看到值。在java.time中,Instantclass表示时间轴中的一个时刻。在 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

奇怪的是,JDBC 4.2 规范不需要支持两种最常见的 java.time 类型:InstantZonedDateTime.该规范确实需要支持 OffsetDateTime。因此,您可以轻松地来回转换。

尽可能避免使用旧的遗留数据类型,例如 java.util.Datejava.sql.Timestamp。它们设计不佳、令人困惑且存在缺陷。

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

  • 现代
    • java.time.Instant
    • java.time.OffsetDateTime 分配的偏移量为 ZoneOffset.UTC
  • 旧版
    • java.util.Date
    • java.sql.Timestamp

如果您想要一个没有时间和时区的纯日期值,请使用 java.time.LocalDate。 class 取代 java.sql.Date.

至于特定的数据库,请注意 SQL 标准几乎没有涉及日期时间类型及其处理的主题。此外,各种数据库差异很大,我的意思是广泛,因为它们支持日期时间功能。有些人几乎没有支持。有些将 SQL 标准类型与早于标准类型或旨在替代标准类型的专有类型混合使用。此外,JDBC 驱动程序在将日期时间值 to/from 编组到数据库时的行为有所不同。一定要研究文档和实践,实践,实践。

Or should I be using Java 8's Instant? If using Instant, will there be a possibility to store only the date part, without time?

Instant应该适合大多数操作。

pom.xml中添加hibernate-java8以支持Java8次API:

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

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

My requirement is to store all dates & date-times in UTC timezone in the database. I am using Java 8's LocalDate & LocalDateTime in my Hibernate entities.

Is that correct as LocalDate & LocalDateTime doesn't have timezone associated with them?

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

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

然后您将在代码中操作 UTC 时间。

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

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

并且:

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

更多选项:

  • Force Java timezone as GMT/UTC
  • How to set java timezone?
  • How to set a JVM TimeZone Properly