我必须与具有SYS_REFCURSOR作为输入参数的外部Oracle过程进行通信:
过程merge_objects(varchar2中的p_table_name,p_id_array中的 SYS_REFCURSOR中的varchar2,p_cur_data)
我需要根据从客户端收到的数据传递SYS_REFCURSOR参数。有没有办法在Java中创建这样的参数?
答案 0 :(得分:3)
可能做这样的事情,但它有点繁琐。我想出了两种方法来做到这一点,但它们都依赖于能够在数据库中创建对象。我感谢您可能没有这样的许可。
底线是必须在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();
}
此方法使用类型而不是临时表,并使用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();
}
答案 1 :(得分:1)
直接从Java传递SYS_REFCURSOR的解决方案存在。无需在数据库中插入数据。
以下语句在Oracle中生成SYS_REFCURSOR(示例包含值和列名称):
OPEN cur_data FOR select&#39; 000000&#39;客栈,&#39; Ch&#39;来自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到必要的过程。