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.Calendarjava.util.Date默認類型使用TIMESTAMP類型在數據庫中表示它們。

4.2 新時間

目前,有了地圖,一切都變得更加簡單和美好。所有數據庫都支持 4 種類型的數據來處理時間:

  • DATE - 日期:年、月、日。
  • TIME - 時間:時、分、秒。
  • TIMESTAMP - 日期、時間和納秒。
  • TIMESTAMP WITH TIME ZONE - 時間戳和時區(時區名稱或偏移量)。

來表示類型 日期在 Java 中,您需要使用java.time.LocalDateJDK 8 DateTime API 中的一個類。

類型時間來自數據庫的數據可以用 Java 中的兩種類型表示:java.time.LocalTimejava.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 類型中。事實上,這是合乎邏輯的,因為 TimeZone 通常有一個字符串名稱,如“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

我真的希望您不會經常在代碼中看到這一點。