【问题标题】:How to use SQL timestamp with java Instant in jOOQ?如何在 jOOQ 中使用带有 java Instant 的 SQL 时间戳?
【发布时间】:2021-04-21 17:54:18
【问题描述】:

我想使用Instant 类型将其放入 MySQL 数据库(timestamp 字段)。不幸的是,当使用 POJO 和Record#from() 时,出于某种原因,jOOQ 不允许我这样做。我的 gradle 配置有以下内容:

forcedTypes {
    forcedType {
        name = "Instant"
        types = "timestamp"
    }
}

代码正在正确生成,但不起作用并在运行时给我错误:

 Data truncation: Incorrect datetime value: '2021-01-16 05:01:25.457+00:00' for column `test`.`messages`.`time_sent` at row 1

我尝试添加自己的转换器和活页夹,但如果在 gradle 配置中设置了 name,它们似乎不起作用,因为在构建过程中会出现警告。但是没有name,我无法让jOOQ为timestamp字段生成Instant

在使用 jOOQ 的 POJO 时,有没有办法将 Instant 与 SQL timestamp 一起使用?

【问题讨论】:

    标签: java mysql timestamp jooq java.time.instant


    【解决方案1】:

    MySQL 的时间戳数据类型有点傻。

    它将时间存储为自纪元(1970 年 1 月 1 日,UTC,午夜)以来的秒数,采用 32 位整数。这有几个问题:

    • 没有毫秒的空间。你的 Instant 有 .457 毫秒,这需要被删除,这就是 JOOQ 拒绝这样做的原因;那是破坏数据。我假设您不关心那些毫秒,但 JOOQ 不知道这一点。您可以尝试从瞬间中去除毫秒,如果必须的话,JOOQ 可能会允许保存瞬间,如果该瞬间具有它所环绕的纪元可被 1000 整除的属性(没有毫秒部分)。除了,这很烦人,所以在某些时候,即使它存储为 32 位 seconds-since-epoch,数据类型现在也单独包含从 0 到 999999 的小数秒。要么你有一个旧版本的 MySQL或者不允许这样做的底层表结构,或者 JOOQ 不知道 MySQL 至少支持毫秒。
    • 在 2038-01-19 03:14:07 UTC,您的应用爆炸。而已。这是 MySQL TIMESTAMP 对象可表示的最后一个时间戳。我们距此不到 18 年。因此,这种数据类型实际上是不可用的,您应该使用其他东西。 (这对您来说可能听起来不可信。如果您需要一些令人信服的信息,请直接从源头阅读MySQL8's user manual §11.2.2)。 Java 的核心即时存储系统不会受到可怕的 Y2K38 的影响,因为 java 在 64 位中使用 millis-since-epoch;在这个数字用完之前,我们还有几十亿年的时间。
    • 请注意,打印的消息有点误导。瞬间存储为自纪元以来的毫秒数并且没有时区,打印输出中的+00.00 表明它确实如此。它没有 - 因此 mysql 的 TIMESTAMP 类型也不是问题。

    解决方案

    到目前为止,最好的解决方案是使用不会像这样损坏的数据库引擎。例如,试试 postgres。

    另一个遥远的第二个解决方案是仔细阅读JOOQ issue #9665 @lucaseder(JOOQ 的核心贡献者;他是在那里施展魔法的天才 :P)说它还不起作用,但那里有一些代码你可能可以使用。

    请注意,如果您真的关心区域,这将变得更加复杂。有 3 种不同的方式来表示时间;这些是不同的,如果没有额外的信息和警告,就不能相互转换;无论如何,大多数工具都会错误地默默地转换它们,并且当这种情况发生时会产生痛苦。因此,您可能需要考虑一下您在这 3 种时间中的哪一种:

    • solarflare 时间:某事发生或将要发生的时刻,例如太阳耀斑。如果某个政治实体决定更改时区,则无效。无论时区发生什么,“事件发生前的时间”/“事件发生后的时间”每秒都会增加 1 秒。在 Java 术语中,java.time.Instant
    • 预约时间:我打电话给我在阿姆斯特丹的理发师,预约时间是 2023 年 1 月 5 日 14:00。你会认为这就像太阳耀斑时间,但是,不。如果荷兰议会猛烈抨击木槌并通过一项法律,荷兰将不再遵守夏令时并将继续在夏季,那么木槌落下的那一刻,距离我的约会的秒数增加了一个小时(是不是减少一个小时?)。这一点都不奇怪 - 请参阅 欧盟指令 2000/84/EC - 事实上,很有可能。 Solarflares 的时间不应该像这样改变,而预约时间会改变。最好表示为年+月+日+小时+分钟+秒+毫秒+一个完整区域(即Europe/Amsterdam,而不是像+0800PST这样的愚蠢无用的东西)。在 Java 术语中,ZonedDateTime
    • 闹钟时间:只有年、月、日、时、分、秒。就是这样 - 它不代表任何特定的东西,而只是概念。如果我设置闹钟在早上 8 点叫醒我,然后我穿越太平洋旅行,那么当我移动时区时,闹钟响起的时间应该会发生巨大变化。在 Java 术语中,LocalDateTime 和朋友。

    我假设您有一个 solarflares 时间(例如,要跟踪“用户 X 在此时更改其密码”)。这个答案假设你是这个意思。如果你不这样做,我的建议会有所改变。大多数情况下,整个“不要使用 mysql”会变得更加强大:您真正想要的是数据类型 TIMESTAMP WITH TIME ZONE,例如 postgres has

    【讨论】:

    • DATETIMETIMESTAMP 值可以包括一个尾随小数秒部分,精度高达微秒(6 位)。特别是,插入DATETIMETIMESTAMP 列的值中的任何小数部分都将被存储而不是丢弃。 dev.mysql.com/doc/refman/8.0/en/datetime.html 这似乎与您的解释相矛盾?同一页面确实在 2038-01-19 03:14:07 UTC 确认了爆炸(除非新的 MySQL 版本可以解决它)。
    • Java 的 Instant 具有 纳秒 精度(9 位小数)。它将秒部分存储在有符号的 64 位 long 中,并且将一直工作到 1 000 000 000 年(这对于我能想象的任何目的都足够了)。
    • @OleV.V.我认为,请先阅读整个答案。我提到了这一点。
    • 嗯,instant 确实在 nanos 之上执行了 seconds-in-64。有点奇怪;存储 millis-in-64 具有最大限制的空间(实际上,这将带您一路达到 2 亿年多一点)。
    • 我看到了这个问题,但该解决方案对我不起作用。要么我用错了,要么在 jOOQ 中发生了一些变化。我也知道 2038 年的问题,但我实际上希望在更高版本的 MySQL 中引入一些东西。不,我不关心时区,这就是我使用 Instant 的原因。
    猜你喜欢
    • 2020-10-17
    • 2016-01-15
    • 2014-11-20
    • 2020-07-29
    • 2015-11-18
    • 1970-01-01
    • 2020-12-29
    • 1970-01-01
    • 2017-07-04
    相关资源
    最近更新 更多