![](/img/trans.png)
[英]Spring boot - Entity : How to add CreatedDate (UTC timezone aware), LastModifiedDate (UTC timezone aware), CreatedBy, LastModifiedBy
[英]How to specify UTC timezone for Spring Boot JPA Timestamp
環境
問題
我正在構建一個 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.