2017-04-18 112 views
7

這可能是一個愚蠢的問題,帶有隱式/直接的答案,但不知何故,我無法將其包圍。在數據庫中使用Java 8 LocalDate和LocalDateTime進行休眠

我的要求是在數據庫中存儲UTC時區的所有日期&日期時間。我在我的Hibernate實體中使用Java 8的LocalDate & LocalDateTime

這是否正確LocalDate & LocalDateTime沒有與他們關聯的時區?

如果不是,我是否應該回到使用舊的(或遺產?)Date & Timestamp

或者我應該使用Java 8的Instant?如果使用Instant,是否有可能僅存儲日期部分,而沒有時間?

如果它有什麼不同,我希望它們工作的數據庫是MySQL & SQL Server。

再一次,如果它做出更大的區別,那麼它就是一個Spring Boot應用程序。

謝謝!

回答

12

「本地...」類型的故意沒有時區的概念。所以他們做而不是代表時間軸上的一個時刻。 A LocalDateTime表示可能時刻的模糊範圍,但在分配偏移或時區之前沒有真正意義。這意味着應用ZoneId獲得ZonedDateTime

例如,說今年聖誕節開始於12月25日的第一時刻,我們要說:

LocalDateTime ldt = LocalDateTime.of(2017 , 12 , 25 , 0 , 0 , 0 , 0); 

但午夜的中風發生在更早的東部比西部。

這就是爲什麼精靈的物流部門在太平洋地區開始繪製聖誕老人的路線,起始地點是Kiribati,太平洋是世界最早的時區,比世界協調時早14小時。在那裏交付後,他們將聖誕老人西向路線發往新西蘭等地的午夜。然後在午夜之後回到亞洲。然後是印度等幾個小時後到達歐洲的午夜,然後是北美東海岸,晚些時候再過幾個小時。所有這些地方都經歷過相同LocalDateTime在不同的時刻,每個送貨所代表的一個不同ZonedDateTime對象。

所以......

  • 如果你想要錄製的概念的聖誕節午夜25日之後出發,使用LocalDateTime和寫入TIMESTAMP WITHOUT TIME ZONE類型的數據庫列。
  • 如果您想記錄聖誕老人每次送貨的確切時刻,請使用ZonedDateTime並寫入類型爲TIMESTAMP WITH TIME ZONE的數據庫列。

關於第二個項目符號,請注意,幾乎每個數據庫系統都將使用區域信息來調整UTC的日期時間並存儲該UTC值。有些還保存區域信息,但某些(如Postgres)在使用它調整爲UTC後丟棄區域信息。所以「帶時區」是一個用詞不當的地方,真的意思是「尊重時區」。如果您關心記住原始區域,您可能需要將其名稱保存在一個單獨的列中。

使用Local…類型的另一個原因是未來約會。政治家喜歡經常改變他們轄區的時區。他們喜歡採用夏令時(DST)。喜歡改變他們DST切換的日期。他們喜歡放棄採用DST。他們喜歡重新定義他們的時區,改變邊界。他們喜歡重新定義它們的UTC偏移量,有時候會像15分鐘那樣。他們很少提前發出通知,在短短一個月或兩個小時內發出警告。

因此,要進行下一年或六個月的體檢預約,無法預測時區定義。因此,如果您要預約上午9點,則應使用記錄在TIMESTAMP WITHOUT TIME ZONE類型的數據庫列中的LocalTimeLocalDateTime。否則,上午9點的預約,如果在夏令時延期的區域劃分,可能會顯示爲上午8點或上午10點。

生成投影計劃時,可以將時區(ZoneId)應用於這些「本地」(未分區)值以創建ZonedDateTime對象。但是,當政客們通過改變區域可能毀掉他們的意義時,不要依賴那些時間太遠的人。

提示:對DST和時區進行頻繁更改意味着您必須保持您的時區tzdata數據庫處於最新狀態。在你的主機操作系統,你的JVM中,或者在你的數據庫系統中,例如Postgres,都有一個tzdata。所有這三個應該經常更新。有時,這些區域的變化速度比計劃的更新週期更快,例如土耳其去年決定在數週前通知DST。所以你可能偶爾需要手動更新這些tzdata文件。 Oracle提供了一個更新其Java實現的tzdata的工具。

處理精確時刻的一般最佳做法是使用UTC跟蹤它們。僅在必要時才應用時區,例如向用戶展示他們希望在自己的教區時區中看到值的用戶。在java.time中,Instant類表示時間軸中的某個時刻。 UTC的分辨率爲納秒。

