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 驱动。在本例中,我们选择使用 EclipseLink 和 PostgreSQL:
<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 API、EclipseLink 和 PostgreSQL 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 类型 TIME
、DATE
和 TIMESTAMP
映射为:
java.sql.Time
java.sql.Date
java.sql.Timestamp
或者使用 java.util.Date
和 java.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 ZONE
和 TIME WITH TIME ZONE
类型的支持。
我们现在可以将 JDBC 类型 TIME
、DATE
和 TIMESTAMP
映射为以下 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 ZONE
和 TIMESTAMP WITH TIME ZONE
。但并不是所有数据库都支持这两种类型。
✅ JPA 原生支持以上五种类型,无需额外配置即可自动识别是日期还是时间。
保存实体后,可以验证数据是否正确写入:
6. 总结
在 Java 8 和 JPA 2.2 之前,开发者通常需要将日期时间统一转换为 UTC 后再持久化。而现在,JPA 2.2 原生支持时区偏移,并借助 JDBC 4.2 的能力,使得处理时区变得简单粗暴。
完整示例代码可在 GitHub 上找到。