如何将Java Instant存储到MySQL数据库中

14
使用Java的对象时,最简单的方法是将它们存储为MySql 对象(以UTC为基准)。但是随着切换到,这种方法将不再适用,因为MySQL 不提供存储纳秒精度的功能。仅仅截断它们可能会导致新创建的对象与从数据库中读取的对象之间出现意外的比较结果。 时间戳并不是一种优雅的解决方案:手动编写选择查询变得更加困难,因为您必须在所有地方转换时间戳以使其可读,并且与甚至值相比,Java中的处理方式有些笨拙。
那么在这里最好的方法是什么呢?很可能不是,对吧?

2
@OleV.V. 好的,但 MySQL 文档似乎暗示 timestamp 只能处理微秒级别。纳秒部分会发生什么? - Tim Biegeleisen
1
有趣的问题,@TimBiegeleisen。文档似乎证实了这一点。看来我们得想出创意来解决它。 - Ole V.V.
1
当然,我可以将这两个“Instant”组件分别存储在两个列中,这会使事情变得更加复杂。我只是想知道是否有一个共同且方便的解决方案来解决这个不太常见的问题。 - NotX
2
我无法提出比你自己更好的建议。如果您将您的 Instant 转换为使用 ZoneOffset.UTC 偏移量的 OffsetDateTime 并使用 uuuu-MM-dd'T'HH:mm.ss.SSSSSSSSSX 格式化它,那么 (1) 直到 9999 年,您的字符串将始终是 30 个字符 (2) 字符串可以直接解析回 Instant (3) 字符串的顺序将与实例的顺序相同,(4) 您数据库中的字符串将比纯数字更易读。 - Ole V.V.
1
@Basil Bourque 您说得对,我实际上并不需要它们。事实上,即使是 Instant文档也不鼓励依赖它们。我只是想避免在运行时创建的 Instant 与从数据库存储和重新加载的这个 Instant 不相等的情况,因为纳秒丢失了。这是一个未记录的副作用,可能会破坏单元测试或更糟。 - NotX
显示剩余4条评论
1个回答

12

截断至微秒

显然,我们无法将Instant纳秒分辨率压缩到MySQL数据类型DateTimeTimestamp微秒分辨率中。

虽然我不使用MySQL,但我想JDBC驱动程序被构建为在接收到Instant时忽略纳秒,并将值截断为微秒。建议您尝试进行实验,以查看并检查符合JDBC 4.2及更高版本的驱动程序源代码。

Instant instant = Instant.now().with( ChronoField.NANO_OF_SECOND , 123_456_789L ) ;  //Set the fractional second to a spefic number of nanoseconds.
myPreparedStatement.setObject( … , instant ) ;

…和…

Instant instant2 = myResultSet.getObject( … , Instant.class ) ;

JDBC 4.2规范要求支持OffsetDateTime,但奇怪的是并不要求支持两种更常用的类型:InstantZonedDateTime。如果你的JDBC驱动程序不支持Instant,请进行转换。

OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;  // Use `OffsetDateTime` if your JDBC driver does not support `Instant`. 
Instant instant2 = odt.toInstant() ;  // Convert from `OffsetDateTime` to `Instant`. 

然后进行比较。
Boolean result = instant.equals( instant2 ) ;
System.out.println( "instant: " + instant + " equals instant2: = " + instant2 + " is: " + result ) ;

您明智地关注了从数据库中提取的值与原始值不匹配的问题。如果符合业务问题的要求,一种解决方案是在原始数据中将任何纳秒截断为微秒。我通常推荐这种方法。 java.time类提供了truncatedTo方法。传递一个ChronoUnit枚举对象以指定粒度。在这种情况下,应该使用ChronoUnit.MICROS
Instant instant = Instant().now().truncatedTo( ChronoUnit.MICROS ) ;

目前这种方法应该足够,因为您的数据中不太可能有任何纳秒。据我所知,今天的主流计算机没有配备能够捕获纳秒的硬件时钟。

从纪元开始计数

如果您不能承受可能存在的任何纳秒数据丢失,请使用从纪元开始计数的方法。

通常我建议不要将日期时间跟踪为从纪元参考日期开始的计数。但是,在将基于纳秒的值存储在仅限于微秒级值的数据库(如MySQL和Postgres)中时,您几乎没有其他选择。

存储整数对

与其使用自1970-01-01T00:00Z以来的极大纳秒数作为纪元,我建议采用Instant类内部采用的方法:使用一对数字。

在数据库中将整个秒数存储为整数。在第二列中,将分数秒中的纳秒数存储为整数。

您可以轻松地从/向Instant对象提取/注入这些数字。只涉及简单的64位long数字;不需要BigDecimalBigInteger。我想您可能能够使用32位整数列来表示两个数字中的至少一个。但为了简单起见并且与java.time.Instant类的一对长整型直接兼容,我会选择64位整数列类型。

long seconds = instant.getEpochSecond() ;
long nanos = instant.getNano() ;

…和…

Instant instant = Instant.ofEpochSecond( seconds , nanos ) ;

当按时间顺序排序时,您需要进行多级排序,首先在整个秒列上进行排序,然后在纳秒分数秒列上进行次要排序。

1
@MathewAlden 谢谢你的修复。这促使我对这个答案进行了一些其他的微调。 - Basil Bourque

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接