[英]Using cursors and getting result in Oracle PL/SQL with Java/JDBC
我有一个像这样构造的PL / SQL查询:
DECLARE
a NUMBER;
B NUMBER;
CURSOR cursor
IS
( SOME SELECT QUERY);
BEGIN
OPEN cursor;
LOOP
SOME STUFF;
END LOOP;
CLOSE cursor;
END
如何使用jdbc从java代码运行此查询并获取结果集? 我已经尝试运行查询而不使用游标,并且它正确运行。 我无法想出在java代码中执行此操作的方法。 如果我直接在oracle客户端上运行查询,它没有任何问题。 所以查询没有问题。
PS我不想将代码存储为存储过程并由于某些约束而调用它。
@Rajat,
你可以尝试下面的方法:
要检索游标,您应该在Package规范中将其声明为REF CURSOR。
--Creating the REF CURSOR type
type g_cursor is ref cursor;
在spec和body中,您需要在过程签名中声明一个REF CURSOR变量,如上所述。
procedure PRO_RETURN_CARS(
i_id in tbl_car.car_id%type,
o_cursor in out g_cursor);
必须在过程的主体中打开光标才能返回,这样:
open o_cursor for
select car_id, company, model, color, hp, price
from tbl_car
where car_id = i_id;
完整的套餐:
create or replace package PAC_CURSOR is
--Creating REF CURSOR type
type g_cursor is ref cursor;
--Procedure that return the cursor
procedure PRO_RETURN_CARS(
i_id in tbl_car.car_id%type,
o_cursor in out g_cursor); -- Our cursor
end PAC_CURSOR;
/
create or replace package body PAC_CURSOR is
procedure PRO_RETURN_CARS(
i_id in tbl_car.car_id%type,
o_cursor in out g_cursor) is
begin
--Opening the cursor to return matched rows
open o_cursor for
select car_id, company, model, color, hp, price
from tbl_car
where car_id = i_id;
end PRO_RETURN_CARS;
end PAC_CURSOR;
我们已经准备好了Oracle,现在我们需要创建Java调用
如何通过过程返回游标,我们将使用java.sql.CallableStatement
实例:
CallableStatement cs = conn.prepareCall("{call PAC_CURSOR.PRO_RETURN_CARS(?,?)}");
registerOutParameter
将获取oracle.jdbc.OracleTypes.CURSOR
类型并返回java.sql.ResultSet
实例。 我们可以像普通Iterator
一样迭代ResultSet
。
SELECT返回的每个行列将使用相应的getter表示地图的方式。 例如,当列的值是varchar时,我们将调用getString()方法,当日期是等时,我们将调用getDate()等。
完整的代码将是这样的:
//Calling Oracle procedure
CallableStatement cs = conn.prepareCall("{call PAC_CURSOR.PRO_RETURN_CARS(?,?)}");
//Defining type of return
cs.registerOutParameter("o_cursor", OracleTypes.CURSOR);
cs.setLong("i_id", id);
cs.execute();//Running the call
//Retrieving the cursor as ResultSet
ResultSet rs = (ResultSet)cs.getObject("o_cursor");
//Iterating the returned rows
while(rs.next()){
//Getting column values
System.out.println("ID: " + rs.getLong("car_id"));
System.out.println("Manufacturer: " + rs.getString("company"));
System.out.println("Model: " + rs.getString("model"));
System.out.println("Color: " + rs.getString("color"));
System.out.println("HP: " + rs.getString("hp"));
System.out.println("Price: " + rs.getFloat("price"));
}
最后,您将获得SELECT子句中返回的任何值。
这是不可能的。 您无法从匿名PL / SQL块返回结果集(因此无法从JDBC获取它)。
您需要直接从JDBC运行select。
唯一的,非常丑陋的解决方法是使用dbms_output.put_line()
和之后的读取。 但这是一个非常丑陋的黑客,直接在JDBC中处理SELECT查询的结果要好得多。
这是使用dbms_output的一个小例子:
Connection con = ....;
// turn on support for dbms_output
CallableStatement cstmt = con.prepareCall("{call dbms_output.enable(32000) }");
cstmt.execute();
// run your PL/SQL block
Statement stmt = con.createStatement();
String sql =
"declare \n" +
" a number; \n" +
" cursor c1 is select id from foo; \n" +
"begin \n" +
" open c1; \n" +
" loop \n" +
" fetch c1 into a; \n" +
" exit when c1%notfound; \n" +
" dbms_output.put_line('ID: '||to_char(a)); \n" +
" end loop; \n" +
"end;";
stmt.execute(sql);
// retrieve the messages written with dbms_output
cstmt = con.prepareCall("{call dbms_output.get_line(?,?)}");
cstmt.registerOutParameter(1,java.sql.Types.VARCHAR);
cstmt.registerOutParameter(2,java.sql.Types.NUMERIC);
int status = 0;
while (status == 0)
{
cstmt.execute();
String line = cstmt.getString(1);
status = cstmt.getInt(2);
if (line != null && status == 0)
{
System.out.println(line);
}
}
嵌套循环来检索数据几乎总是一个坏主意。 如果你发现自己做了这样的事情:
begin
for data_1 in (select id from foo_1) loop
dbms_output.put_line(to_char(data_1.id));
for data_2 in (select f2.col1, f2.col2 from foo_2 f2 where f2.id = data_1.id) loop
... do something else
end loop;
end loop;
end;
/
这样做效率会高得多:
begin
for data_1 in (select f2.col1, f2.col2 from foo_2 f2
where f2.id in (select f1.id from foo_1 f1)) loop
... do something
end loop;
end;
/
这可以在没有过多内存的情况下使用以下内容处理:
String sql = "select f2.col1, f2.col2 from foo_2 f2 where f2.id in (select f1.id from foo_1 f1)";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next())
{
String col1_value = rs.getString(1);
int col2_value = rs.getInt(2);
... do something
}
即使您处理了数十亿行,上面的代码也只会在内存中保留一行。 确切地说:JDBC驱动程序实际上会预取多行。 默认值为10,可以更改。 但即便如此,你也没有过多的内存使用量。
这里的其他答案似乎超级复杂。
SYS_REFCURSOR
从永远,您可以非常轻松地从JDBC检索SYS_REFCURSOR
类型:
DECLARE
cur SYS_REFCURSOR;
BEGIN
OPEN cur FOR SELECT ...;
? := cur;
END;
现在从Java运行上面这样:
try (CallableStatement c = con.prepareCall(sql)) {
c.registerOutParameter(1, OracleTypes.CURSOR); // -10
c.execute();
try (ResultSet rs = (ResultSet) c.getObject(1)) {
...
}
}
当然,您也可以按照pmr的回答建议在包中声明自己的游标,但是如果您从JDBC运行匿名块,为什么还要这样?
Oracle 12c为这些案例添加了一个方便的新功能,类似于SQL Server / Sybase和MySQL思考返回结果的过程/批处理的方式。 您现在可以在任何游标上使用DBMS_SQL.RETURN_RESULT
过程,该游标将“通过魔法”返回:
DECLARE
cur SYS_REFCURSOR;
BEGIN
OPEN cur FOR SELECT ...;
DBMS_SQL.RETURN_RESULT(cur);
END;
由于Oracle JDBC驱动程序中的错误(或“功能”),从JDBC正确获取游标更加棘手, 但它肯定可以像我在本文中所示的那样完成 。 这是您可以从任何匿名PL / SQL块和/或过程,触发器等发现任意数量的隐式游标......:
try (PreparedStatement s = cn.prepareStatement(sql)) {
// Use good old three-valued boolean logic
Boolean result = s.execute();
fetchLoop:
for (int i = 0;; i++) {
// Check for more results if not already done in this iteration
if (i > 0 && result == null)
result = s.getMoreResults();
System.out.println(result);
if (result) {
result = null;
try (ResultSet rs = s.getResultSet()) {
System.out.println("Fetching result " + i);
}
catch (SQLException e) {
// Ignore ORA-17283: No resultset available
if (e.getErrorCode() == 17283)
continue fetchLoop;
else
throw e;
}
}
else if (s.getUpdateCount() == -1)
// Ignore -1 value if there is one more result!
if (result = s.getMoreResults())
continue fetchLoop;
else
break fetchLoop;
}
}
因为java.sql.PreparedStatement.execute()的签名是“boolean execute()”而不是“Boolean execute()”,所以“result”变量永远不能为空,因为装箱s.execute的返回值( ),所以测试“i> 0 && result == null”可能是“result == null”
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.