【问题标题】:Mapping an Oracle stored procedure result to a custom Java type (class)将 Oracle 存储过程结果映射到自定义 Java 类型(类)
【发布时间】:2016-08-24 07:10:57
【问题描述】:

我必须在 Oracle (11g) 中调用一个使用单个 IN OUT 参数的存储过程。该参数是定义为

的 Oracle 自定义类型
 CREATE OR REPLACE TYPE "SEPADD"."T_NAPRAVI_NALOG_TEST" IS OBJECT
 (
    I_INICIJALI              varchar2(3)  ,          
    I_STATUS                 number(1)        
 )

实际类型更复杂,但我在这里对其进行了简化以提高可读性。使用这种类型(再次简化)的 Oracle 过程定义为

  CREATE OR REPLACE PROCEDURE "SEPADD"."GETNALOGTESTPROC"(nalog in out T_NAPRAVI_NALOG_TEST )
IS   
      BEGIN       
  nalog.I_INICIJALI := 'PC';         
      nalog.I_STATUS := nalog.I_STATUS + 3; 
END;

Oracle 自定义类型映射到实现 SQLData 接口的 Java 类。 (见https://docs.oracle.com/javase/tutorial/jdbc/basics/sqlcustommapping.html#implementing_sqldata

 public class TNapraviNalog implements SQLData{

private int I_STATUS;
private String I_INICIJALI;
private String sql_type = "T_NAPRAVI_NALOG_TEST";
public String getSQLTypeName() {
        return sql_type;
    }
    public int getIStatus(){
     return this.I_STATUS;
}
    public String getIInicijali(){
        return this.I_INICIJALI;
    }
    public void setIInicijali(String in){
        I_INICIJALI = in;
    }
    public void setIStatus(int st){
        I_STATUS = st;
    }
    public void readSQL(SQLInput stream, String type)
        throws SQLException {
        sql_type = type;
        I_INICIJALI = stream.readString();
        I_STATUS = stream.readInt();

    }

    public void writeSQL(SQLOutput stream)
        throws SQLException {
        stream.writeString(I_INICIJALI);
        stream.writeInt(I_STATUS);          
    }
}

现在,从我的 JDBC 代码中,我按以下方式调用 Oracle 存储过程

    Object obj=null;
    ResultSet rs=null;
    CallableStatement stmt=null;
    TNapraviNalog n = null;
    try{

          sqlQuery = "{call getnalogtestproc(?)}";

          Map m = conn.getTypeMap();
          m.put("sepadd.T_NAPRAVI_NALOG_TEST", Class.forName("ib.easyorm.db.TNapraviNalog"));//this maps the Java class to the Oracle custom type
          conn.setTypeMap(m);

          stmt=conn.prepareCall(sqlQuery);
          stmt.registerOutParameter(1, Types.STRUCT, "T_NAPRAVI_NALOG_TEST");
          stmt.setObject(1, paramValues.get(0) ); //paramValues.get(0) returns an instance of TNapraviNalog class

          stmt.execute();    

          obj = stmt.getObject(1, m);

          //obj = stmt.getObject(1,TNapraviNalog.class); this method is not implemented in the JDBC driver

    }catch(Exception e){
          throw new EasyORMException(e);
    }finally{
          closeResources(rs,stmt);
    }
    return obj;

现在,问题是,虽然我可以得到存储过程返回的结果,但结果并没有转换为 Java 类 (TNapraviNalog),所以我必须手动完成。我可以成功 使用 TNapraviNalog ( stmt.setObject(1, paramValues.get(0) ); ) 的实例调用 Oracle 过程,但我无法将结果转换为 TNapraviNalog。我真的很想 能够拥有类似的东西

 TNapraviNalog nalog = stmt.getObject(1, m); 

但是,这一行会导致异常(java.lang.ClassCastException: oracle.sql.STRUCT cannot be cast to ib.easyorm.db.TNapraviNalog)。 我猜测 JDBC 驱动程序不知道 stmt.getObject(1,m) 返回的实际类型,因此无法进行转换。

有人知道这是否可以使用普通的 JDBC 或 Hibernate 来完成?

编辑:Oracle 页面中的相关代码(cihan 七的答案中给出的链接)

从 Callable Statement OUT 参数中检索 SQLData 对象

假设您有一个调用 PL/SQL 函数 GETEMPLOYEE 的 OracleCallableStatement 实例 ocs。程序将员工编号传递给函数。该函数返回相应的 Employee 对象。要检索此对象,请执行以下操作:

1.准备一个OracleCallableStatement来调用GETEMPLOYEE函数,如下:

 OracleCallableStatement ocs = (OracleCallableStatement)conn.prepareCall("{ ? = call GETEMPLOYEE(?) }");

2.将empnumber 声明为GETEMPLOYEE 的输入参数。使用类型代码 OracleTypes.STRUCT 将 SQLData 对象注册为 OUT 参数。然后,运行语句。这可以按如下方式完成:

 ocs.setInt(2, empnumber); 
 ocs.registerOutParameter(1, OracleTypes.STRUCT, "EMP_OBJECT"); 
ocs.execute(); 

3.使用getObject 方法检索员工对象。以下代码假设有一个类型映射条目将 Oracle 对象映射到 Java 类型 Employee:

 Employee emp = (Employee)ocs.getObject(1); //my comment-->this doesn't seem to work

如果没有类型映射条目,则 getObject 将返回一个 oracle.sql.STRUCT 对象。将输出转换为 STRUCT 类型,因为 getObject 方法返回通用 java.lang.Object 类的实例。这样做如下:

STRUCT emp = (STRUCT)ocs.getObject(1); 

谢谢

【问题讨论】:

    标签: java oracle stored-procedures jdbc


    【解决方案1】:

    错误似乎在这一行:

          m.put("sepadd.T_NAPRAVI_NALOG_TEST", Class.forName("ib.easyorm.db.TNapraviNalog"));//this maps the Java class to the Oracle custom type
    

    这是您使用架构所有者限定 T_NAPRAVI_NALOG_TEST 类型的唯一行。您在其他两个地方引用它时没有架构名称。

    如果您以SEPADD 用户身份连接到您的数据库(您似乎是),您可以从该行中删除架构所有者前缀sepadd.。或者,尝试将上述行中的架构所有者更改为大写。

    【讨论】:

    • 我尝试在任何地方添加架构名称,但没有任何区别。当然,显式添加模式名称绝不是错误(无论使用的用户名如何),即使您已经注意到用户名确实是分隔的。删除架构名称没有帮助。不过,映射似乎没问题,因为我可以使用 TNapaviNalog 实例成功调用该过程。问题是 JDBC 驱动程序总是将结果作为 STRUCT 返回。似乎它从未尝试将结果转换为 TNapaviNalog 对象。
    • @dsp_user:这很奇怪。我对您的代码所做的一切就是将上面一行中的模式名称更改为大写。删除架构名称也有效。我在 Windows 10 x64 上使用 Oracle 11g XE 版本 11.2.0.2.0,并且我已经成功地使用我碰巧拥有的四个不同的 Oracle JDBC 驱动程序 JAR 运行了您的代码:ojdbc14.jar 版本 10.2.0.5 .0、ojdbc5.jar 版本 11.2.0.4.0、ojdbc6.jar 版本 11.2.0.2.0 和 ojdbc7.jar 版本 12.1.0.2.0。
    • “让它工作”到底是什么意思?你得到的结果是 TNapraviNalog 类型吗?我的代码确实可以工作,但结果并未转换为 TNapaviNalog,而是我总是从 stmt.getObject(1,m) 获取 STRUCT 对象。当然,我可以解析返回的对象并很好地获取值。
    • @dsp_user:当我说“让它工作”时,我的意思是“将调用stmt.getObject(1, m) 返回的值作为TNapraviNalog 对象而不是STRUCT 对象”。跨度>
    【解决方案2】:

    您尝试将您的 Struct 对象转换为TNapraviNalog。但根据 Oracle documentation,您应该首先将您的结构检索为 java.sql.STRUCT 对象,然后将其转换为 TNapraviNalog 对象。

    试试这个:

    java.sql.Struct jdbcStruct = (java.sql.Struct)stmt.getObject(1, m);
    

    【讨论】:

    • 不,文档说当没有定义类型映射时会返回 oracle.sql.STRUCT(java.sql.Struct 的超类)。无论如何,一个 Struct(或 STRUCT)不能转换为自定义类型(我试过了)
    • 当然,我得到的结果是 oracle.sql.STRUCT(java.sql.Struct 的子类),但它不能转换为 TNapaviNalog。
    • 也许您可以定义一个 generic java 类并尝试将 oracle.sql.STRUCT 转换为它,然后将您的 generic 类转换为 TNapraviNalog 类?
    • 当我说 STRUCT 不能强制转换为 TNapraviNalog 时,我的意思是它会编译,但它总是会在运行时生成 ClassCast 异常。这是意料之中的,因为这两种类型不扩展相同的类。 Oracle 文档说自定义类型对象可以直接从 CallableStatement.getObject() 获得。不知道这是驱动程序的错误还是我遗漏了什么。
    猜你喜欢
    • 2011-02-15
    • 1970-01-01
    • 2010-09-10
    • 1970-01-01
    • 2021-09-01
    • 2015-02-24
    • 2016-09-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多