16

我目前正在使用来自 Java 的 iBATIS 处理 Oracle SQL DATE 转换问题。

我正在使用 Oracle JDBC 瘦驱动程序 ojdbc14 版本 10.2.0.4.0。iBATIS 版本 2.3.2。Java 1.6.0_10-rc2-b32。

问题围绕此 SQL 片段返回的 DATE 类型的列:

SELECT *
FROM   TABLE(pk_invoice_qry.get_contract_rate(?,?,?,?,?,?,?,?,?,?)) order by from_date

包过程调用返回一个引用游标,该游标被包装在一个 TABLE 中,然后很容易读取结果集,就像对表的选择查询一样。

在 PL/SQL Developer 中,返回的列之一是 SQL DATE 类型的 FROM_DATE,具有一天中时间的精度:

Tue Dec 16 23:59:00 PST 2008

但是当我通过 iBATIS 和 JDBC 访问它时,该值只保留当天的精度:

Tue Dec 16 12:00:00 AM PST 2008

像这样显示时会更清楚:

本来应该:

1229500740000 milliseconds since epoch
Tuesday, December 16, 2008 11:59:00 PM PST

但是得到这个:

1229414400000 milliseconds since epoch
Tuesday, December 16, 2008 12:00:00 AM PST
(as instance of class java.sql.Date)

无论我尝试什么,我都无法公开要通过 Java JDBC 和 iBATIS 返回的此 DATE 列的完整精度。

iBATIS 的映射是这样的:

FROM_DATE : 2008-12-03 : class java.sql.Date

当前的 iBATIS 映射是这样的:

<result property="from_date" jdbcType="DATE" javaType="java.sql.Date"/>

我也试过:

<result property="from_date" jdbcType="DATETIME" javaType="java.sql.Date"/>

或者

<result property="from_date" jdbcType="TIMESTAMP" javaType="java.sql.Timestamp"/>

但是所有尝试的映射都会产生相同的截断日期值。就好像 JDBC 在 iBATIS 接触它之前就已经造成了丢失数据精度的损害。

显然,当我在 PL/SQL Developer 中运行与测试脚本相同的 SQL 片段时,我会通过 JDBC 和 iBATIS 丢失一些数据精度。完全不能接受,非常令人沮丧,最终非常可怕。

4

7 回答 7

9

完整信息(它比此处描述的更复杂,可能取决于正在使用的特定版本的 Oracle 驱动程序)在 Richard Yee 的回答中 - [现在到 Nabble 的链接已过期]


在它从 nabble 到期之前快速抓取...

罗杰,见:http ://www.oracle.com/technetwork/database/enterprise-edition/jdbc-faq-090281.html#08_01

具体来说:简单数据类型 DATE 和 TIMESTAMP 发生了什么?本节介绍简单数据类型。:-)

在 9.2 之前,Oracle JDBC 驱动程序将 DATE SQL 类型映射到 java.sql.Timestamp。这具有一定的意义,因为 Oracle DATE SQL 类型与 java.sql.Timestamp 一样包含日期和时间信息。由于 java.sql.Date 不包括时间信息,因此更明显的映射到 java.sql.Date 有点问题。也是RDBMS不支持TIMESTAMP SQL类型的情况,所以将DATE映射到Timestamp没有问题。

在 9.2 TIMESTAMP 支持被添加到 RDBMS。DATE 和 TIMESTAMP 之间的区别在于 TIMESTAMP 包括纳秒,而 DATE 不包括。因此,从 9.2 开始,DATE 映射到 Date 并且 TIMESTAMP 映射到 Timestamp。不幸的是,如果您依赖 DATE 值来包含时间信息,就会出现问题。

有几种方法可以解决这个问题:

更改您的表以使用 TIMESTAMP 而不是 DATE。这可能很少有可能,但它是最好的解决方案。

更改您的应用程序以使用 defineColumnType 将列定义为 TIMESTAMP 而不是 DATE。这样做存在问题,因为您真的不想使用 defineColumnType 除非您必须(请参阅什么是 defineColumnType 以及何时应该使用它?)。

更改您的应用程序以使用 getTimestamp 而不是 getObject。如果可能,这是一个很好的解决方案,但是许多应用程序包含依赖于 getObject 的通用代码,因此并不总是可行的。

