从Java创建并将SYS_REFCURSOR作为输入参数传递给Oracle过程

我必须与具有SYS_REFCURSOR作为输入参数的外部Oracle过程进行通信:

过程merge_objects(varchar2中的p_table_name,varchar2中的p_id_array,SYS_REFCURSOR中的p_cur_data)

我需要根据从客户端收到的数据传递SYS_REFCURSOR参数。 有没有办法在Java中创建这样的参数?

可能做这样的事情,但它有点繁琐。 我想出了两种方法来做到这一点,但它们都依赖于能够在数据库中创建对象。 我感谢您可能没有这样做的许可。

底线是必须在Oracle数据库本身内创建传递给存储过程的引用游标对象。 我们必须以某种方式将数据放入数据库中,然后在其周围放置一个光标。 您无法创建自己的ResultSet实现,并期望JDBC驱动程序和数据库从中读取数据。

为了演示的目的,我将创建以下表和过程:

 CREATE TABLE example_table (id NUMBER, name VARCHAR2(100)); CREATE OR REPLACE PROCEDURE p_insert_objects ( p_records IN SYS_REFCURSOR ) IS l_id example_table.id%TYPE; l_name example_table.name%TYPE; BEGIN LOOP FETCH p_records INTO l_id, l_name; EXIT WHEN p_records%NOTFOUND; INSERT INTO example_table (id, name) VALUES (l_id, l_name); END LOOP; END; / 

我们还将使用以下简单的Java类,它表示表的一行:

 class Row { private int id; private String name; public Row(int id, String name) { this.id = id; this.name = name; } public int getId() { return this.id; } public String getName() { return this.name; } } 

方法1:使用全局临时表

此方法涉及将所有数据插入临时表,然后创建游标以从中选择数据。 为此,我们需要在数据库中创建以下内容:

 CREATE GLOBAL TEMPORARY TABLE example_tmp (id NUMBER, name VARCHAR2(100)) ON COMMIT DELETE ROWS; 

完成后,以下代码应该起作用:

  // Clear out anything that happens to be in the temp table, eg because of a // previous call to this code in the same transaction. try (PreparedStatement stmt = conn.prepareStatement("DELETE FROM example_tmp")) { stmt.execute(); } List data = ... // get these from somewhere try (PreparedStatement stmt = conn.prepareStatement( "INSERT INTO example_tmp (id, name) VALUES (?, ?)")) { for (Row row : data) { stmt.setInt(1, row.getId()); stmt.setString(2, row.getName()); stmt.execute(); } } String plsql = "DECLARE\n" + " l_cursor SYS_REFCURSOR;\n" + "BEGIN\n" + " OPEN l_cursor FOR SELECT id, name FROM example_tmp;\n" + " p_insert_objects(l_cursor);\n"+ "END;"; try (PreparedStatement stmt = conn.prepareStatement(plsql)) { stmt.execute(); } 

方法2:类型和SQLData

此方法使用类型而不是临时表,并使用SQLData接口允许JDBC驱动程序将Java对象映射到Oracle对象。 它需要在数据库中创建以下类型(随意选择更好的名称):

 CREATE OR REPLACE TYPE row_t AS OBJECT (id NUMBER, name VARCHAR2(100)); / CREATE OR REPLACE TYPE rows_t AS TABLE OF row_t; / 

我们还需要修改Row类来实现SQLData :这需要添加以下三种方法:

  public void readSQL(SQLInput input, String typeName) throws SQLException { this.id = Integer.parseInt(input.readString()); this.name = input.readString(); } public void writeSQL(SQLOutput output) throws SQLException { output.writeString(Integer.toString(this.id)); output.writeString(this.name); } public String getSQLTypeName() { return "ROW_T"; } 

完成后,以下内容应允许您调用该过程:

  // Tell the connection to associate the Row class with the ROW_T type Map> map = conn.getTypeMap(); map.put("ROW_T", Row.class); conn.setTypeMap(map); List data = ... // get these from somewhere. Array array = ((OracleConnection)conn).createOracleArray("ROWS_T", data.toArray()); String plsql = "DECLARE\n" + " l_rows ROWS_T;\n" + " l_cursor SYS_REFCURSOR;\n" + "BEGIN\n" + " l_rows := ?;\n" + " OPEN l_cursor FOR SELECT id, name FROM TABLE(l_rows);\n" + " p_insert_objects(l_cursor);\n"+ "END;"; try (PreparedStatement stmt = conn.prepareStatement(plsql)) { stmt.setObject(1, array); stmt.execute(); } 

直接从Java传递SYS_REFCURSOR的解决方案存在。 无需在数据库中插入数据。

以下语句在Oracle中生成SYS_REFCURSOR(示例包含值和列名称):

打开cur_data FOR选择’000000’旅馆,’Ch’姓氏来自双;

现在我将展示如何实现这一点。 这是代码的测试工作示例。 过程merge_objects将SYS_REFCURSOR作为第三个输入参数。 Oracle的示例:

 public static void main(String[] args) { try { Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@...", "username", "password"); String plsql = "declare cur_data SYS_REFCURSOR;\n" + "BEGIN\n" + "OPEN cur_data FOR select '000000' inn, 'Ch' lastname from dual;\n" + "END;\n" + "merge_objects('tbl_o_persons',\n" + " '19863572,19863598',\n" + " cur_data);\n" + "CLOSE cur_data;\n" + "end;"; try (PreparedStatement stmt = conn.prepareStatement(plsql)) { stmt.execute(); } conn.close(); }catch(Exception ex){ System.out.println("Error: " + ex.toString()); } } 

因此,根据您的数据,您可以使用OPEN语句修改字符串,包含您的数据,从而直接从Java传递CURSOR到必要的过程。