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



环境

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

问题

我正在构建一个SpringBoot微服务,它与另一个服务共享Postgresql数据库。数据库从外部初始化(超出我们的控制范围),其他服务使用的日期时间列类型为不带时区的时间戳。因此,由于我希望数据库中的所有日期都具有相同的类型,因此对于我的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引导配置中指定时区?

我能想出的解决这个问题的最合适的方法是使用AttributeConverter将Java 8ZonedDateTime对象转换为java.sql.Timestamp对象,这样它们就可以映射到PostgreSQLtimestamp 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对象。这样,无论我的应用程序运行在哪个时区,我都会保留数据库上可以看到的确切日期时间。

由于toLocalDateTime()还应用了系统默认的时区转换,因此AttributeConverter基本上取消了JDBC驱动程序应用的转换。

你真的需要使用timestamp without timezone

事实是,如果您在时区上存储日期-时间信息(即使是UTC),timestamp without timezonePostgreSQL类型是错误的选择。要使用的正确数据类型将是timestamp with timezone,它确实包括时区信息。有关此主题的更多信息,请点击此处。

但是,无论出于何种原因,如果必须使用timestamp without timezone,我认为上面的ZonedDateTime方法是一个稳健且一致的解决方案。

您是否也将ZonedDateTime序列化为JSON

那么您可能感兴趣的是,您至少需要jackson-datatype-jsr310依赖项的2.6.0版本才能使序列化工作。在这个答案中有更多关于这一点的内容。

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

你可以尝试解决一些棘手的问题,例如写一个JPA 2.1AttributeConverter来将你的时间戳转移到目的地区域,但最终它们注定要失败,因为你的客户时区有夏令时调整,使某些时间变得模糊或不可代表。

您必须在客户端上设置默认时区,或者使用本机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")));
}
}

相关内容

  • 没有找到相关文章

最新更新