4.1 历史的补充
将 Java 对象保存到数据库的任务几乎是在 Java 语言创建后立即发生的。当时,Java 语言中只有一种数据类型 Date,它按照 UNIX 时间标准存储时间:自 1970 年以来的毫秒数。
嗯,在当时的数据库中,日期已经有不同的数据类型,至少日期、时间和日期+时间有不同的类型:
- 日期
- 时间
- 时间戳
因此,Java 语言的创建者为其添加了一个特殊的包——java.sql,其中包含类:
- java.sql.日期
- java.sql.时间
- java.sql.时间戳
映射这些类是一种真正的乐趣:
@Entity
public class TemporalValues {
@Basic
private java.sql.Date sqlDate;
@Basic
private java.sql.Time sqlTime;
@Basic
private java.sql.Timestamp sqlTimestamp;
}
但是由于程序员习惯于使用类java.util.Date
,Hibernate 添加了一个特殊的注释@Temporal
来控制 Date 类型的映射。
例子:
// If the annotation is missing, then the database will have a TIMESTAMP type
Date dateAsTimestamp;
@Temporal(TemporalType.DATE) // will be mapped to DATE type
Date dateAsDate;
@Temporal(TemporalType.TIME) // will be mapped to TIME type
Date dateAsTime;
类型java.util.Calendar
和java.util.Date
默认类型使用TIMESTAMP类型在数据库中表示它们。
4.2 新时间
目前,有了地图,一切都变得更加简单和美好。所有数据库都支持 4 种类型的数据来处理时间:
- DATE - 日期:年、月、日。
- TIME - 时间:时、分、秒。
- TIMESTAMP - 日期、时间和纳秒。
- TIMESTAMP WITH TIME ZONE - 时间戳和时区(时区名称或偏移量)。
来表示类型 日期在 Java 中,您需要使用java.time.LocalDate
JDK 8 DateTime API 中的一个类。
类型时间来自数据库的数据可以用 Java 中的两种类型表示:java.time.LocalTime
和java.time.OffsetTime
. 没什么复杂的。
以及类型所代表的确切日期和时间时间戳在基础中,在 Java 中它可以用 4 种类型表示:
- java.time.Instant
- java.time.LocalDateTime
- java.time.OffsetDateTime
- java.time.ZonedDateTime
最后带时区的时间戳可以用两种类型表示:
- java.time.OffsetDateTime
- java.time.ZonedDateTime
由于您已经熟悉DateTime API,因此记住这件事对您来说并不困难 :)
映射它们是纯粹的乐趣:
@Basic
private java.time.LocalDate localDate;
@Basic
private java.time.LocalTime localTime;
@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;
注释@Basic
意味着该字段应该被自动处理:Hibernate 将决定该字段应该映射到哪个列和类型。
4.3 使用时区
如果时区是日期的一部分,那么将它们存储在数据库中就很简单——就像常规日期一样:
@Basic
private java.time.OffsetDateTime offsetDateTime;
@Basic
private java.time.ZonedDateTime zonedDateTime;
但是,如果您想将时区与日期分开存储:
@Basic
private java.time.TimeZone timeZone;
@Basic
private java.time.ZoneOffset zonedOffset;
然后 Hibernate 会默认将它们存储在 VARCHAR 类型中。事实上,这是合乎逻辑的,因为时区通常有一个字符串名称,如“UTC + 3”或“Cairo”。
4.4 设置自己的时区
当您将日期保存到数据库时,您会发现已经有 4 个地方可以设置当前时区:
- 服务器操作系统;
- 数据库管理系统;
- Java应用程序
- 休眠。
如果 DBMS 没有指定时区(TimeZone),那么它将从操作系统设置中获取。这可能很不方便,因为备份 DBMS 通常位于具有自己时区的其他数据中心。
因此,几乎所有的 DBMS 管理员都设置了一个区域,以便可以轻松地将数据从一台服务器传输到另一台服务器。
Java 应用程序的情况类似。它也可以在不同数据中心的不同服务器上运行,所以它通常有一个明确的时区。
java -Duser.timezone=UTC ...
或者在程序运行时:
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
当然,Hibernate 允许您明确设置时区。
首先,可以在配置SessionFactory时指定:
settings.put(
AvailableSettings.JDBC_TIME_ZONE,
TimeZone.getTimeZone("UTC")
);
其次,可以指定时区对于特定会话:
Session session = sessionFactory()
.withOptions()
.jdbcTimeZone(TimeZone.getTimeZone("UTC"))
.openSession();
4.5 @TimeZoneStorage注解
经常发生的情况是,程序员开始设计一个基于在一个国家(和一个时区)工作的数据库,然后在几年后他们需要添加对在不同时区工作的支持。
因此,他们只是在数据库中添加了一个单独的列来存储时区。这种情况很常见,Hibernate 添加了一个特殊的注释,允许您将特定日期的时区存储在单独的列中。
例子:
@TimeZoneStorage(TimeZoneStorageType.COLUMN)
@TimeZoneColumn(name = "birthday_offset_offset")
@Column(name = "birthday_offset")
private OffsetDateTime offsetDateTimeColumn;
@TimeZoneStorage(TimeZoneStorageType.COLUMN)
@TimeZoneColumn(name = "birthday_zoned_offset")
@Column(name = "birthday_zoned")
private ZonedDateTime zonedDateTimeColumn;
这是拐杖。但它也有一个借口:它出现在 DateTime API 还不存在的时候。并且不可能将 TimeZone 存储在类中java.util.Date
。
我真的希望您不会经常在代码中看到这一点。
GO TO FULL VERSION