使用游标并使用Java / JDBC在Oracle PL / SQL中获取结果

时间:2013-04-16 21:11:41

标签: java sql oracle jdbc plsql

我有一个像这样构建的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客户端上运行查询,它没有任何问题。所以查询没有问题。

P.S。我不希望将代码存储为存储过程,并且由于某些约束而调用它。

4 个答案:

答案 0 :(得分:6)

@Rajat,

您可以尝试以下方法:

要检索光标,您应该在Package spec中将其声明为REF CURSOR。

  --Creating the REF CURSOR type
  type g_cursor is ref cursor;

在规范和正文中,你需要在程序签名中声明一个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个实例。我们可以像常见ResultSet一样迭代Iterator

SELECT返回的每一行将用相应的getter表示一个地图的方式。例如,当column的值是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子句中返回的任何值。

答案 1 :(得分:5)

这是不可能的。您无法从匿名PL / SQL块返回结果集(因此无法从JDBC获取它)。

您需要直接从JDBC运行select。

唯一的,非常丑陋的解决方法是使用dbms_output.put_line()和之后的阅读。但这是一个非常难看的黑客,直接在JDBC中处理SELECT查询的结果要好得多。


编辑1

以下是使用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);
    }
}

编辑2(这对评论来说太长了)

嵌套循环以检索数据几乎总是一个坏主意。如果你发现自己做了这样的事情:

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,可以更改。但即便如此,你也没有过多的内存使用。

答案 2 :(得分:3)

这里的其他答案似乎非常复杂。

使用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's answer的建议在包中声明自己的游标,但是如果您从JDBC运行匿名块,为什么会这样?

使用Oracle 12c隐式结果集

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;

Due to a bug (or "feature") in the Oracle JDBC driver,从JDBC but it can certainly be done as I've shown in this article here正确获取游标有点棘手。这是你可以从任何匿名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;
    }
}

答案 3 :(得分:0)

因为java.sql.PreparedStatement.execute()的签名是" boolean execute()"不是"布尔执行()","结果"作为装入s.execute()的返回值的结果,变量永远不会为null,因此测试" i> 0&&结果== NULL"可能是"结果== null"