Instant instant = Instant.now() ; // Current moment on the timeline in UTC. 
ZonedDateTime zdt = instant.atZone(z) ; // Assign a time zone to view the same moment through the lens of a particular region’s wall-clock time. 
Instant instant = zdt.toInstant(); // revert back to UTC, stripping away the time zone. But still the same moment in the timeline. 

順便說一句,符合JDBC 4.2及更高版本的驅動程序可以直接處理java。時間類型通過:

  • PreparedStatement::setObject
  • ResultSet::getObject

避免舊舊的數據類型,如java.util.Datejava.sql.Timestamp只要有可能。它們設計不佳,混亂和缺陷。

明白,所有這四個都在UTC時間線上的時刻表示:

  • 現代
    • java.time.Instant
    • java.time.OffsetDateTimeZoneOffset.UTC
  • 分配的偏移
  • 傳統
    • java.util.Date
    • java.sql.Timestamp

如果你想有一個日期,只值沒有時間的天,沒有時區,使用java.time.LocalDate。這個類代替java.sql.Date

至於具體的數據庫,要知道,SQL標準幾乎沒有觸及到的日期時間類型及其處理的話題。此外,各種數據庫有很大的不同,我真的是廣泛,在他們的支持的日期時間的功能。有些幾乎沒有支持。有些將SQL標準類型與專有類型混合使用,這些類型要麼早於標準類型,要麼作爲標準類型的替代品。另外,JDBC驅動程序的行爲與將日期時間值封送到數據庫或從數據庫封裝日期時間值不同。一定要研究文件和練習,練習,練習。

+0

我非常同意這樣的說法,即一段時間應該以UTC存儲,並且只有在向用戶顯示時才劃分區域。 –

+0

我真的很希望我8歲的女兒(誰聲稱聖誕老人是不真實的)有一段時間讀這個。不僅是一個很好的答案,但是當我得到「這就是爲什麼精靈......」的時候upvoted。:) – NealeU

+0

TimeZone精美的解釋。 Upvoted,謝謝:-) –

-4

他們將新日期API的一部分分成了日期類型。包括時區的正確類是ZonedDateTime

// Get the current date and time 
     ZonedDateTime date1 = ZonedDateTime.parse("2007-12-03T10:15:30+05:30[Asia/Karachi]"); 
     System.out.println("date1: " + date1); 

     ZonedDateTime zonedDateTime = ZonedDateTime.now(); 
     System.out.println("Zoned Date Time: " + zonedDateTime); 

     ZoneId id = ZoneId.of("Europe/Paris"); 
     System.out.println("ZoneId: " + id); 

     ZoneId currentZone = ZoneId.systemDefault(); 
     System.out.println("CurrentZone: " + currentZone); 

打印:

date1: 2007-12-03T10:15:30+05:00[Asia/Karachi] 
Zoned Date Time: 2017-04-18T11:36:09.126-04:00[America/New_York] 
ZoneId: Europe/Paris 
CurrentZone: America/New_York 
+0

我知道'ZonedDateTime'類。我的問題是關於在Hibernate中使用'LocalDate'和'LocalDateTime'類在UTC中將日期和日期時間存儲在數據庫中的問題。 – adi

+0

然後你會想使用最可能的DateFormatter –

+0

感謝您再次閱讀問題。你能否更新你的答案,以便真正回答我問的問題? – adi

6

或者我應該使用Java 8的瞬間?如果使用Instant,是否有 只能存儲日期部分,而沒有時間?

即時應該是適合大多數操作。

添加hibernate-java8pom.xml支持Java 8時API:

<dependency> 
    <groupId>org.hibernate</groupId> 
    <artifactId>hibernate-java8</artifactId> 
    <version>${version.hibernate}</version> 
</dependency> 

然後你可以使用對Hibernate實體領域LocalDateLocalDateTimeInstant。您需要刪除@Temporal(TemporalType.TIMESTAMP)

我的要求是存儲所有日期&日期時間在UTC時區在 數據庫。我用我 休眠實體的Java 8的LOCALDATE的& LocalDateTime。

是否正確LocalDate & LocalDateTime沒有與他們關聯的時區 ?

您可以在構型碼的地方設置默認JVM時區:

@PostConstruct 
void setUTCTimezone() { 
    TimeZone.setDefault(TimeZone.getTimeZone("UTC")); 
} 

然後你會在你的代碼運行UTC時間。

使用Java在DTO 8種日期類型,你需要添加Jsr310JpaConverters

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-data-jpa</artifactId> 
</dependency> 

和:

@EntityScan(basePackageClasses = { Application.class, Jsr310JpaConverters.class }) 
SpringBootApplication 
public class Application { … } 

更多選擇: