Oracle / JDBC:检索具有 ISO 8601 格式的时区值的时间戳

2022-09-02 22:41:06

关于这个主题的部分内容已经说了很多(并在SO上写了),但不是以全面,完整的方式,所以我们可以有一个“终极的,涵盖一切”的解决方案供每个人使用。

我有一个Oracle数据库,用于存储全局事件的日期+时间+时区,因此必须保留原始TZ,并根据请求将其交付给客户端。理想情况下,它可以通过使用标准的ISO 8601“T”格式很好地工作,该格式可以使用“带有时区的时间戳”列类型(“TSTZ”)很好地存储在Oracle中。

类似于 '2013-01-02T03:04:05.060708+09:00'

我需要做的就是从DB中检索上述值并将其发送到客户端,而无需任何操作。

问题是Java缺乏对ISO 8601(或任何其他日期+时间+nano+tz数据类型)的支持,情况更糟,因为Oracle JDBC驱动程序(ojdbc6.jar)对TSTZ的支持更少(与Oracle DB本身相反,它得到了很好的支持)。

具体来说,这是我应该或不能做的事情:

  • 从TSTZ到java Date,Time,Timestamp(例如通过JDBC getTimestamp()调用)的任何映射都不起作用,因为我丢失了TZ。
  • Oracle JDBC驱动程序没有提供任何将TSTZ映射到java Calendar对象的方法(这可能是一个解决方案,但它不存在)
  • JDBC getString() 可以工作,但 Oracle JDBC 驱动程序返回格式
    为 '2013-01-02 03:04:05.060708 +9:00' 的字符串,这不符合 ISO 8601(没有“T”,在 TZ 中没有尾随 0 等)。此外,这种格式在Oracle JDBC驱动程序实现中是硬编码的(!),它还忽略了JVM区域设置和Oracle会话格式化设置(即它忽略了NLS_TIMESTAMP_TZ_FORMAT会话变量)。
  • JDBC getObject() 或 getTIMESTAMPTZ() 都返回 Oracle 的 TIMESTAMPTZ 对象,这实际上是无用的,因为它没有任何转换为日历(只有 Date、Time 和 Timestamp),所以再次丢失 TZ 信息。

因此,以下是我剩下的选项:

  1. 使用 JDBC getString(),并对其进行字符串操作以修复并使 ISO 8601 兼容。这很容易做到,但如果Oracle更改内部硬编码的getString()格式,就会有死亡的危险。此外,通过查看 getString() 源代码,似乎使用 getString() 也会导致一些性能损失。

  2. 使用 Oracle DB “toString” 转换:“SELECT TO_CHAR(tstz...)EVENT_TIME...“。这工作正常,但有2个主要缺点:

    • 每个SELECT现在都必须包括TO_CHAR呼叫,这是一个令人头疼的记忆和写作
    • 每个SELECT现在都必须添加EVENT_TIME列“别名”(例如,需要自动将结果序列化为Json
      )。
  3. 使用Oracle的TIMESTAMPTZ java类,并从其内部(记录的)字节数组结构中手动提取相关值(即实现我自己的toString()方法,Oracle忘记在那里实现)。如果Oracle更改内部结构(不太可能),并且需要相对复杂的功能来实现和维护,则这是有风险的。

  4. 我希望有第4个,很棒的选择,但从整个网络和SO中寻找 - 我看不到任何东西。

想法?意见?

更新

下面已经给出了很多想法,但看起来没有正确的方法来做到这一点。就个人而言,我认为使用方法#1是最短,最易读的方式(并保持不错的性能,而不会丢失亚毫秒或基于SQL时间的查询功能)。

这就是我最终决定使用的内容:

String iso = rs.getString(col).replaceFirst(" ", "T");

感谢大家的好回答,
B。


答案 1

JDBC getObject() 或 getTIMESTAMPTZ() 都返回 Oracle 的 TIMESTAMPTZ 对象,这实际上是无用的,因为它没有任何转换为日历(只有 Date、Time 和 Timestamp),所以再次丢失 TZ 信息。

这将是我的建议,作为获取您所寻求的信息的唯一可靠方法。

如果您使用的是 Java SE 8 并且拥有 ojdbc8,则可以使用 )。请注意,当您使用 )时,您可能会受到错误25792016的影响。getObject(int, OffsetDateTime.classgetObject(int, ZonedDateTime.class

使用Oracle的TIMESTAMPTZ java类,并从其内部(记录的)字节数组结构中手动提取相关值(即实现我自己的toString()方法,Oracle忘记在那里实现)。如果Oracle更改内部结构(不太可能),并且需要相对复杂的功能来实现和维护,则这是有风险的。

这就是我们最终采用的方法,直到Oracle JDBC驱动程序中提供了无错误的JSR-310支持。我们确定这是获取所需信息的唯一可靠方法。


答案 2

对#2略有改进:

CREATE OR REPLACE PACKAGE FORMAT AS
  FUNCTION TZ(T TIMESTAMP WITH TIME ZONE) RETURN VARCHAR2;
END;
/
CREATE OR REPLACE PACKAGE BODY FORMAT AS
  FUNCTION TZ(T TIMESTAMP WITH TIME ZONE) RETURN VARCHAR2
  AS
  BEGIN
    RETURN TO_CHAR(T,'YYYYMMDD"T"HH24:MI:SS.FFTZHTZM');
  END;
END;
/

在 SQL 中,这变成:

SELECT FORMAT.TZ(tstz) EVENT_TIME ...

它更具可读性。
如果你需要改变它,它是1个地方。
缺点是它是一个额外的函数调用。