在数据库中使用 Java 8 LocalDate & LocalDateTime 休眠

2022-09-01 11:21:13

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

这是正确的,因为&gt没有与它们相关的时区?LocalDateLocalDateTime

如果没有,我应该回退到使用旧的(或旧的)吗? & ?DateTimestamp

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

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


答案 1

“本地...”类型故意没有时区的概念。因此,它们并不代表时间轴上的某个时刻。A 表示可能时刻的模糊范围,但在分配偏移量或时区之前没有实际意义。这意味着应用 a 来获取 .LocalDateTimeZoneIdZonedDateTime

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

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

但是,午夜的中风在东方比在西方发生得更早。

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

所以。。。

  • 如果要记录圣诞节的概,从25日午夜之后开始,请使用a并写入类型为的数据库列。LocalDateTimeTIMESTAMP WITHOUT TIME ZONE
  • 如果要记录圣诞老人每次交付的确切时刻,请使用 a 并写入类型为 的数据库列。ZonedDateTimeTIMESTAMP WITH TIME ZONE

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

使用类型的另一个原因是用于将来的约会。政治家喜欢经常改变其管辖的时区。他们喜欢采用夏令时 (DST)。喜欢更改其 DST 转换的日期。他们喜欢放弃对 DST 的采用。他们喜欢重新定义自己的时区,改变边界。他们喜欢重新定义他们的UTC偏移量,有时大约是15分钟。他们很少提前通知,在短短一两个月的警告下做出这样的改变。Local…

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

生成投影明细表时,可以将时区 () 应用于这些“本地”(未分区)值以创建对象。但是,不要依赖那些时间太远的人,因为政客们可能会通过改变区域来破坏它们的意义。ZoneIdZonedDateTime

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

处理精确时刻的一般最佳实践是使用 UTC 方式跟踪它们。仅在必要时(例如在演示文稿中)将时区应用于用户,他们希望在自己的教区时区中看到值。在 java.time 中,该类表示时间轴中的某个时刻。采用 UTC 格式,分辨率为纳秒。Instant

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 类型:& .规范确实需要支持 。因此,您可以轻松地来回转换。InstantZonedDateTimeOffsetDateTime

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

请注意,这四者都是 UTC 时间轴上某个时刻的表示形式:

  • 摩登
    • java.time.Instant
    • java.time.OffsetDateTime指定的偏移量为ZoneOffset.UTC
  • 遗产
    • java.util.Date
    • java.sql.Timestamp

如果需要不带时间且不带时区的仅日期值,请使用 。这个类取代了 。java.time.LocalDatejava.sql.Date

对于特定数据库,请注意,SQL 标准几乎没有涉及日期时间类型及其处理的主题。此外,各种数据库差异很大,我的意思是广泛,因为它们支持日期时间功能。有些几乎没有任何支持。有些将 SQL 标准类型与专有类型混合在一起,这些类型要么早于标准类型,要么旨在作为标准类型的替代类型。此外,JDBC 驱动程序的行为不同,它们将日期时间值封送到/从数据库封送。一定要学习文献和实践,实践,实践。

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


答案 2

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

即时应该适合大多数操作。

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

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

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

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

这是正确的吗,因为LocalDate和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 { … }

更多选项:


推荐