为什么OJDBC 7不将CHAR数据类型映射为Java String?

5
OJDBC的工作之一是将Oracle数据类型映射到Java数据类型。但我们发现如果给定一个CHAR数据类型,它不会被映射到java.lang.String。表现出这种行为的版本为: OJDBC7 v12.1.0.2和OJDBC6 v12.1.0.1。旧版本确实将CHAR数据类型映射到java.lang.String。深入挖掘后,我们发现在OJDBC的oracle.jdbc.driver包中有一个StructMetaData类实现了Oracle数据类型到Java类型的映射。其中一个值得注意的方法是: 'getColumnClassName(int arg0)'。我们注意到对于OJDBC v7,映射到java.lang.String的情况如下:
    int arg1 = this.getColumnType(arg0);
    switch (arg1) {
    case -104:
        return "oracle.sql.INTERVALDS";
    case -103:
        return "oracle.sql.INTERVALYM";
    case -102:
        return "oracle.sql.TIMESTAMPLTZ";
    case -101:
        return "oracle.sql.TIMESTAMPTZ";
    case -15:
    case -9:
    case 12:
        return "java.lang.String";
     ...

然而,在旧版的OJDBC实现中,它看起来像这样:
    int arg1 = this.getColumnType(arg0);
    switch (arg1) {
    case -104:
        return "oracle.sql.INTERVALDS";
    case -103:
        return "oracle.sql.INTERVALYM";
    case -102:
        return "oracle.sql.TIMESTAMPLTZ";
    case -101:
        return "oracle.sql.TIMESTAMPTZ";
    case -15:
    case -9:
    case 1:
    case 12:
        return "java.lang.String";
    ...

在后一种情况下,有一个额外的案例映射到java.lang.String,即“case 1”。然而,在上面显示的第一段代码中,“case 1”并没有映射到java.lang.String

进一步查看发现,同一StructMetaData类的getColumnTypeName(int arg0)方法中将这个“case 1”映射到了CHAR类型。

public String getColumnTypeName(int arg0) throws SQLException {
    int arg1 = this.getColumnType(arg0);
    int arg2 = this.getValidColumnIndex(arg0);
    switch (arg1) {
    case -104:
        return "INTERVALDS";
    case -103:
        return "INTERVALYM";
    case -102:
        return "TIMESTAMP WITH LOCAL TIME ZONE";
    case -101:
        return "TIMESTAMP WITH TIME ZONE";
    case -15:
        return "NCHAR";
    case -13:
        return "BFILE";
    case -9:
        return "NVARCHAR";
    case -2:
        return "RAW";
    case 1:
        return "CHAR";
 ...

因此,如果我们使用OJDBC 7或OJDBC6 v12.1.0.1,并将CHAR指定为列的数据类型,则以下代码在调用此列的索引时返回null

 for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
     ...
     resultSetMetaData.getColumnClassName(columnIndex)
     ...

如果我替换了旧版本的OJDBC jar文件(例如:11.2.0.3),那么相同的代码会返回:java.lang.String。这是一个bug还是被设计移除了?有没有人遇到过类似问题?

1
你有检查过驱动程序的发布说明吗?如果你认为这是错误的,请在我的Oracle支持中心打开一个SR。我对提供的解决方案非常满意。 - Michael-O
1个回答

1

很好的发现...!

看起来确实像是一个bug; 也许唯一反对的论点是这将是Oracle极其大意的疏忽。

支持bug:

  • 向后兼容性问题。jdbc驱动程序的更新会干扰那些明确依赖于CHAR声明的现有应用程序
  • 关于JPublisher Data Type and Java-to-Java Type Mappings 的说明描述了12c版本的映射

(注意第一行,与CHAR类型相关,它映射到java.lang.String)

| SQL and PL/SQL Data Type | Oracle Mapping   | JDBC Mapping
|------------------------- |------------------|-----------------------------------------------
| CHAR, CHARACTER, LONG,   |                  | 
|STRING, VARCHAR, VARCHAR2 | oracle.sql.CHAR  | java.lang.String
| NCHAR, NVARCHAR2         | oracle.sql.NCHAR | oracle.sql.NString 
| NCLOB                    | oracle.sql.NCLOB | oracle.sql.NCLOB 

防止错误:

  • 一个花哨的假设可能会认为Oracle正在试图将CHAR从支持的类型中删除。实际上,CHAR类型与VARCHAR2相比真正没有任何好处,并且一些缺点使其成为不可选项。过去我喜欢"CHAR"这个事实可以向其他开发人员传达你定义具有预定义和不变长度的项目的意图,但它甚至不能强制执行这一点(如果您用太短的值填充它,它只是填充字符串)。 如果您感兴趣,优秀书籍Expert Oracle Database Architecture - Oracle Database 9i, 10g, and | Thomas Kyte | Apress中有一节专门讨论此主题。您可以在Ask Tom "Char Vs Varchar"中阅读作者引用自己书籍的摘录:
事实上,CHAR/NCHAR 实际上只是 VARCHAR2/NVARCHAR2 的伪装,因此我认为只有两种字符字符串类型值得考虑,即 VARCHAR2 和 NVARCHAR2。我从未在任何应用程序中使用 CHAR 类型。由于 CHAR 类型总是将结果字符串空白填充到固定宽度,因此我们很快发现它在表段和任何索引段中消耗了最大的存储空间。这已经够糟糕了,但是还有另一个重要原因要避免使用 CHAR/NCHAR 类型:它们会在需要检索此信息的应用程序中造成混淆(许多应用程序在存储后无法“找到”其数据)。原因与字符字符串比较规则及其执行严格程度有关......

[接下来是同一帖子中的一个很好的示例;非常值得阅读]

当然,CHAR 不好的事实并不能证明 Oracle 没有明确通知就破坏了现有的应用程序;因此,认为这是一个错误更加合理。

鉴于理论上不会有副作用,作为一种极端的解决方法,您可以修改所有涉及的表以重新定义它们的 CHAR 类型为 VARCHAR2(如果您有权这样做,并且这是可行的)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接