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

Posted

技术标签:

【中文标题】从 Java 创建并将 SYS_REFCURSOR 作为输入参数传递给 Oracle 过程【英文标题】:Create and pass SYS_REFCURSOR as an input parameter to Oracle procedure from Java 【发布时间】:2017-08-24 15:13:01 【问题描述】:

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

procedure merge_objects(p_table_name in varchar2, p_id_array in varchar2, p_cur_data in SYS_REFCURSOR)

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

【问题讨论】:

Calling PL/SQL procedure with SYS_REFCURSOR as IN parameter using JDBC的可能重复 【参考方案1】:

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

底线是传递到存储过程的引用游标对象必须在 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; 

方法一:使用全局临时表

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

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, e.g. because of a 
    // previous call to this code in the same transaction.
    try (PreparedStatement stmt = conn.prepareStatement("DELETE FROM example_tmp")) 
        stmt.execute();
    

    List<Row> 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<String, Class<?>> map = conn.getTypeMap();
    map.put("ROW_T", Row.class);
    conn.setTypeMap(map);        

    List<Row> 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();
    

【讨论】:

这似乎是一个很好的答案,现在我明白了。谢谢,我会尝试这些方法。 我怀疑我应该传递给我的过程的实际光标可以通过 (ResultSet) stmt.getObject(?) 获得,对吧?是否可以在答案中添加获取光标? 哦!我看到您建议在 java 中将数据保存到数据库中,然后让 Oracle 将光标传递给过程,对吗? 请参阅下面的从 Java 传递 CURSOR 而不将数据保存到数据库的简单方法。【参考方案2】:

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

以下语句在 Oracle 中生成 SYS_REFCURSOR(带有值和列名的示例):

OPEN cur_data FOR select '000000' inn, 'Ch' lastname from dual;

现在我将展示如何实现这一点。这是代码的测试工作示例。 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 传递给必要的过程。

【讨论】:

以上是关于从 Java 创建并将 SYS_REFCURSOR 作为输入参数传递给 Oracle 过程的主要内容,如果未能解决你的问题,请参考以下文章

在 SYS_REFCURSOR 中执行动态 sql 语句

在过程或函数中使用 SYS_REFCURSOR

如何在 oracle 中使用 sys_refcursor 创建动态 sql

创建具有 SYS_REFCURSOR 作为输出参数的 mysql 过程时出错

Oracle - pl sql 从 SYS_REFCURSOR 中选择

Oracle JDBC从过程中获取SYS_REFCURSOR