Java 8日期与数据库日期的映射关系

2021-09-24 18:49 阅读

Java8中有很多新型的日期类型,比传统的日期类型好用。使用什么和数据库的日期进行映射,却是一个比较复杂的问题。

JDBC 4.2 规范

根据JDBC4.2的规范,Java日期类型和数据库日期类型关系如下:

Java 日期 数据库日期
java.sql.Date DATE
java.sql.Time TIME
java.sql.Timestamp TIMESTAMP
java.util.Calendar TIMESTAMP
java.util.Date TIMESTAMP
java.time.LocalDate DATE
java.time.LocalTime TIME
java.time.LocalDateTime TIMESTAMP
java.time.OffsetTime TIME_WITH_TIMEZONE
java.time.OffsetDatetime TIMESTAMP_WITH_TIMEZONE

有两个是比较特别的。

  • TIMESTAMP_WITH_TIMEZONE:包含 Time Zone 的日期时间(DateTime),映射为OffsetDatetime
  • TIME_WITH_TIMEZONE:包含 Time Zone 的时间(Time),映射为OffsetTime

java8中新的日期类型代替旧日期类型

  • java.time.LocalDate代替java.sql.Date
  • java.time.LocalTime代替java.sql.Time
  • java.time.LocalDateTime代替java.sql.Timestamp

注意:JDBC4.2规范不支持InstantZonedOffsetDateTime

PostgreSQL JDBC 实现

Java 日期 数据库日期
java.time.LocalDate DATE
java.time.LocalTime TIME[ WITHOUT TIME ZONE ]
java.time.LocalDateTime TIMESTAMP [ WITHOUT TIME ZONE ]
java.time.OffsetDatetime TIMESTAMP WITH TIME ZONE

除了不支持InstantZonedOffsetDateTime外,OffsetTime也不支持。

参考:PostgreSQL JDBC: Using Java 8 Date and Time classes

PostgreSQL 数据库

timestamp类似LocalDateTime,只是本地时间。要确保JVM的时区和数据库的时区一致,否则会出现时差。

timestamptzTIMESTAMP WITH TIME ZONE类型,但并没有保存 Time Zone 信息,只是简单的使用UTC标准时间。理由是Time Zone只用于显示,而如何显示时间应该由应用程序处理,没有必要保存到数据库中。

MySQL 数据库

MySQL甚至没有提供TIMESTAMP WITH TIME ZONE的类型,日期时间类型只有DateTime,没有 Time Zone 概念。必须使用jdbc连接中的serverTimezone确定时区。如jdbc:mysql://localhost/ujcms?serverTimezone=Asia/Shanghai

Oracle 数据库

Date:本地时间。精度到秒。
Timestamp:本地时间。精度可以到纳秒。
TIMESTAMP WITH TIME ZONE:标准的OffsetDateTime,保存有Time Zone 信息。
TIMESTAMP WITH LOCAL TIME ZONE:和PostgresSQL的timestamptz类似,只保存标准的UTC时间,然后根据本地的 Time Zone 进行计算。

SQL Server 数据库

datetime2:本地时间。
datetimeoffset:标准的OffsetDateTime,保存有 Time Zone 信息。

JPA

JPA对日期的支持于JDBC规范是一致的。

Hibernate

Hibernate在JPA的基础上进行了扩展,支持Instant、ZonedDateTime。

但所有的Java8日期类型最后都转换成Timestamp进行处理。也就是说即使数据库支持TIMESTAMP WITH TIME ZONE并保存了时区信息,Hibernate也会将其丢弃,转而使用JVM的时区(时间是确保正确的)。

MyBatis

支持Instant。转为Timestamp处理。

支持ZonedDateTime,直接使用原生的。兼容性差,如PostgreSQL JDBC不支持这种类型的,会报错。

Freemarker

使用freemarker-java-8进行格式化。

支持OffsetDateTime和ZonedDateTime的格式化,使用对象中自带的时区。

不支持Instant格式化,会直接调用toString()方法。官方说会增加Instant的支持,但已经3年没有发布新版本。

Thymeleaf

支持OffsetDateTime和ZonedDateTime的格式化,使用对象中自带的时区。

支持Instant格式化,使用JVM默认时区。

Jackson

  • Instant:2008-08-08T08:00:00Z
  • OffsetDateTime:与Instant一致。
  • ZondDateTime:2008-08-08T08:00:00Z[UTC]

如何选择

LocalDateTime虽然日期显示友好,但时区不确定,取决于JVM的时区。这导致时间也不确定,不同时区的JVM访问数据库,会得到不一样的时间。这非常致命,使用LocalDateTime一定要确保JVM和数据库的时区一致。

按照JDBC规范,毫无疑问应该选择OffsetDateTime。OffsetDateTime是一个好选择,具有像LocalDateTime一样直观友好的日期显示,又能确保时间的确定性。

但由于MySQL和PostgreSQL都没有提供真正的保存时区的TIMESTAMP WITH TIME ZONE,OffsetDateTime其实已经降级为Instant(PostgresSQL的timestamptz本质上就是Instant)。特别是PostgreSQL提供的是一个标准UTC时区,而实际需要的是UTC+8的北京时间,这导致在Freemarker和Thymeleaf中都无法得到正确的格式化。

考虑到数据库兼容性的问题,Instant似乎是一个更好的选择。但JDBC4.2及JDBC4.3都不支持Instant,且Instant在Freemarker中也无法格式化。

大部分数据库都提供真正的TIMESTAMP WITH TIME ZONE,即使是MySQL也能通过设置serverTimezone得到时区正确的OffsetDateTime,再加上JDBC规范的要求,OffsetDateTime还是首选。至于PostgreSQL的兼容性,可以在FreeMarker和Thymeleaf中自定义日期格式化方法。

咨询
交流群
电话