Hibernate – Mapping Date and Time – Hibernate – 映射日期和时间

最后修改: 2017年 11月 5日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll show how to map temporal column values in Hibernate, including the classes from java.sql, java.util and java.time packages.

在本教程中,我们将展示如何在Hibernate中映射时间列值,包括来自java.sqljava.utiljava.time包的类。

2. Project Setup

2.项目设置

To demonstrate the mapping of the temporal types, we’re going to need the H2 database and the latest version of the hibernate-core library:

为了演示时态类型的映射,我们需要H2数据库和最新版本的hibernate-core库。

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.4.12.Final</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.194</version>
</dependency>

For the current version of the hibernate-core library, head over to the Maven Central repository.

关于当前版本的hibernate-core库,请前往Maven Central仓库。

3. Time Zone Setup

3.时区设置

When dealing with dates, it’s a good idea to set a specific time zone for the JDBC driver. This way, our application will be independent of the current time zone of the system.

当处理日期时,为JDBC驱动程序设置一个特定的时区是一个好主意。这样,我们的应用程序将不受系统当前时区的影响。

For our example, we’ll set it up on a per-session basis:

对于我们的例子,我们将在每个会话的基础上设置它。

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

Another way would be to set up the hibernate.jdbc.time_zone property in the Hibernate properties file that is used to construct the session factory. This way, we could specify the time zone once for the entire application.

另一种方法是在用于构建会话工厂的Hibernate属性文件中设置hibernate.jdbc.time_zone属性。这样一来,我们就可以为整个应用程序指定一次时区。

4. Mapping java.sql Types

4.映射java.sql类型

The java.sql package contains JDBC types that are aligned with the types defined by the SQL standard:

java.sql包包含与SQL标准定义的类型一致的JDBC类型。

  • Date corresponds to the DATE SQL type, which is only a date without time.
  • Time corresponds to the TIME SQL type, which is a time of the day specified in hours, minutes and seconds.
  • Timestamp includes information about date and time with precision up to nanoseconds and corresponds to the TIMESTAMP SQL type.

These types are in line with SQL, so their mapping is relatively straightforward.

这些类型与SQL是一致的,所以它们的映射是相对简单的。

We can use either the @Basic or the @Column annotation:

我们可以使用@Basic@Column注释。

@Entity
public class TemporalValues {

    @Basic
    private java.sql.Date sqlDate;

    @Basic
    private java.sql.Time sqlTime;

    @Basic
    private java.sql.Timestamp sqlTimestamp;

}

We could then set the corresponding values:

然后我们可以设置相应的值。

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"));

Note that choosing java.sql types for entity fields may not always be a good choice. These classes are JDBC-specific and contain a lot of deprecated functionality.

请注意,为实体字段选择java.sql类型可能并不总是一个好的选择。这些类是针对JDBC的,包含了很多被废弃的功能。

5. Mapping java.util.Date Type

5.映射java.util.Date类型

The type java.util.Date contains both date and time information, up to millisecond precision. But it doesn’t directly relate to any SQL type.

类型java.util.Date包含了日期和时间信息,精度高达毫秒。但是它与任何SQL类型没有直接关系。

This is why we need another annotation to specify the desired SQL type:

这就是为什么我们需要另一个注解来指定所需的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;

The @Temporal annotation has the single parameter value of type TemporalType. It can be either DATE, TIME or TIMESTAMP, depending on the underlying SQL type that we want to use for the mapping.

@Temporal注解有TemporalType类型的单一参数值。它可以是DATETIMETIMESTAMP,这取决于我们想用于映射的基础SQL类型。

We could then set the corresponding fields:

然后我们可以设置相应的字段。

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"));

As we’ve seen, the java.util.Date type (milliseconds precision) is not precise enough to handle the Timestamp value (nanoseconds precision).

正如我们所看到的,java.util.Date类型(毫秒级精度)不足以处理时间戳值(纳秒级精度)。

So, when we retrieve the entity from the database, we’ll unsurprisingly find a java.sql.Timestamp instance in this field, even if we initially persisted a java.util.Date:

因此,当我们从数据库中检索实体时,我们会毫不意外地在这个字段中找到一个java.sql.Timestamp实例,即使我们最初坚持的是java.util.Date

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

This should be fine for our code since Timestamp extends Date.

这对我们的代码来说应该没有问题,因为Timestamp扩展了Date

6. Mapping java.util.Calendar Type

6.映射java.util.Calendar类型

As with the java.util.Date, the java.util.Calendar type may be mapped to different SQL types, so we have to specify them with @Temporal.

java.util.Date一样,java.util.Calendar类型可能被映射到不同的SQL类型,所以我们必须用@Temporal来指定它们。

The only difference is that Hibernate does not support mapping Calendar to TIME:

唯一的区别是,Hibernate不支持将Calendar映射到TIME

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

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

Here’s how we can set the value of the field:

下面是我们如何设置该字段的值。

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);

7. Mapping java.time Types

7.映射java.time类型

Since Java 8, the new Java Date and Time API is available for dealing with temporal values. This API fixes many of the problems of java.util.Date and java.util.Calendar classes.

自Java 8以来,新的Java Date and Time API可用于处理时间值。该API修复了java.util.Datejava.util.Calendar类中的许多问题。

The types from the java.time package are directly mapped to corresponding SQL types.

来自 java.time包的类型被直接映射到相应的SQL类型。

So, there’s no need to explicitly specify @Temporal annotation:

所以,没有必要明确指定@Temporal注解。

  • LocalDate is mapped to DATE.
  • LocalTime and OffsetTime are mapped to TIME.
  • Instant, LocalDateTime, OffsetDateTime and ZonedDateTime are mapped to TIMESTAMP.

This means that we can mark these fields only with @Basic (or @Column) annotation:

这意味着我们只能用@Basic(或@Column)注解来标记这些字段。

@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;

Every temporal class in the java.time package has a static parse() method to parse the provided String value using the appropriate format.

java.time包中的每个时间类都有一个静态的parse()方法,以使用适当的格式解析提供的String值。

So, here’s how we can set the values of the entity fields:

所以,这里我们可以设置实体字段的值。

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]"));

8. Conclusion

8.结论

In this article, we’ve shown how to map temporal values of different types in Hibernate.

在这篇文章中,我们已经展示了如何在Hibernate中映射不同类型的时间值。

The source code for the article is available over on GitHub.

该文章的源代码可在GitHub上获得