1. 概述
本文将深入探讨在 Hibernate 中如何映射各种日期时间类型,涵盖 java.sql
、java.util
和 java.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
→ SQLDATE
(仅日期)Time
→ SQLTIME
(时分秒)Timestamp
→ SQLTIMESTAMP
(日期+时间,纳秒精度)
映射方式简单粗暴,直接使用 @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 中日期时间类型的映射方案:
- java.sql 类型:与 SQL 直接对应,但包含废弃 API
- java.util.Date/Calendar:需配合
@Temporal
使用,存在精度问题 - java.time 类型:现代 API,直接映射,类型安全
🚀 最佳实践:新项目直接使用
java.time
API,配合全局时区配置,可避免绝大多数日期时间问题
完整示例代码见 GitHub 仓库。