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

18

我正在尝试了解使用JDBC从Oracle存储过程/函数获取表数据的不同方法。六种方式如下:

  1. 将架构级表类型作为OUT参数返回的存储过程
  2. 将程序包级表类型作为OUT参数返回的存储过程
  3. 将程序包级游标类型作为OUT参数返回的存储过程
  4. 返回架构级表类型的函数
  5. 返回程序包级表类型的函数
  6. 返回程序包级游标类型的函数

以下是一些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

显然,我对以下内容的理解存在困难:

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

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

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


你期望的结果是表结构,比如column1 varchar2 (100), column2 varchar2 (50)...吗?还是想看到“IS REF CURSOR”或类似的内容? - Joel Slowik
我获取有关光标/表类型的信息越多,就越好。但无论如何,我认为通过JDBC的ResultSet.getMetaData()方法,我可以实时发现这些信息。 - Lukas Eder
@LukasEder MySQL怎么样?我需要使用JDBC在Java中调用一个返回表格的存储过程...通过JDBC使用MySQL是否可行? - Sarah Akhavan
@Jack-in-the-box:请提出一个新问题。这个问题是一个非常具体的关于Oracle的问题。 - Lukas Eder
@LukasEder 谢谢。我前几天问过这个问题 :) 这里 - Sarah Akhavan
2个回答

11

无法从Java访问PL/SQL对象(情况2和5 =包级对象),请参见"java-在Oracle存储过程中传递数组"。但是,您可以访问SQL类型(情况1和4)。

要将PL/SQL的OUT参数传递到Java中,您可以使用Tom Kyte的一个线程中描述的方法,使用OracleCallableStatement。由于您检索的是Object表而不是VARCHAR表,因此您的代码将具有额外的步骤。

这里是一个使用Table of SQL Object的演示,首先进行设置:

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”类型有关,所以我会尝试你建议的方法。 - Lukas Eder
啊,我真傻。我忘记在注册“OUT”参数时提供“ARRAY”类型名称了。 - Lukas Eder
然而,仍然有一件事情让我困扰,那就是我可以访问包级别的REF CURSOR类型。我认为无法访问包级别的TABLE类型只是JDBC驱动程序实现上的缺陷... - Lukas Eder
1
我认为您可以访问引用游标类型,因为它是通用的(所有引用游标都相同)。SQL 也无法访问 PL/SQL 类型(例如,在 SQL*Plus 中,您的查询 #5 将失败),因此我不确定 jdbc 无法访问这些类型是一个缺陷还是存在技术限制(PL/SQL 类型由于某些原因是“私有”的?)。 - Vincent Malgrat
1
@Lukas 在最近的数据库版本(10g?)中,如果函数被声明为PIPELINED,则可以从SQL包级别访问从函数返回的表。您能否使用管道函数代替常规函数? - Vincent Malgrat
显示剩余2条评论

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集合类型是否可以被实例化。 - Lukas Eder

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