如何为Spring Boot JPA Timestamp指定UTC时区

时间:2017-01-12 16:22:26

标签: java postgresql jpa spring-boot timezone

环境

  • Spring Boot Starter Data JPA 1.4.2
  • Eclipselink 2.5.0
  • Postgresql 9.4.1211.jre7

问题

我正在构建一个Spring Boot微服务,它与不同的服务共享一个Postgresql数据库。数据库在外部初始化(在我们的控制之外),其他服务使用的日期时间列类型为时区,没有时区。因此,由于我希望db上的所有日期具有相同的类型,因此具有该类型是我的JPA实体日期的要求。

我在JPA实体对象上映射它们的方式如下:

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

问题在于,当我按如下方式创建时间戳时:

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

我查看数据库,时间戳包含我的本地时区日期时间,但我想将其存储在UTC中。这是因为我的默认时区设置为' Europe / Brussels'和JPA / JDBC将我的java.sql.Timestamp对象转换为我的时区,然后再将其放入数据库。

发现不理想的解决方案

  • TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC"));具有我想要实现的效果,但它并不合适,因为它并非特定于我的服务。即它会影响整个JVM或当前线程加上孩子。

  • 使用-Duser.timezone=GMT启动应用程序似乎也可以为正在运行的JVM的单个实例执行任务。因此,比前一个更好的解决方案。

但是有没有办法在JPA / datasource / spring boot配置中指定时区?

2 个答案:

答案 0 :(得分:6)

我能解决问题的最合适的解决方法是使用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?

然后,您可能感兴趣的是,您至少需要2.6.0依赖项的jackson-datatype-jsr310版本才能使序列化工作。更多关于in this answer

答案 1 :(得分:1)

你做不到。 Eclipselink使用setTimestamp的单个arg版本,将时区处理的责任委派给驱动程序,而postgresql jdbc驱动程序不允许覆盖默认时区。 postgres驱动程序甚至将客户端时区传播到会话,因此服务器端默认值对您来说也没用。

你可以尝试解决这个问题,例如写一个JPA 2.1 AttributeConverter来将你的时间戳转移到目标区域,但最终它们注定要失败,因为你的客户时区有白天储蓄调整,有时模棱两可或无法代表。

您必须在客户端上设置默认时区,或者放入本机SQL以将时间戳设置为带有强制转换的字符串。