当前位置: 首页 > 知识库问答 >
问题:

JPA即时/分区DateTime数据库映射

澹台景辉
2023-03-14

我遇到了java的一个问题。时间类及其到DB类型的映射。我想存储Instant类型,但它的行为非常不直观。

我有基本的实体类:

@Entity
@Getter
@Setter
public class AbstractEntity {
    @Id
    @GeneratedValue
    private Long id;
//    @Column(columnDefinition = "TIMESTAMP WITH TIME ZONE")
    private Instant createdAt;
//    @Column(columnDefinition = "TIMESTAMP WITH TIME ZONE")
    private ZonedDateTime createdAtZoned;
    private LocalDateTime createdAtLocal;
    private Instant modifiedAt;

    @PrePersist
    protected void prePersist() {
        createdAt = Instant.now();
        modifiedAt = createdAt;
        createdAtZoned = createdAt.atZone(ZoneId.systemDefault());
        createdAtLocal = createdAtZoned.toLocalDateTime();
        System.out.println(String.format("Created: instant=%s, zoned=%s, local=%s", createdAt, createdAtZoned, createdAtLocal));
    }

    @PreUpdate
    protected void preUpdate() {
        modifiedAt = Instant.now();
    }
}

现在,我将创建并持久化实体:

    @Transactional
    @GetMapping("create")
    public AbstractEntity createNew() {
        System.out.println("create");
        System.out.println("ZoneId=" + ZoneId.systemDefault());
        var e = new AbstractEntity();
        em.persist(e);
        return e;
    }

Java中的数据符合预期-系统输出如下:

ZoneId=Europe/Bratislava
Created: instant=2021-12-29T14:13:55.624902400Z, zoned=2021-12-29T15:13:55.624902400+01:00[Europe/Bratislava], local=2021-12-29T15:13:55.624902400

但是,DB中的数据不是。它们都存储为相同的值。所有列的默认类型都设置为TIMESTAMP。它们都被存储为应用程序JVM时区中的LocalDateTime。

我一点也不喜欢这样,这些类型之间没有区别,值的“含义”取决于应用程序区域。如果我在不知道哪个应用程序写入数据以及它的区域是什么的情况下查看数据库,我不能说日期时间意味着什么。更糟糕的是,如果我使用不同的区域运行应用程序,它会假设值是在它的时区中写入的,而它们不是。

有人说,时间数据应该存储在UTC中(我部分同意),他们建议设置以下属性,以便在JDBC存储到DB之前将值转换为UTC区域:

spring.jpa.properties.hibernate.jdbc.time_zone: UTC

但是,这会导致更糟糕的行为,因为它不仅适用于Instant/ZonedDateTime,还适用于LocalDateTime,这在逻辑上对我来说似乎是错误的。LocalDateTime值根本不应该受到任何时区转换的影响,这就是为什么它是LocalDateTime。

系统输出:

ZoneId=Europe/Bratislava
Created: instant=2021-12-29T14:15:13.614799900Z, zoned=2021-12-29T15:15:13.614799900+01:00[Europe/Bratislava], local=2021-12-29T15:15:13.614799900

数据库结果:

我希望其中之一会发生:

  1. 默认情况下将Instant和ZonedDateTime映射到TIMESTAMP_WITH_TIMEZONE。此外,Instant类型应该始终使用UTC区域进行序列化-就像打印到字符串或使用jackson序列化时一样。(理想)
  2. 将Instant和ZonedDateTime映射到TIMESTAMP,但在序列化时将它们转换为UTC,在反序列化时转换回JVM时区。(不理想,但足够好)

我可以通过取消注释下面的行来显式重写默认映射。

// @Column(columnDefinition = "TIMESTAMP WITH TIME ZONE")

这很好,只有Instant没有转换为UTC,而是采用JVM区域,就像ZonedDateTime一样。从技术上讲,它完全没问题,因此我看不到任何不一致的结果,但我个人更喜欢它们在UTC中,这样它们总是统一的。这的第二个问题是,我需要为每个字段显式配置它。

ZoneId=Europe/Bratislava
Created: instant=2021-12-29T14:19:14.600583400Z, zoned=2021-12-29T15:19:14.600583400+01:00[Europe/Bratislava], local=2021-12-29T15:19:14.600583400

最后,我有几个问题,我真的希望有人能回答我:)**

  1. 有没有办法得到理想解(第一个提到的)?
  2. 有没有办法至少全局配置类型映射?
  3. 你能看出这种方法有什么风险吗?
  4. 有没有更好或更标准化的方法,我不知道?你处理时间的策略是什么?

谢谢

共有1个答案

艾凯捷
2023-03-14

我不能与JPA交谈,但我可以解释一些JDBC问题。

我要存储即时类型

JDBC规范没有映射Instant

  • 对于类型类似于SQL标准类型TIMESTAMP WITHOUT TIME ZONE的列,使用Java类LocalDateTime
  • 对于类型类似于SQL标准类型TIMESTAMP AND TIME ZONE的列,使用Java类OffsetDateTime

在JDBC中,使用setObject

myPreparedStatement.setObject( … , myOffsetDateTime ) ;

一些数据库(如Postgres)会自动将传入数据调整为UTC,偏移量为0小时分秒。对于跨数据库的可移植代码,您可能希望在Java代码中进行调整。

OffsetDateTime myOdt = otherOdt.withOffsetSameInstant( ZoneOffset.UTC ) ;

将当前时刻捕获为OffsetDateTime