设置 V8Compatible 连接属性。这告诉 JDBC 驱动程序使用旧映射而不是新映射。您可以将此标志设置为连接属性或系统属性。通过将连接属性添加到传递给 DriverManager.getConnection 或 OracleDataSource.setConnectionProperties 的 java.util.Properties 对象来设置连接属性。通过在 java 命令行中包含 -D 选项来设置系统属性。

java -Doracle.jdbc.V8Compatible="true" MyApp Oracle JDBC 11.1 修复了这个问题。从此版本开始,驱动程序默认将 SQL DATE 列映射到 java.sql.Timestamp。无需设置 V8Compatible 即可获得正确的映射。强烈反对 V8Compatible。你根本不应该使用它。如果你确实将它设置为 true 它不会伤害任何东西,但你应该停止使用它。

虽然它很少以这种方式使用,但 V8Compatible 的存在不是为了修复 DATE to Date 问题,而是为了支持与 8i 数据库的兼容性。8i(和更早版本)数据库不支持 TIMESTAMP 类型。设置 V8Compatible 不仅会导致 SQL DATE 在从数据库读取时映射到 Timestamp,还会导致所有 Timestamp 在写入数据库时​​都转换为 SQL DATE。由于不支持 8i,因此 11.1 JDBC 驱动程序不支持此兼容模式。因此,不支持 V8Compatible。

如上所述,11.1 驱动程序在从数据库读取时默认将 SQL DATE 转换为 Timestamp。这始终是正确的做法,而 9i 的更改是一个错误。11.1 驱动程序已恢复为正确行为。即使您没有在应用程序中设置 V8Compatible,在大多数情况下,您也不应该看到任何行为差异。如果您使用 getObject 读取 DATE 列,您可能会注意到不同之处。结果将是时间戳而不是日期。由于 Timestamp 是 Date 的子类,这通常不是问题。您可能会注意到不同之处在于您是否依赖从 DATE 到 Date 的转换来截断时间组件,或者您是否对值执行 toString。否则更改应该是透明的。

如果由于某种原因您的应用程序对这种更改非常敏感并且您必须具有 9i-10g 行为,那么您可以设置一个连接属性。将 mapDateToTimestamp 设置为 false,驱动程序将恢复为默认的 9i-10g 行为并将 DATE 映射到 Date。

如果可能,您应该将列类型更改为 TIMESTAMP 而不是 DATE。

-理查德


Roger Voss 写道: 我在 stackoverflow 上发布了以下问题/问题,所以如果有人知道解决方案,很高兴看到它在那里得到回答:

通过 Java JDBC 使用 iBATIS 的 Oracle SQL DATE 转换问题

以下是问题描述:

我目前正在努力解决使用来自 Java 的 iBATIS 的 Oracle sql DATE 转换问题。

我正在使用 Oracle JDBC 瘦驱动程序 ojdbc14 版本 10.2.0.4.0。iBATIS 版本 2.3.2。Java 1.6.0_10-rc2-b32。

问题围绕此 SQL 片段返回的 DATE 类型的列:

SELECT * FROM TABLE(pk_invoice_qry.get_contract_rate(?,?,?,?,?,?,?,?,?,?)) 按 from_date 排序

包过程调用返回一个引用游标,该游标被包装在一个 TABLE 中,然后很容易读取结果集,就像对表的选择查询一样。

在 PL/SQL Developer 中,返回的列之一是 SQL DATE 类型的 FROM_DATE,具有一天中时间的精度:

Tue Dec 16 23:59:00 PST 2008

但是当我通过 iBATIS 和 JDBC 访问它时,该值只保留当天的精度:

Tue Dec 16 12:00:00 AM PST 2008

像这样显示时会更清楚:

应该是:1229500740000 毫秒,自 2008 年 12 月 16 日星期二太平洋标准时间晚上 11:59:00

但改为:1229414400000 毫秒,自 2008 年 12 月 16 日星期二太平洋标准时间上午 12:00:00 以来(作为 java.sql.Date 类的实例)

无论我尝试什么,我都无法公开要通过 Java JDBC 和 iBATIS 返回的此 DATE 列的完整精度。

iBATIS 的映射是这样的:

FROM_DATE:2008-12-03:类 java.sql.Date

当前的 iBATIS 映射是这样的:

我也试过:

或者

但是所有尝试的映射都会产生相同的截断日期值。就好像 JDBC 在 iBATIS 接触它之前就已经造成了丢失数据精度的损害。

