ResultSet#getDate() 语义

Posted

技术标签:

【中文标题】ResultSet#getDate() 语义【英文标题】:ResultSet#getDate() semantics 【发布时间】:2013-07-10 13:52:07 【问题描述】:

我们从 ojdbc6-11.2.0.3.0 迁移到 ojdbc7-12.1.0.1 并观察到 ​​ResultSet#getDate() 语义的变化。以前,返回的java.sql.Date 将通过根据java.sql.Date 上指定的合同将小时、分钟、秒和毫秒设置为零来“标准化”。 ojdbc7 不再是这种情况,java.sql.Date 根据数据库上的值设置小时、分钟、秒和毫秒。

我查看了ResultSet#getDate() 的Javadoc,它没有明确说明哪种行为是正确的。我会假设旧的行为是规范的意图。我对吗?我们是否遇到了驱动程序错误?

【问题讨论】:

出现 ojdbc7-12.1.0.1 的另一个相关问题:PreparedStatement.setDate(index, java.sql.Date) 在将值存储到数据库之前不会截断任何时间组件。它可能只发生在 10g 上(我没有 11g 或 12c 在其他版本上测试它)。 【参考方案1】:

这显然是一个错误,因为 java.sql.Date 类的文档状态

为了符合 SQL DATE 的定义,由 java.sql.Date 实例包装的毫秒值必须通过在特定时区中将小时、分钟、秒和毫秒设置为零来进行“规范化”。实例已关联。

【讨论】:

【参考方案2】:

Oracle 不会更改 JDBC 规范。我们更新了 Oracle 数据库 JDBC 驱动程序文档。如果文档中有任何令人困惑或不正确的内容,我们将对其进行修复。不过,JDBC 规范没有变化。

以前版本的驱动不一致。在某些地方,他们将秒数归零。在其他地方,他们没有。在 12.1 中,我们使驱动程序保持一致。问题是,什么是正确的行为。无论哪种方式,一些客户都会看到行为发生变化。

经过漫长而激烈的辩论,我们决定对 Oracle 数据库客户来说最好的事情不是将秒数归零。让我解释一下。

ANSI SQL DATE 类型不存储秒。 JDBC 规范主要采用 ANSI SQL。 Oracle 数据库 DATE 类型确实存储秒。 ANSI SQL 委员会的成员向我保证,这完全符合 ANSI SQL。 JDBC 的目标是公开数据库。 JDBC 规范没有定义一些抽象数据库,目的是驱动程序实现该抽象数据库。 JDBC 驱动程序需要公开数据库的详细信息,而不是隐藏它们。 JDBC 定义了一些工具来抽象一些数据库细节,但程序员可以选择使用或不使用这些工具。 java.sql.Date 不会强制执行零秒行为,尽管它很容易做到。程序有责任强制执行或不强制执行该行为。

所以,Oracle DATE 有秒数。 Oracle JDBC 驱动程序公开了 Oracle 数据库。如果getDate 将秒数归零,则会丢失数据。对于某些用户来说,这无关紧要,但对于其他用户来说却是。由于 Oracle DATE 存储秒,因此许多 Oracle 数据库在 DATE 列中存储具有秒精度的时间。在这些情况下将秒归零会丢失信息。

如果程序将一个非零秒的日期传递给setDate,则程序创建了一个不合规的日期。如果驱动程序将秒数归零,则驱动程序丢弃了程序提供且数据库可以存储的信息。司机又一次丢失了信息。

编写 SQL 或 Java 将 get 和 set 的秒数归零很容易。尽管有可能,但要解决丢失信息的问题更加困难。

所以我们选择让驱动程序始终保持java.sql.Dates 上的秒数。 ResultSet.getDate 可以构造一个非零秒的java.sql.Date,但这准确地反映了数据库中的内容。如前所述, Date 的实现可以强制执行此操作,但没有。一种看待它的方法是程序在将日期存储在数据库中时创建了日期,因此这是程序的责任。驱动程序只是使用程序提供的数据。

我真的很抱歉驱动程序之前不一致。我们一直在努力清理不一致和奇怪的角落案例。但我们拥有庞大的安装基础。每次我们更改某些内容,甚至是明显的错误修复时,某处的某些客户都会受到影响。因此,我们尝试在改进驱动程序和保持向后兼容性之间取得平衡。 12c 是一个主要版本。我们借此机会做出了一些更明显的改变。我们对这次中断感到遗憾,但认为这对整体客户来说是正确的。

【讨论】:

感谢您的解释。 jdbc 规范说 java.sql.Date 没有时间组件(与 java.sql.Timestamp 不同)。时间组件在许多业务上下文(例如出生日期)中没有意义,并且此更改将导致在业务上下文中使用此行为的现有应用程序在迁移到 12c 时中断。如果开发人员需要时间组件,jdbc 规范表明他们应该对 DATE 列使用 get/setTimestamp() 而不是 get/setDate()。最后,它可以节省时间来识别驱动程序随附的自述文件中列出的更改问题。 我知道这个话题是两年前提出的,但对我来说仍然是个问题。在设置 JVM 参数时,是否有某种方法可以让 ojdbc7 表现得像 ojdbc6?这不是 Oracle 第一次破坏我公司的遗留代码,在版本之间进行此类更改(mapDateToTimestamp、J2EE13Compliant ...) "但认为这对整体客户来说是正确的做法" - 不,不是。这显然违反了 JDBC 规范。所以 Oracle(控制 Java 和 JDBC 规范)现在发布了一个甚至不兼容 JDBC 的驱动程序。 嗨,Douglas Surber,是否有官方 Oracle 链接,我可以在其中获取有关此类更改的发行说明?【参考方案3】:

我在 11g 数据库上分析了这个问题,可以确认 shonky linux 用户提到的PreparedStatement.setDate(index, java.sql.Date) 的行为(我测试了ojdbc6-11.2.0.4-gojdbc7-12.1.0.2)。

虽然新行为可能不会明确违反 JDBC 规范,但它肯定不适合具有 java.sql.Datejava.sql.Timejava.sql.Timestamp 以及相应数据类型的概念Types.DATETypes.TIMETypes.TIMESTAMP

因此,尽管 Douglas Surber 解释的“为现有 Oracle 客户减轻痛苦”的决定可能是有道理的,但从(独立于 DBMS)JDBC 的角度来看,它似乎并不正确。

【讨论】:

以上是关于ResultSet#getDate() 语义的主要内容,如果未能解决你的问题,请参考以下文章

JDBC ResultSet:我需要一个 getDateTime,但只有 getDate 和 getTimeStamp

ResultSet getDate() 返回不正确和正确的日期

来自ResultSet java的getDateTime

ResultSet next方法

ResultSet - 它是啥类型的对象?

获取Java中ResultSet返回的行数