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

oiz*_*ain 7 java postgresql timezone jpa spring-boot

环境

  • 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;
Run Code Online (Sandbox Code Playgroud)

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

new java.sql.Timestamp(System.currentTimeMillis())
Run Code Online (Sandbox Code Playgroud)

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

找不到理想的解决方案

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

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

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

oiz*_*ain 6

我能解决问题的最合适的解决方法是使用a AttributeConverter将Java 8 ZonedDateTime对象转换为java.sql.Timestamp对象,以便它们可以映射到PostgreSQL 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")));
    }

}
Run Code Online (Sandbox Code Playgroud)

这允许我读取没有时区信息的数据库时间戳作为ZonedDateTime具有UTC时区的对象.这样我就可以在数据库中看到确切的日期时间,无论我的应用运行的时区如何.

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

你真的需要用timestamp without timezone吗?

实际情况是,如果您在时区上存储日期时间信息(即使它是UTC),timestamp without timezonePostgreSQL类型是错误的选择.要使用的正确数据类型timestamp with timezone包括时区信息.更多关于此主题的信息.

但是,如果出于某种原因,你必须使用timestamp without timezone,我认为ZonedDateTime上面的方法是一个强大而一致的解决方案.

你还要序列化为ZonedDateTimeJSON吗?

然后你在你需要至少版本的事实可能感兴趣2.6.0的的jackson-datatype-jsr310依赖性的序列化工作.更多关于这个答案.