【问题标题】:How to store a Java Instant in a MySQL database如何在 MySQL 数据库中存储 Java Instant
【发布时间】:2018-04-22 06:25:18
【问题描述】:

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

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

去这里最好的方式是什么?应该不是varchar吧?

【问题讨论】:

  • @OleV.V.好的,但 MySQL 文档似乎暗示时间戳最多只能处理微秒。纳秒部分会发生什么?
  • 有趣的问题,@TimBiegeleisen。 The docs 似乎证实了这一点。看来我们必须要有创意。
  • 当然我可以将两个Instant 组件存储在两列中,这会使事情变得更加复杂。我只是想知道对于这个不那么奇怪的问题是否有一个通用且方便的解决方案。
  • 我想不出比你更好的建议。如果您在偏移量ZoneOffset.UTC 处将Instant 转换为OffsetDateTime 并使用uuuu-MM-dd'T'HH:mm.ss.SSSSSSSSSX 对其进行格式化,那么(1)直到9999 年,您的字符串将始终为30 个字符(2)可以将字符串直接解析回Instant (3) 字符串的顺序将与瞬间的顺序相同,并且 (4) 数据库中的字符串将比纯数字更具可读性。
  • @Basil Bourque 你说得对,我真的不需要它们。事实上,即使是 documentationInstant 也不鼓励依赖它们。我只是想避免在运行时创建的 Instant 不等于存储并从数据库重新加载的 Instant 的情况,只是因为 nanos 丢失了。这是一个未记录的副作用,可能会搞砸 UnitTest 或更糟。

标签: java mysql date datetime java.time.instant


【解决方案1】:

截断到微秒

显然我们不能将Instantnanoseconds 分辨率压缩到MySQL data types DateTime and Timestampmicroseconds 分辨率中。

虽然我不使用 MySQL,但我想 JDBC driver 被构建为在接收 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 spec 需要支持OffsetDateTime,但奇怪的是不需要InstantZonedDateTime 这两种更常用的类型。如果您的JDBC driver 不支持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 类内部采用的方法:使用 pair 的数字。

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

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

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

……和……

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

按时间顺序排序时,您需要进行多级排序,首先在整个 seconds 列上排序,然后在 nanos fraction-of-second 列上进行第二次排序。

【讨论】:

  • @MathewAlden 感谢您的修复。这促使我在这个答案中添加了一些其他的调整。
猜你喜欢
  • 2018-02-21
  • 1970-01-01
  • 1970-01-01
  • 2011-05-14
  • 2022-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多