1. 概述

本文将深入探讨在 Hibernate 中如何映射各种日期时间类型,涵盖 java.sqljava.utiljava.time 包中的常用类。通过实际代码示例,展示不同类型在持久化过程中的处理方式及注意事项。

2. 项目准备

为了演示日期时间类型的映射,我们需要以下依赖:

  • H2 内存数据库
  • Hibernate 核心库(最新稳定版)
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core.orm</artifactId>
    <version>6.4.2.Final</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.1.214</version>
</dependency>

💡 提示:最新版本可通过 Maven Central 获取

3. 时区配置

⚠️ 踩坑预警:处理日期时间时,务必为 JDBC 驱动指定时区!这能避免应用依赖系统时区导致的诡异问题。

推荐两种配置方式:

3.1 会话级别配置

session = HibernateUtil.getSessionFactory().withOptions()
  .jdbcTimeZone(TimeZone.getTimeZone("UTC"))
  .openSession();

3.2 全局配置

在 Hibernate 配置文件中设置:

hibernate.jdbc.time_zone=UTC

✅ 最佳实践:生产环境建议使用全局配置,确保整个应用时区一致

4. 映射 java.sql 类型

java.sql 包中的类型与 SQL 标准类型直接对应:

  • Date → SQL DATE(仅日期)
  • Time → SQL TIME(时分秒)
  • Timestamp → SQL TIMESTAMP(日期+时间,纳秒精度)

映射方式简单粗暴,直接使用 @Basic@Column 注解:

@Entity
public class TemporalValues {

    @Basic
    private java.sql.Date sqlDate;

    @Basic
    private java.sql.Time sqlTime;

    @Basic
    private java.sql.Timestamp sqlTimestamp;
}

赋值示例:

temporalValues.setSqlDate(java.sql.Date.valueOf("2017-11-15"));
temporalValues.setSqlTime(java.sql.Time.valueOf("15:30:14"));
temporalValues.setSqlTimestamp(
  java.sql.Timestamp.valueOf("2017-11-15 15:30:14.332"));

⚠️ 注意:这些 JDBC 特定类型包含大量废弃 API,新项目建议使用 java.time

5. 映射 java.util.Date 类型

java.util.Date 包含日期和时间信息(毫秒精度),但无直接对应的 SQL 类型。需要通过 @Temporal 注解指定目标 SQL 类型:

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

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

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

赋值示例:

temporalValues.setUtilDate(
  new SimpleDateFormat("yyyy-MM-dd").parse("2017-11-15"));
temporalValues.setUtilTime(
  new SimpleDateFormat("HH:mm:ss").parse("15:30:14"));
temporalValues.setUtilTimestamp(
  new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
    .parse("2017-11-15 15:30:14.332"));

⚠️ 精度问题java.util.Date(毫秒)无法完整保存 TIMESTAMP(纳秒)精度。从数据库读取时,实际返回的是 java.sql.Timestamp 实例:

temporalValues = session.get(TemporalValues.class, 
  temporalValues.getId());
assertThat(temporalValues.getUtilTimestamp())
  .isEqualTo(java.sql.Timestamp.valueOf("2017-11-15 15:30:14.332"));

✅ 兼容性:由于 Timestamp 继承自 Date,代码层面通常无需特殊处理

6. 映射 java.util.Calendar 类型

Date 类似,Calendar 也需要 @Temporal 指定 SQL 类型。但 Hibernate 不支持映射到 TIME 类型:

@Basic
@Temporal(TemporalType.DATE)
private java.util.Calendar calendarDate;

@Basic
@Temporal(TemporalType.TIMESTAMP)
private java.util.Calendar calendarTimestamp;

赋值示例:

Calendar calendarDate = Calendar.getInstance(
  TimeZone.getTimeZone("UTC"));
calendarDate.set(Calendar.YEAR, 2017);
calendarDate.set(Calendar.MONTH, 10);
calendarDate.set(Calendar.DAY_OF_MONTH, 15);
temporalValues.setCalendarDate(calendarDate);

💡 提示:Calendar 的月份从 0 开始(10=11月),这是常见的踩坑点

7. 映射 java.time 类型

Java 8 引入的日期时间 API(java.time)解决了旧版 API 的诸多问题。其类型与 SQL 类型直接对应:

Java 类型 SQL 类型
LocalDate DATE
LocalTime TIME
OffsetTime TIME
Instant TIMESTAMP
LocalDateTime TIMESTAMP
OffsetDateTime TIMESTAMP
ZonedDateTime TIMESTAMP

无需 @Temporal 注解,直接使用 @Basic 即可:

@Basic
private java.time.LocalDate localDate;

@Basic
private java.time.LocalTime localTimeField;

@Basic
private java.time.OffsetTime offsetTime;

@Basic
private java.time.Instant instant;

@Basic
private java.time.LocalDateTime localDateTime;

@Basic
private java.time.OffsetDateTime offsetDateTime;

@Basic
private java.time.ZonedDateTime zonedDateTime;

赋值示例(所有类型都提供 parse() 方法):

temporalValues.setLocalDate(LocalDate.parse("2017-11-15"));

temporalValues.setLocalTime(LocalTime.parse("15:30:18"));
temporalValues.setOffsetTime(OffsetTime.parse("08:22:12+01:00"));

temporalValues.setInstant(Instant.parse("2017-11-15T08:22:12Z"));
temporalValues.setLocalDateTime(
  LocalDateTime.parse("2017-11-15T08:22:12"));
temporalValues.setOffsetDateTime(
  OffsetDateTime.parse("2017-11-15T08:22:12+01:00"));
temporalValues.setZonedDateTime(
  ZonedDateTime.parse("2017-11-15T08:22:12+01:00[Europe/Paris]"));

✅ 强烈推荐:新项目优先使用 java.time API,类型安全且无历史包袱

8. 总结

本文系统梳理了 Hibernate 中日期时间类型的映射方案:

  1. java.sql 类型:与 SQL 直接对应,但包含废弃 API
  2. java.util.Date/Calendar:需配合 @Temporal 使用,存在精度问题
  3. java.time 类型:现代 API,直接映射,类型安全

🚀 最佳实践:新项目直接使用 java.time API,配合全局时区配置,可避免绝大多数日期时间问题

完整示例代码见 GitHub 仓库


原始标题:Hibernate – Mapping Date and Time