环境
问题
我正在构建一个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配置中指定时区?
答案 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以将时间戳设置为带有强制转换的字符串。