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 類型中。事實上,這是合乎邏輯的,因為 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
。
我真的希望您不會經常在代碼中看到這一點。