如何在 MySQL 数据库中存储 Java Instant

How to store a Java Instant in a MySQL database

提问人:NotX 提问时间:11/9/2017 最后编辑:NotX 更新时间:12/10/2020 访问量:16399

问:

对于 Java 对象,最简单的方法是将它们存储为 MySql 对象(以 UTC 为单位)。切换到这种方法将不再起作用,因为MySQL不提供存储纳秒的精度。仅仅截断它们可能会导致新创建的对象与从数据库读取的对象之间出现意外的比较结果。DateDateTimeInstantDateTimeInstant

BigDecimal在我看来,时间戳并不是一个优雅的解决方案:手动编写选择查询变得更加困难,因为你必须在任何地方转换时间戳以使其可读,而且与值相比,Java 中的处理有些笨拙。InstantLong

最好的方式是什么?可能不是,对吧?varchar

mysql 日期 时间 java.time.instant

评论

2赞 Tim Biegeleisen 11/9/2017
@OleV.V.好的,但是MySQL文档似乎暗示时间戳最多只能处理几微秒。纳秒分量会发生什么变化?
1赞 Anonymous 11/9/2017
有趣的问题,@TimBiegeleisen。文档似乎证实了这一点。看来我们必须对此有创意。
1赞 NotX 11/9/2017
当然,我可以将这两个组件存储在两列中,这将使事情变得更加复杂。我只是想知道对于这个不那么奇特的问题,是否有一种常见且方便的解决方案。Instant
2赞 Anonymous 11/10/2017
我想不出比你自己更好的建议。如果你将你的 to at offset 并使用 格式化,那么 (1) 直到 9999 年,你的字符串将始终是 30 个字符 (2) 字符串可以直接解析回 (3) 字符串的顺序将与瞬间的顺序相同,以及 (4) 数据库中的字符串将比纯数字更具可读性。InstantOffsetDateTimeZoneOffset.UTCuuuu-MM-dd'T'HH:mm.ss.SSSSSSSSSXInstant
1赞 NotX 11/10/2017
@Basil Bourque:你说得对,我真的不需要它们。事实上,即使是文档也不鼓励依赖它们。我只是想避免在运行时创建的不等于从数据库中存储和重新加载的情况,只是因为 nanos 丢失了。这是一个未记录的副作用,可能会搞砸 UnitTests 或更糟。InstantInstantInstant

答:

12赞 Basil Bourque 11/11/2017 #1

截断为微秒

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

虽然我不使用 MySQL,但我认为 JDBC 驱动程序在接收 时会忽略纳秒,将值截断为微秒。我建议您尝试一个实验来查看,并可能检查符合 JDBC 4.2 及更高版本的驱动程序的源代码。Instant

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 规范需要支持,但奇怪的是,它不需要两种更常用的类型,即 .如果您的 JDBC 驱动程序不支持 ,请转换。OffsetDateTimeInstantZonedDateTimeInstant

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.MICROSChronoUnit

Instant instant = Instant().now().truncatedTo( ChronoUnit.MICROS ) ;

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

从纪元开始计数

如果您无法承受丢失可能存在的任何纳秒数据,请使用 count-from-epoch。

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

存储整数对

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

在数据库中以整数形式存储数秒数。在第二列中,以小数秒为单位的纳秒数存储为整数。

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

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

...和。。。

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

按时间顺序排序时,您需要进行多级排序,首先对整个秒列进行排序,然后对纳米秒的分数列进行第二次排序。

评论

1赞 Basil Bourque 12/10/2020
@MathewAlden 感谢您的修复。这促使我在此答案中添加了一些其他调整。