编程

持久化 LocalDateTime、ZonedDateTime 以及与 Hibernate 协作

518 2024-10-14 18:55:00

在 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 typeJDBC type
java.time.DurationBIGINT
java.time.InstantTIMESTAMP
java.time.LocalDateTimeTIMESTAMP
java.time.LocalDateDATE
java.time.LocalTimeTIME
java.time.OffsetDateTimeDepends on Hibernate version:
TIMESTAMP, TIMESTAMP_WITH_TIMEZONE, 2 columns
java.time.OffsetTimeTIME
java.time.ZonedDateTimeDepends on Hibernate version:
TIMESTAMP, TIMESTAMP_WITH_TIMEZONE, 2 columns

如你所见 ,除了 OffsetDateTimeZonedDateTime 之外,所有类型的映射都很简单。但这两种类型的映射有点复杂。原因是并非所有数据库都支持 TIMESTAMP_WITH_TIMEZONE 字段类型。

我解释了不同的映射选项、它们可用的 Hibernate 版本,现在在 Hibernate 的 TimezoneStorageType 指南中配置它们。
以下是简述:

  • Hibernate 5 将 OffsetDateTimeZonedDateTime 标准化为应用的时区,并将其存储在 TIMESTAMP 类型的列中,不包含时区信息。
  • Hibernate 6 引入了 @TimezoneStorageType 注解和一个配置参数。它们使你能够指定 OffsetDateZonedDateTime 的处理方式。你可以:
    • 将其保存到字段类型 TIMESTAMP_WITH_TIMEZONE
      • 将时间戳标准化为 UTC,并将其存储在 TIMESTAMP 类型的列中,不包含时区信息。
    • 将没有时区信息的时间戳存储在 TIMESTAMP 类型的列中,并存储时间戳的偏移量
    • 将没有时区信息的时间戳存储在 TIMESTAMP 类型的列中,并将时间戳的时区到 UTC 的偏移量存储在单独的字段中。

将日期和时间 API 类作为实体属性

Hibernate 支持日期和时间 API 的类作为基础类型。这提供了一个主要优点,即你不必添加任何额外的注解。即使是当前添加到每个java.util.Date 中的 @Temporal  注解也不用。

Hibernate 从属性的类型中获取所有必需的信息。你可以在以下代码段中看到一个具有 LocalDateLocalDateTimeDuration 类型属性的实体示例。

@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 中改进了对 ZonedDateTimeOffsetDateTime 的支持。

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 规范要求更多的日期类型。

使用 OffsetDateTimeZonedDateTime 时,你应该特别注意 Hibernate 如何存储时区信息。在版本 5 中,Hibernate 将时间戳标准化为应用的时区,并将其存储在数据库中,不包含时区信息。从 Hibernate ORM 6 开始,您你该使用 @TimezoneStorageType 注释来指定时区处理。