簡體   English   中英

如何為 Spring Boot JPA 時間戳指定 UTC 時區

[英]How to specify UTC timezone for Spring Boot JPA Timestamp

環境

  • Spring Boot 入門數據 JPA 1.4.2
  • Eclipse 鏈接 2.5.0
  • PostgreSQL 9.4.1211.jre7

問題

我正在構建一個 Spring Boot 微服務,它與另一個服務共享一個 Postgresql 數據庫。 數據庫在外部初始化(我們無法控制),並且其他服務使用的 datetime 列類型是timestamp without time zone 因此,由於我希望數據庫上的所有日期都具有相同的類型,因此具有該類型是我的 JPA 實體日期的要求。

我將它們映射到我的 JPA 實體對象的方式如下:

@Column(name = "some_date", nullable = false)
private Timestamp someDate;

問題是當我按如下方式創建時間戳時:

new java.sql.Timestamp(System.currentTimeMillis())

我查看數據庫,時間戳包含我的本地時區日期時間,但我想將它存儲在 UTC 中。 這是因為我的默認時區設置為“歐洲/布魯塞爾”,而 JPA/JDBC 將我的java.sql.Timestamp對象轉換為我的時區,然后再將其放入數據庫。

發現不理想的解決方案

  • TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC")); 有我想要達到的效果,但是不適合,因為不是我的服務特有的。 即它會影響整個JVM 或當前線程加上孩子。

  • 使用-Duser.timezone=GMT啟動應用程序似乎也可以為正在運行的 JVM 的單個實例完成這項工作。 因此,比前一個更好的解決方案。

但是有沒有辦法在 JPA/datasource/spring boot 配置中指定時區?

我能想到的最合適的解決方法是使用AttributeConverter將 Java 8 ZonedDateTime對象轉換為java.sql.Timestamp對象,以便它們可以映射到timestamp without time zone

您需要AttributeConverter的原因是 Java 8/Joda 時間日期時間類型尚未與 JPA 兼容

AttributeConverter看起來像這樣:

@Converter(autoApply = true)
public class ZonedDateTimeAttributeConverter implements AttributeConverter<ZonedDateTime, Timestamp> {

    @Override
    public Timestamp convertToDatabaseColumn(ZonedDateTime zonedDateTime) {
        return (zonedDateTime == null ? null : Timestamp.valueOf(zonedDateTime.toLocalDateTime()));
    }

    @Override
    public ZonedDateTime convertToEntityAttribute(Timestamp sqlTimestamp) {
        return (sqlTimestamp == null ? null : sqlTimestamp.toLocalDateTime().atZone(ZoneId.of("UTC")));
    }

}

這允許我將沒有時區信息的數據庫時間戳作為具有UTC時區的ZonedDateTime對象讀取。 這樣,無論我的應用程序在哪個時區運行,我都可以保留在 db 上可以看到的確切日期時間。

由於toLocalDateTime()也應用了系統默認的時區轉換,所以這個AttributeConverter基本上取消了 JDBC 驅動應用的轉換。

你真的需要使用timestamp without timezone嗎?

現實情況是,如果您在時區上存儲日期時間信息(即使它是 UTC),那么timestamp without timezone是錯誤的選擇。 要使用的正確數據類型是timestamp with timezone其中確實包含時區信息。 更多關於這個主題的信息

但是,如果出於某種原因,您必須使用timestamp without timezone ,我認為上面的ZonedDateTime方法是一種強大且一致的解決方案。

您是否還將ZonedDateTime序列化為 JSON?

那么您可能對以下事實感興趣:您至少需要jackson-datatype-jsr310依賴項的2.6.0版本才能使序列化工作。 更多關於這個答案

你不能。 Eclipselink 使用單參數版本的setTimestamp ,將時區處理的責任委托給驅動程序,而 postgresql jdbc 驅動程序不允許覆蓋默認時區。 postgres 驅動程序甚至將客戶端時區傳播到會話,因此服務器端默認設置對您也沒有用處。

您可以嘗試一些駭人聽聞的事情來解決這個問題,例如編寫一個 JPA 2.1 AttributeConverter來將您的時間戳轉移到目標區域,但最終它們注定要失敗,因為您的客戶端時區有夏令時調整,使某些時間模棱兩可或無法代表。

您必須在客戶端上設置默認時區,或者放入本機 SQL 以將時間戳設置為帶有強制轉換的字符串。

使用 Instant 而不是 LocalDateTime 怎么樣? 我認為它可以在 UTC 中將實體值轉換為時間戳

@Converter(autoApply = true)
public class ZonedDateTimeAttributeConverter implements         AttributeConverter<ZonedDateTime, Timestamp> {

    @Override
    public Timestamp convertToDatabaseColumn(ZonedDateTime zonedDateTime) {
            return (zonedDateTime == null ? null : Timestamp.from(zonedDateTime.toInstant()));
    }

    @Override
    public ZonedDateTime convertToEntityAttribute(Timestamp sqlTimestamp) {
        return (sqlTimestamp == null ? null : sqlTimestamp.toInstant().atZone(ZoneId.of("UTC")));
    }

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM