使用 JDBC 从存储过程中获取 Oracle 表类型

Posted

技术标签:

【中文标题】使用 JDBC 从存储过程中获取 Oracle 表类型【英文标题】:Fetch Oracle table type from stored procedure using JDBC 【发布时间】:2011-09-18 14:55:13 【问题描述】:

我试图了解使用 JDBC 从 Oracle 存储过程/函数获取表数据的不同方法。这六种方式分别是:

    将架构级表类型作为 OUT 参数返回的过程 将包级表类型作为 OUT 参数返回的过程 将包级游标类型作为 OUT 参数返回的过程 函数返回模式级表类型 函数返回包级表类型 函数返回包级游标类型

以下是 PL/SQL 中的一些示例:

-- schema-level table type
CREATE TYPE t_type AS OBJECT (val VARCHAR(4));
CREATE TYPE t_table AS TABLE OF t_type;

CREATE OR REPLACE PACKAGE t_package AS
  -- package level table type
  TYPE t_table IS TABLE OF some_table%rowtype;
  -- package level cursor type
  TYPE t_cursor IS REF CURSOR;
END library_types;

-- and example procedures:
CREATE PROCEDURE p_1 (result OUT t_table);
CREATE PROCEDURE p_2 (result OUT t_package.t_table);
CREATE PROCEDURE p_3 (result OUT t_package.t_cursor);
CREATE FUNCTION f_4 RETURN t_table;
CREATE FUNCTION f_5 RETURN t_package.t_table;
CREATE FUNCTION f_6 RETURN t_package.t_cursor;

我已经成功用JDBC调用了3、4、6:

// Not OK: p_1 and p_2
CallableStatement call = connection.prepareCall(" call p_1(?) ");
call.registerOutParameter(1, OracleTypes.CURSOR);
call.execute(); // Raises PLS-00306. Obviously CURSOR is the wrong type

// OK: p_3
CallableStatement call = connection.prepareCall(" call p_3(?) ");
call.registerOutParameter(1, OracleTypes.CURSOR);
call.execute();
ResultSet rs = (ResultSet) call.getObject(1); // Cursor results

// OK: f_4
PreparedStatement stmt = connection.prepareStatement("select * from table(f_4)");
ResultSet rs = stmt.executeQuery();

// Not OK: f_5
PreparedStatement stmt = connection.prepareStatement("select * from table(f_5)");
stmt.executeQuery(); // Raises ORA-00902: Invalid data type

// OK: f_6
CallableStatement call = connection.prepareCall(" ? = call f_6 ");
call.registerOutParameter(1, OracleTypes.CURSOR);
call.execute();
ResultSet rs = (ResultSet) call.getObject(1); // Cursor results

很明显,我很难理解

    如何从存储过程中的 OUT 参数中检索架构级别和包级别的表类型 如何从存储的函数中检索包级表类型

我似乎找不到任何关于此的文档,因为每个人总是使用游标而不是表类型。也许是因为不可能?不过,我更喜欢表类型,因为它们是正式定义的,并且可以使用字典视图(至少是模式级别的表类型)来发现。

注意:显然,我可以编写一个包装函数,返回 OUT 参数和包级表类型。但我更喜欢干净的解决方案。

【问题讨论】:

您是否期望结果是表的结构,即 column1 varchar2 (100)、column2 varchar2 (50)...?或者你想看'IS REF CURSOR'或类似的东西吗? 我获得的有关光标/表格类型的信息越多越好。但无论如何,我认为使用 JDBC 的 ResultSet.getMetaData() 我可以即时发现这些信息 @LukasEder mysql 怎么样?我需要使用 jdbc 在 java 中调用一个过程,它返回一个表......是否可以通过 jdbc 使用 mysql? @Jack-in-the-box:请提出一个新问题。这是一个非常具体的 Oracle 相关问题 @LukasEder 谢谢。前几天我问过:)here 【参考方案1】:

你也可以使用下面的一个

public List<EmployeeBean> fetchDataFromSPForRM(String sInputDate) 

         List<EmployeeBean> employeeList = new ArrayList<EmployeeBean>();

         Connection dbCon = null;
         ResultSet data = null;
         CallableStatement cstmt = null;


         try 
                dbCon = DBUtil.getDBConnection();
                String sqlQuery = "? = call PKG_HOLD_RELEASE.FN_RM_PDD_LIST()";

                cstmt = dbCon.prepareCall(sqlQuery);

                cstmt.registerOutParameter(1, OracleTypes.CURSOR);

                cstmt.execute();

                data = (ResultSet) cstmt.getObject(1);              

                    while(data.next())
                        EmployeeBean employee = new EmployeeBean();

                        employee.setEmpID(data.getString(1));
                        employee.setSubBusinessUnitId((Integer)data.getObject(2));
                        employee.setMonthOfIncentive((Integer)data.getObject(3));
                        employee.setPIPStatus(data.getString(5));
                        employee.setInvestigationStatus(data.getString(6));
                        employee.setEmpStatus(data.getString(7));
                        employee.setPortfolioPercentage((Integer)data.getObject(8));
                        employee.setIncentive((Double)data.getObject(9));
                        employee.setTotalSysemHoldAmt((Double)data.getObject(10));
                        employee.setTotalManualHoldAmt((Double)data.getObject(11));

                        employeeList.add(employee);
                    

             catch (SQLException e) 
                e.printStackTrace();
            finally
                try 

                    if(data != null)

                            data.close();               
                            data = null;
                    
                    if(cstmt != null)

                        cstmt.close();
                        cstmt = null;
                    
                    if(dbCon != null)

                            dbCon.close();              
                            dbCon = null;
                    

                 catch (SQLException e) 
                    e.printStackTrace();
                
            


        return employeeList;                
     

【讨论】:

谢谢。这不是这个问题的重点。是的,您可以实现从存储过程返回的 (ref) 游标。问题是关于什么类型的“游标式”PL/SQL 类型可以物化,特别是:PL/SQL 集合类型可以物化【参考方案2】:

您无法从 java 访问 PLSQL 对象(案例 2 和 5 = 包级对象),请参阅 "java - passing array in oracle stored procedure"。但是,您可以访问 SQL 类型(案例 1 和 4)。

要从 PL/SQL 获取 OUT 参数到 java,您可以使用 one of Tom Kyte's thread 中描述的方法使用 OracleCallableStatement。您的代码将有一个额外的步骤,因为您检索的是 Object 表而不是 VARCHAR 表。

这是一个使用 SQL 对象表的演示,首先是设置:

SQL> CREATE TYPE t_type AS OBJECT (val VARCHAR(4));
  2  /
Type created

SQL> CREATE TYPE t_table AS TABLE OF t_type;
  2  /
Type created

SQL> CREATE OR REPLACE PROCEDURE p_sql_type (p_out OUT t_table) IS
  2  BEGIN
  3     p_out := t_table(t_type('a'), t_type('b'));
  4  END;
  5  /
Procedure created

实际的java类(使用dbms_output.put_line记录因为我会从SQL调用它,如果从java调用使用System.out.println):

SQL> CREATE OR REPLACE
  2  AND COMPILE JAVA SOURCE NAMED "ArrayDemo"
  3  as
  4  import java.sql.*;
  5  import oracle.sql.*;
  6  import oracle.jdbc.driver.*;
  7  
  8  public class ArrayDemo 
  9     
 10     private static void log(String s) throws SQLException 
 11        PreparedStatement ps =
 12           new OracleDriver().defaultConnection().prepareStatement
 13           ( "begin dbms_output.put_line(:x); end;" );
 14        ps.setString(1, s);
 15        ps.execute();
 16        ps.close();
 17     
 18  
 19     public static void getArray() throws SQLException 
 20  
 21        Connection conn = new OracleDriver().defaultConnection();
 22  
 23        OracleCallableStatement cs =
 24           (OracleCallableStatement)conn.prepareCall
 25           ( "begin p_sql_type(?); end;" );
 26        cs.registerOutParameter(1, OracleTypes.ARRAY, "T_TABLE");
 27        cs.execute();
 28        ARRAY array_to_pass = cs.getARRAY(1);
 29  
 30        /*showing content*/
 31        Datum[] elements = array_to_pass.getOracleArray();
 32  
 33        for (int i=0;i<elements.length;i++)
 34           Object[] element = ((STRUCT) elements[i]).getAttributes();
 35           String value = (String)element[0];
 36           log("array(" + i + ").val=" + value);
 37        
 38     
 39  
 40  /
Java created

我们称之为:

SQL> CREATE OR REPLACE
  2  PROCEDURE show_java_calling_plsql
  3  AS LANGUAGE JAVA
  4  NAME 'ArrayDemo.getArray()';
  5  /

Procedure created

SQL> EXEC show_java_calling_plsql;

array(0).val=a
array(1).val=b

【讨论】:

感谢您的回答!我认为这可能与ARRAY 类型有关,所以我将尝试您的建议。 啊,我真傻。我唯一忘记的是在注册OUT 参数时提供ARRAY 类型名称。 有一点仍然困扰着我,我可以访问包级别的 REF CURSOR 类型。我认为无法访问包级 TABLE 类型只是 JDBC 驱动程序的一个实现缺陷...... 我认为您可以访问 ref 游标类型,因为它是通用的(所有 ref 游标都是相同的)。 SQL 也无法访问 PL/SQL 类型(例如,您的查询 #5 在 SQL*Plus 中会失败)所以我不确定 jdbc 无法访问这些类型是缺陷还是存在技术限制( PL/SQL 类型由于某种原因是“私有的”?)。 @Lukas 在最新版本的数据库(10g?)中,如果函数声明为 PIPELINED,您可以从函数返回的 SQL 包级表中访问。可以使用流水线函数代替常规函数吗?

以上是关于使用 JDBC 从存储过程中获取 Oracle 表类型的主要内容,如果未能解决你的问题,请参考以下文章

JDBC:Oracle 存储过程返回嵌套表

带有 PL SQL 表类型参数的 Oracle 存储过程的 JDBC 调用

JDBC调用存储过程的例子

Oracle JDBC从过程中获取SYS_REFCURSOR

如何从 JDBC 中的存储过程中获取输出

使用 JDBC 从 PL/SQL 存储函数中获取表返回值