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

2022-09-03 06:40:47

我目前正在与使用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

包过程调用返回一个 ref 游标,该游标被包装在 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)

无论我尝试什么,我都无法公开此 DATE 列的全部精度,以便通过 Java JDBC 和 iBATIS 返回。

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接触它之前就已经造成了失去数据精度的损害。

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


答案 1

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


在它从小动作过期之前快速抓取...

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

具体来说: 简单数据类型 日期和时间戳是怎么回事?本节介绍简单数据类型。:-)

在 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 映射到时间戳也没有问题。

在 9.2 中,对 RDBMS 添加了 TIMESTAMP 支持。日期和时间戳之间的区别在于,时间戳包含纳秒,而日期不包含纳秒。因此,从 9.2 开始,DATE 映射到 Date,TIMESTAMP 映射到 Timestamp。遗憾的是,如果您依赖 DATE 值来包含时间信息,则存在问题。

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

更改表以使用时间戳而不是日期。这可能很少可能,但当它是最好的解决方案时。

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

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

设置 V8 兼容连接属性。这告诉 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的存在不是为了修复日期到日期问题,而是为了支持与8i数据库的兼容性。8i(及更早版本)数据库不支持时间戳类型。设置 V8Compatible 不仅导致从数据库读取时 SQL DATE 映射到时间戳,还导致所有时间戳在写入数据库时都转换为 SQL DATE。由于 8i 不受支持,因此 11.1 JDBC 驱动程序不支持此兼容模式。因此,不支持 V8Compatible。

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

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

如果可能,应将列类型更改为时间戳,而不是 DATE。

-理查德


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

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

以下是问题描述:

我目前正在与使用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 类型的列:

选择 * 从表(pk_invoice_qry.get_contract_rate(?,?,?,?,?,?,?,?,?,?)) 按 from_date 排序

包过程调用返回一个 ref 游标,该游标被包装在 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 毫秒 星期二, 十二月 16, 2008 11:59:00 PM PST

但得到这个相反:自纪元以来1229414400000毫秒 星期二, 十二月 16, 2008 12:00:00 AM PST (作为类 java.sql.Date 的实例)

无论我尝试什么,我都无法公开此 DATE 列的全部精度,以便通过 Java JDBC 和 iBATIS 返回。

iBATIS的映射是这样的:

FROM_DATE : 2008-12-03 : 类 java.sql.日期

当前的 iBATIS 映射是这样的:

我也试过:

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

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


答案 2

我发现了如何解决这个问题。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日期时,我现在都会这样描述它:

<result property="from_date" jdbcType="DATETIME" javaType="date"/>