如何为 Spring Boot JPA 时间戳指定 UTC 时区
Posted
技术标签:
【中文标题】如何为 Spring Boot JPA 时间戳指定 UTC 时区【英文标题】:How to specify UTC timezone for Spring Boot JPA Timestamp 【发布时间】:2017-05-27 20:30:49 【问题描述】:环境
Spring Boot Starter Data JPA 1.4.2 Eclipselink 2.5.0 Postgresql 9.4.1211.jre7问题
我正在构建一个 Spring Boot 微服务,它与另一个服务共享一个 Postgresql 数据库。数据库在外部初始化(我们无法控制),其他服务使用的日期时间列类型是timestamp without time zone。因此,由于我希望 db 上的所有日期都具有相同的类型,因此具有该类型是我的 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"));
有我想要达到的效果,但是不适合,因为不是我的服务特有的。 IE。它将影响整个 JVM 或当前线程加上子线程。
使用-Duser.timezone=GMT
启动应用程序似乎也可以为正在运行的JVM 的单个实例完成这项工作。因此,比上一个更好的解决方案。
但是有没有办法在 JPA/datasource/spring boot 配置中指定时区?
【问题讨论】:
如何“查看数据库”?也许只是 PostgreSQL 将时间戳调整为会话的时区?见this answer。 感谢您回答@Adam,我只是SELECT * FROM my_table
。根据您链接的答案,“当您稍后显示该时间戳时,您会返回您按字面输入的内容”。如果我SELECT my_timestamp AT TIME ZONE 'UTC' AT TIME ZONE 'UTC' FROM my_table
我看到相同的结果,这是有道理的。考虑到这一点,我假设不是 PostgreSQL 调整时区,调整似乎发生在值进入 Java 端的数据库之前。
【参考方案1】:
我能想到的最合适的解决方法是使用 AttributeConverter
将 Java 8 ZonedDateTime
对象转换为 java.sql.Timestamp
对象,以便它们可以映射到 PostgreSQL timestamp without time zone
类型。
您需要AttributeConverter
的原因是因为Java 8/Joda 时间日期时间类型are not yet compatible with 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
PostgreSQL 类型是错误的选择。要使用的正确数据类型是timestamp with timezone
,其中包含时区信息。有关此主题的更多信息here。
但是,如果出于某种原因,您必须使用timestamp without timezone
,我认为上面的ZonedDateTime
方法是一种稳健且一致的解决方案。
您是否还将ZonedDateTime
序列化为 JSON?
那么您可能对以下事实感兴趣:您至少需要jackson-datatype-jsr310
依赖项的2.6.0
版本才能使序列化工作。更多关于 in this answer.
【讨论】:
【参考方案2】:你不能。 Eclipselink 使用 setTimestamp
的单 arg 版本,将时区处理的责任委托给驱动程序,并且 postgresql jdbc 驱动程序不允许覆盖默认时区。 postgres 驱动程序甚至会将客户端时区传播到会话,因此服务器端默认设置对您也没有用处。
您可以尝试一些骇人听闻的事情来解决该问题,例如编写 JPA 2.1 AttributeConverter
将您的时间戳转移到目标区域,但最终它们注定要失败,因为您的客户端时区有夏令时调整,有时会变得模棱两可或无法表示。
您必须在客户端上设置默认时区,或者使用原生 SQL 将时间戳设置为带有强制转换的字符串。
【讨论】:
以上是关于如何为 Spring Boot JPA 时间戳指定 UTC 时区的主要内容,如果未能解决你的问题,请参考以下文章
Spring Boot - 如何为指定根目录中的多个路由提供一个静态 html 文件
如何为非Spring Boot的Java应用程序指定外部配置文件