繁体   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