显然,当我在 PL/SQL Developer 中运行与测试脚本相同的 SQL 片段时,我会通过 JDBC 和 iBATIS 失去一些数据精度。完全不能接受,非常令人沮丧,最终非常可怕。

于 2009-01-20T12:50:41.060 回答
6

我发现了如何解决这个问题。iBATIS 允许注册自定义类型处理程序。所以在我的 sqlmap-config.xml 文件中我添加了这个:

<typeAlias alias="OracleDateHandler" type="com.tideworks.ms.CustomDateHandler"/>
<typeHandler callback="OracleDateHandler" jdbcType="DATETIME" javaType="date"/>

然后添加了这个实现iBATIS TypeHandlerCallback 接口的类:

// corrected getResult()/setParameter() to correctly deal with when value is null
public class CustomDateHandler implements TypeHandlerCallback {
    @Override
    public Object getResult(ResultGetter getter) throws SQLException {
        final Object obj = getter.getTimestamp();
        return obj != null ? (Date) obj : null;
    }

    @Override
    public void setParameter(ParameterSetter setter,Object value) throws SQLException {
        setter.setTimestamp(value != null ? new Timestamp(((Date)value).getTime()) : null);
    }

    @Override
    public Object valueOf(String datetime) {
        return Timestamp.valueOf(datetime);
    }
}

当我不需要映射 Oracle DATE 时,我现在这样描述它:

<result property="from_date" jdbcType="DATETIME" javaType="date"/>
于 2008-12-21T08:45:16.330 回答
3

我已经解决了我的问题,jdbcType="TIMESTAMP"而不是jdbcType="DATE"

• 问题:

<result column="MY_UTC_POS" property="myUtcPosition" jdbcType="DATE" />

• 解决了:

<result column="MY_UTC_POS" property="myUtcPosition" jdbcType="TIMESTAMP" />
于 2009-03-18T12:42:43.850 回答
1

问题出在 Oracle 驱动程序上。

我找到的最佳解决方案是将所有 jdbcType="DATE" 更改为 jdbcType="TIMESTAMP" 并将所有 #column_name:DATE# 更改为 #column_name:TIMESTAMP#

所以改变:

<result property="from_date" jdbcType="DATE" javaType="java.sql.Date"/>

<result property="from_date" jdbcType="TIMESTAMP" javaType="java.sql.Date"/>
于 2009-09-03T22:03:57.333 回答
0

问题是使用java.sql.Date. 根据Javadoc,由实例包装的毫秒值java.sql.Date必须通过在与实例关联的特定时区中将小时、分钟、秒和毫秒设置为零来“规范化”,以符合 SQL 的定义DATE

于 2008-12-20T23:50:34.503 回答
0

是的,我明白了——普通的 SQL DATE 标准必须只存储到当天的分辨率。事实上,这里有一段关于 Oracle 的 DATE 类型的片段:

Oracle 同时支持日期和时间,尽管与 SQL2 标准不同。Oracle 没有使用两个单独的实体(日期和时间),而是只使用一个实体 DATE。DATE 类型以一种特殊的内部格式存储,不仅包括月、日和年,还包括小时、分钟和秒。

这表明 Oracle 的 DATE 超过了标准 SQL DATE。

嗯,Oracle PL/SQL 人员广泛使用 DATE 来保存他们依赖于秒的分辨率的值。看起来 iBATIS 需要类似于 Hibernate sql 方言概念的东西,而不是通过 java.sql.Date 解释 DATE,而是可以覆盖并改为通过 java.util.Date 解释,Javadocs 将其定义为允许毫秒分辨率。

不幸的是,当我将映射更改为以下内容时:

<result property="from_date" jdbcType="DATE" javaType="java.util.Date"/>

或者

<result property="from_date" jdbcType="DATETIME" javaType="java.util.Date"/>

它似乎仍然首先将 SQL DATE 转换为 java.sql.Date 并丢失了时间精度。

于 2008-12-21T03:26:43.943 回答
0

Richard Yee 提到 Oracle 的最新驱动程序解决了这个问题。我可以确认。在这里使用 10.2 驱动程序也有同样的问题,今天升级到 ojdbc5.jar (11.2.0.1.0),现在问题消失了。

于 2009-10-06T16:55:21.713 回答