如何为 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 Rest 使用自定义异常?

Spring JPA:如何为百分比值建模

Spring Boot - 如何为指定根目录中的多个路由提供一个静态 html 文件

如何为非Spring Boot的Java应用程序指定外部配置文件

如何为 Spring Boot RESTful Web 服务配置多级身份验证?

保存对象时未填充 Spring Boot JPA@CreatedDate @LastModifiedDate