在数据库中使用Java 8 LocalDate和LocalDateTime进行Hibernate

adi*_*adi 20 java hibernate utc java-time

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

那是正确的,因为LocalDateLocalDateTime不具有与之相关联的时区?

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

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

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

Bas*_*que 46

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

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

LocalDateTime ldt = LocalDateTime.of( 2017 , 12 , 25 , 0 , 0 , 0 , 0 );
Run Code Online (Sandbox Code Playgroud)

但午夜时分发生在东部早于西部.

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

所以…

  • 如果要在25日午夜之后记录圣诞节的概念,请使用a LocalDateTime并写入类型的数据库列TIMESTAMP WITHOUT TIME ZONE.
  • 如果要记录Santa发送的每个传递的确切时刻,请使用a 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.
Run Code Online (Sandbox Code Playgroud)

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

  • PreparedStatement::setObject
  • ResultSet::getObject

避免旧的遗留数据类型,如InstantZonedDateTime尽可能.它们设计糟糕,容易混淆,有缺陷.

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

  • 现代
    • OffsetDateTime
    • java.util.Date 指定的偏移量 java.sql.Timestamp
  • 遗产
    • java.time.Instant
    • java.time.OffsetDateTime

如果您想要一个没有时间且没有时区的仅限日期的值,请使用ZoneOffset.UTC.这个课程取代了java.util.Date.

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

  • 我真的希望我8岁的女儿(声称圣诞老人不是真实的人)能读懂这段时间。不仅是一个很好的答案,而且当我到达“那就是精灵的原因..”的时候,我大受鼓舞。:) (4认同)

Jus*_*tas 7

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

瞬间应该适合大多数操作.

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

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-java8</artifactId>
    <version>${version.hibernate}</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

然后你可以使用LocalDateLocalDateTimeInstant对Hibernate的实体领域.你需要删除@Temporal(TemporalType.TIMESTAMP).

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

这是正确的,因为LocalDate和LocalDateTime没有与它们相关的时区?

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

@PostConstruct
void setUTCTimezone() {
    TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}
Run Code Online (Sandbox Code Playgroud)

然后,您将在代码中运行UTC时间.

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Run Code Online (Sandbox Code Playgroud)

和:

@EntityScan(basePackageClasses = { Application.class,    Jsr310JpaConverters.class })
SpringBootApplication
public class Application { … }
Run Code Online (Sandbox Code Playgroud)

更多的选择: