![](/img/trans.png)
[英]Spring boot - Entity : How to add CreatedDate (UTC timezone aware), LastModifiedDate (UTC timezone aware), CreatedBy, LastModifiedBy
[英]How to specify UTC timezone for Spring Boot JPA Timestamp
环境
问题
我正在构建一个 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.