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

这可能是一个带有隐含/直接答案的愚蠢问题,但不知怎的,我无法绕过它。

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

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

如果没有,我应该回到使用旧的(或传统的?) DateTimestamp

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

如果它有所不同,我希望它们工作的数据库是MySQL和SQL Server。

同样,如果它有进一步的区别,那么它是一个Spring Boot应用程序。

谢谢!!

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

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

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

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

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

所以…

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

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

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

因此,为了明年或六个月进行体检,无法预测时区定义。 因此,如果您希望预约9 AM,则应使用记录在TIMESTAMP WITHOUT TIME ZONE类型的数据库列中的LocalTimeLocalDateTime 。 否则上午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标准几乎没有涉及日期时间类型及其处理的主题。 此外,各种数据库差别很大,我的意思是广泛支持日期时间function。 有些人几乎没有支持。 有些将SQL标准类型与专有类型混合在一起,这些类型要么早于标准类型,要么作为标准类型的替代品。 此外,JDBC驱动程序的行为与编组数据库的日期时间值或来自数据库的日期时间值不同。 务必学习文档和练习,练习,练习。

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

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

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

  org.hibernate hibernate-java8 ${version.hibernate}  

然后,您可以使用LocalDateLocalDateTimeInstant for Hibernate实体字段。 你需要删除@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 :

  org.springframework.boot spring-boot-starter-data-jpa  

和:

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

更多的选择:

  • 强制Java时区为GMT / UTC
  • 如何设置java时区?
  • 如何正确设置JVM TimeZone

作为新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