持久化 LocalDateTime、ZonedDateTime 以及与 Hibernate 协作
在 Java 8 中引入的日期和时间 API 最终取代了旧的 java.util.Date
。日期。它使用起来容易得多,最终提供了表示日期、日期与时间以及仅表示时间信息的单独类。这不仅可以改进您的业务代码,还可以更容易地将它们用作实体属性。至少在你使用的是正确版本的 JPA 和/或 Hibernate 时如此。
如果你希望在持久化日期和时间 API 类时使用正确的 JDBC 类型,那么你有三个选项:
- 如果你使用 JPA 版本 < 2.2,或使用 Hibernate 版本 <5,则可以实现 JPA
AttributeConverter
,并将 Java 8 类转换为 Hibernate 支持的类。我在【如何使用 JPA 持久化 LocalDate 和 LocalDateTime】 一文中对此进行了详细描述。这种方法不使用任何特定于 Hibernate 的 API,并且可以移植到其他 JPA 实现中,但有点复杂。 - 你使用 JPA >=2.2 版本。它支持日期和时间 API 的某些类作为基本属性类型。
- 你可以使用 Hibernate 5 引入的 Hibernate 特定的 Java 8 支持。它比 JPA 2.2 支持更多的数据类型。
Hibernate >= 5 中的 Java 8 支持
Hibernate 5 新增的一个特性是支持 Java 8 类,如日期和时间 API。
Hibernate 5.0 和 5.1 中的 Java 8 支持
Java 8 支持最初是在一个名为 hibernate-java8.jar 的单独 jar 文件中提供的,你需要将其添加到应用的类路径中。
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-java8</artifactId>
<version>5.1.0.Final</version>
</dependency>
Hibernate >= 5.2 中的 Java 8 支持
自 5.2 版本始,Hibernate ORM 基于 Java 8,对日期和时间 API 的支持已成为核心发行版的一部分。不再需要额外的 jar 文件。
JDBC 映射
Hibernate 将日期和时间 API 的类映射到相应的 JDBC 类型。下表显示了支持的类及其 JDBC 映射的概述。
Java type | JDBC type |
---|---|
java.time.Duration | BIGINT |
java.time.Instant | TIMESTAMP |
java.time.LocalDateTime | TIMESTAMP |
java.time.LocalDate | DATE |
java.time.LocalTime | TIME |
java.time.OffsetDateTime | Depends on Hibernate version: TIMESTAMP, TIMESTAMP_WITH_TIMEZONE, 2 columns |
java.time.OffsetTime | TIME |
java.time.ZonedDateTime | Depends on Hibernate version: TIMESTAMP, TIMESTAMP_WITH_TIMEZONE, 2 columns |
如你所见 ,除了 OffsetDateTime
和 ZonedDateTime
之外,所有类型的映射都很简单。但这两种类型的映射有点复杂。原因是并非所有数据库都支持 TIMESTAMP_WITH_TIMEZONE
字段类型。
我解释了不同的映射选项、它们可用的 Hibernate 版本,现在在 Hibernate 的 TimezoneStorageType
指南中配置它们。
以下是简述:
- Hibernate 5 将
OffsetDateTime
和ZonedDateTime
标准化为应用的时区,并将其存储在TIMESTAMP
类型的列中,不包含时区信息。 - Hibernate 6 引入了
@TimezoneStorageType
注解和一个配置参数。它们使你能够指定OffsetDate
和ZonedDateTime
的处理方式。你可以:- 将其保存到字段类型
TIMESTAMP_WITH_TIMEZONE
中- 将时间戳标准化为 UTC,并将其存储在 TIMESTAMP 类型的列中,不包含时区信息。
- 将没有时区信息的时间戳存储在 TIMESTAMP 类型的列中,并存储时间戳的偏移量
- 将没有时区信息的时间戳存储在 TIMESTAMP 类型的列中,并将时间戳的时区到 UTC 的偏移量存储在单独的字段中。
- 将其保存到字段类型
将日期和时间 API 类作为实体属性
Hibernate 支持日期和时间 API 的类作为基础类型。这提供了一个主要优点,即你不必添加任何额外的注解。即使是当前添加到每个java.util.Date
中的 @Temporal
注解也不用。
Hibernate 从属性的类型中获取所有必需的信息。你可以在以下代码段中看到一个具有 LocalDate
、LocalDateTime
和 Duration
类型属性的实体示例。
@Entity
public class MyEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private LocalDate date;
private LocalDateTime dateTime;
private Duration duration;
...
}
然后,你可以像 Java 代码中的其他属性一样使用这些属性。
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
MyEntity e = new MyEntity();
e.setDate(LocalDate.now());
e.setDateTime(LocalDateTime.now());
e.setDuration(Duration.ofDays(2));
em.persist(e);
正如下面的屏幕截图所示,Hibernate 使用正确的 JDBC 数据类型来持久化它们,而不是在没有 hibernate-java8.jar
的情况下使用 blob 类型。
小结
Hibernate 在版本 5 中开始支持日期和时间 API 类,并在版本 6 中改进了对 ZonedDateTime
和 OffsetDateTime
的支持。
Hibernate 5.0 和 Hibernate 5.1 仍然基于Java 7,需要一个额外的 jar 文件来支持 Java 8 的任何功能。自从 Hibernate 5.2 以来,日期和时间 API 支持是 hibernate-core 模块的一部分。
Hibernate 将日期和时间 API 的类作为 BasicTypes 处理。这使得它们比旧的 java.util.Date 更容易使用 。你不再需要添加任何额外的注解来定义要映射到的 JDBC 类型。Hibernate 还支持比 JPA 规范要求更多的日期类型。
使用 OffsetDateTime
或 ZonedDateTime
时,你应该特别注意 Hibernate 如何存储时区信息。在版本 5 中,Hibernate 将时间戳标准化为应用的时区,并将其存储在数据库中,不包含时区信息。从 Hibernate ORM 6 开始,您你该使用 @TimezoneStorageType
注释来指定时区处理。