1. 概述

JPA 2.2 正式引入了对 Java 8 新日期时间 API 的支持。在此之前,我们只能依赖一些自定义的解决方案,或者使用 JPA 的 AttributeConverter 来手动转换。

在本篇文章中,我们将介绍如何将各种 Java 8 的日期时间类型映射到数据库字段,特别关注那些支持时区偏移信息的类型。

2. Maven 依赖

在开始之前,我们需要在项目中引入 JPA 3.1 API。如果你使用的是 Maven 工程,只需要在 pom.xml 文件中添加以下依赖:

<dependency>
    <groupId>jakarta.persistence</groupId>
    <artifactId>jakarta.persistence-api</artifactId>
    <version>3.1.0</version>
</dependency>

注意:如果你已经引入了 hibernate-core 依赖,那么 jakarta.persistence-api 会被自动作为传递依赖引入,无需手动添加。

此外,为了运行这个项目,我们还需要一个 JPA 实现和数据库的 JDBC 驱动。在本例中,我们选择使用 EclipseLinkPostgreSQL

<dependency>
    <groupId>org.eclipse.persistence</groupId>
    <artifactId>eclipselink</artifactId>
    <version>4.0.1</version>
    <scope>runtime</scope>
    <exclusions>
      <exclusion>
         <groupId>jakarta.xml.bind</groupId>
         <artifactId>jakarta.xml.bind-api</artifactId>
      </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.5.4</version>
    <scope>runtime</scope>
</dependency>

📌 你可以随时在 Maven Central 上查看 JPA APIEclipseLinkPostgreSQL JDBC 驱动 的最新版本。

当然,你也可以使用其他数据库或 JPA 实现,比如 Hibernate。

3. 时区支持

虽然我们可以使用任意数据库,但在使用前最好先确认数据库是否支持以下标准 SQL 类型(JDBC 4.2 基于这些类型):

  • TIMESTAMP(n) WITH TIME ZONE
  • TIMESTAMP(n) WITHOUT TIME ZONE
  • TIME(n) WITH TIME ZONE
  • TIME(n) WITHOUT TIME ZONE

其中 n 是小数秒的精度,范围是 0 到 9 位数字。WITHOUT TIME ZONE 是可选的,可以省略。如果使用 WITH TIME ZONE,则必须提供时区名称或相对于 UTC 的偏移量。

时区信息可以用以下两种格式表示:

  • 时区名称(如 Asia/Shanghai)
  • 相对于 UTC 的偏移量,或者字母 Z 表示 UTC

在本例中我们选择了 PostgreSQL,因为它完整支持 TIME WITH TIME ZONE 类型。

⚠️ 其他数据库可能不支持这些类型,使用时需要特别注意。

4. Java 8 之前的日期时间类型映射

在 Java 8 之前,我们通常将 SQL 类型 TIMEDATETIMESTAMP 映射为:

  • java.sql.Time
  • java.sql.Date
  • java.sql.Timestamp

或者使用 java.util.Datejava.util.Calendar

首先来看 java.sql 类型的使用方式。我们只需在 @Entity 类中定义这些类型的字段即可:

@Entity
public class JPA22DateTimeEntity {

    private java.sql.Time sqlTime;
    private java.sql.Date sqlDate;
    private java.sql.Timestamp sqlTimestamp;
    
    // ...
}

java.sql 类型无需额外注解,可以直接使用。

java.util 类型则需要通过 @Temporal 注解来指定对应的 SQL 类型:

@Temporal(TemporalType.TIME)
private java.util.Date utilTime;

@Temporal(TemporalType.DATE)
private java.util.Date utilDate;

@Temporal(TemporalType.TIMESTAMP)
private java.util.Date utilTimestamp;

📌 使用 Hibernate 时,注意它 不支持将 Calendar 映射为 TIME 类型

同样,也可以使用 Calendar 类:

@Temporal(TemporalType.TIME)
private Calendar calendarTime;

@Temporal(TemporalType.DATE)
private Calendar calendarDate;

@Temporal(TemporalType.TIMESTAMP)
private Calendar calendarTimestamp;

⚠️ 但这些类型都不支持时区或偏移信息。为处理时区,传统做法是统一转换为 UTC 时间再存储。

5. Java 8 日期时间类型映射

Java 8 引入了 java.time 包,JDBC 4.2 也增加了对 TIMESTAMP WITH TIME ZONETIME WITH TIME ZONE 类型的支持。

我们现在可以将 JDBC 类型 TIMEDATETIMESTAMP 映射为以下 java.time 类型:

  • LocalTime
  • LocalDate
  • LocalDateTime

示例代码如下:

@Column(name = "local_time", columnDefinition = "TIME")
private LocalTime localTime;

@Column(name = "local_date", columnDefinition = "DATE")
private LocalDate localDate;

@Column(name = "local_date_time", columnDefinition = "TIMESTAMP")
private LocalDateTime localDateTime;

此外,还支持带偏移量的类型:

  • OffsetTime
  • OffsetDateTime
@Column(name = "offset_time", columnDefinition = "TIME WITH TIME ZONE")
private OffsetTime offsetTime;

@Column(name = "offset_date_time", columnDefinition = "TIMESTAMP WITH TIME ZONE")
private OffsetDateTime offsetDateTime;

📌 对应的数据库列类型应该是 TIME WITH TIME ZONETIMESTAMP WITH TIME ZONE。但并不是所有数据库都支持这两种类型。

✅ JPA 原生支持以上五种类型,无需额外配置即可自动识别是日期还是时间。

保存实体后,可以验证数据是否正确写入:

date time

6. 总结

在 Java 8 和 JPA 2.2 之前,开发者通常需要将日期时间统一转换为 UTC 后再持久化。而现在,JPA 2.2 原生支持时区偏移,并借助 JDBC 4.2 的能力,使得处理时区变得简单粗暴。

完整示例代码可在 GitHub 上找到。


原始标题:JPA Support for java.time Types | Baeldung