OffsetDateTime myOdt = OffsetDateTime.now( ZoneOffset.UTC ) ;

如果您手头有一个即时数据库,请将其转换为OffsetDateTime,以便通过JDBC存储在数据库中。

OffsetDateTime myOdt = myInstant.atOffset( ZoneOffset.UTC ) ;

您不需要在数据库中存储分区或未分区的值。您可以在数据库检索后用Java代码轻松调整时区,就像您在其他本地化需求时所做的那样。

ZoneId z = ZoneId.of( "America/Edmonton" ) ;
ZonedDateTime zdt = myOdt.atZoneSameInstant( z ) ;

我认为在处理时间线上的时刻和特定点时使用LocalDateTime通常没有好处。该类故意缺少时区或偏移量的上下文。此类对象仅包含一天中的时间的日期,因此本质上是模棱两可的。您可以从OffsetDateTime转换为LocalDateTime,但会剥离重要信息,而不会获得任何回报。

它们都被存储为应用程序JVM时区中的LocalDateTime。

这在术语上是矛盾的。LocalDateTime没有时区或偏移量。

所有列的默认类型都设置为TIMESTAMP。

列的数据类型没有默认值。创建列时指定数据类型。插入行时,数据类型不会更改。只有发出ALTER TABLE命令,数据类型才能更改。

此外,我们不知道您对TIMESTAMP这个词的意思是什么类型,因为您忽略了提及您的特定数据库。如上所述,SQL标准对两种非常不同的时间戳类型中的每一种都使用了四个词。

将Instant和ZonedDateTime映射到时间戳,但在序列化时将其转换为UTC,

不,JDBC不需要InstantZonedDateTime的任何此类映射。

您需要在Java代码中,在JDBC调用之外进行这样的转换。

例外是您的特定JDBC驱动程序可以选择超出JDBC规范的要求。如果创建者认为合适,驱动程序可以自由处理InstantZonedDateTime类型。但请注意,您编写的任何此类代码可能无法跨其他JDBC驱动程序移植。

反序列化时html" target="_blank">返回JVM时区。

我相信,如果作为程序员/DBA/系统管理员,您的大部分思考、日志记录、调试、数据存储和数据交换都是在UTC(偏移量为零)下进行的,那么这项工作就会容易得多。

转换为即时是调整UTC的一种简单方法。

Instant instant = odt.toInstant() ;
Instant instant = zdt.toInstant() ;

提示:通常编写代码时不要依赖于JVM、数据库会话或主机操作系统的默认时区。作为程序员,这些默认值不受您的控制。并且这些默认值可以在运行时的任何时候更改。因此,仅在需要时使用默认值,例如本地化到用户移动设备的默认区域以在用户界面中呈现。并显式使用这样的默认值,始终为各种调用指定其他可选的区域/偏移量参数。

提示:记录插入或更新行的时刻通常最好留给数据库。使用数据库服务器捕获当前时刻并将其分配给字段的触发器。然后,您可以在Java应用程序之外进行操作,例如批量数据加载。

 类似资料:
  • 其他人有这个奇怪的问题吗?错误消息: 致命错误:未捕获异常“exception”,消息为“DateTime::\uuu construct():无法分析位置17(A)处的时间字符串(2016年1月18日美国/纽约凌晨00:00):在数据库中找不到时区 异常:DateTime::_构造():未能分析位置17(A)处的时间字符串(2016年1月18日美国/纽约凌晨00:00):在数据库中找不到时区 原

  • 现在,如果这个日期被转换到任何其他时区..假设GMT+5:30,我会得到意想不到的结果 若要修复上述问题...我编写了以下代码 使用上面的代码..我可以在UTC时区获得数据库存储日期,但当我使用以下逻辑转换到其他时区(例如GMT+5:30)时 ps:我希望输出是java.util.date/Calendar的形式,因为我需要在这个日期再做一次转换 请帮我解决这个问题

  • 尝试与数据库MySQL建立连接时出现此错误 与MySQL的连接是这样完成的:

  • 我正在编写一个Python脚本来从我的数码相机导入图片,并且我正在使用Pandas来帮助记账传入的图像。我正在使用EXIF数据为单个图像标记信息,例如相机型号、图像模式、图像格式和相机获取图像的时间戳。这些数据用于将图像分离到目录结构中。我正在努力解决的是如何使用Pandas根据一组时间戳对图像进行分组,例如,这些时间戳都在彼此相隔半小时之内。举个例子,假设我有六张照片,其中三张是在相隔9分钟内拍

  • 我正在尝试使用ZonedDateTime.ofInstant给出特定的日期时间来获取时间,但是我注意到在某些情况下,给出的结果不包含秒数。表示我正在处理的情况的代码示例 此代码执行的结果如下所示: 我需要获得第一个结果中没有显示的秒数,但我不明白为什么没有显示。ZonedDateTime.ofInstant没有给出完整时间是有原因的吗,或者有不同的方法来获得YYYY-MM-DD:HH:MM:SS中

  • 我正在Debian8上使用PHP5.6.17。在建立owncloud之后,我注意到owncloud(而不是apache)自写的日志有错误的时区。 我调查了一下,好像,那一行: 这不关心任何时区设置。而不是欧洲/柏林(+1/+2)的时间,我总是得到UTC的时间。 在中,我设置了并且系统时间(debian)也是正确的。 即使运行如下所示,也会得到相同的输出(UTC): 对这个问题有什么想法吗?