Использование курсоров и получение результата в Oracle PL / SQL с 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

Как я могу запустить этот запрос из кода java с помощью jdbc и получить resultset? Я попытался запустить запрос без использования курсора, и он работает правильно. Я не мог понять, как это сделать в java-коде. Если я запускаю запрос непосредственно на клиент oracle, он работает без проблем. Таким образом, нет никаких проблем с запросом.

P. S. Я не хочу, чтобы хранить код в хранимую процедуру и вызвать из-за к некоторым ограничениям.

4 ответов


Это невозможно. Вы не можете вернуть результирующий набор из анонимного блока PL/SQL (и поэтому нет способа получить его из JDBC).

вам нужно будет запустить select непосредственно из JDBC.

единственное, очень некрасиво решение будет использовать dbms_output.put_line() и прочитайте это позже. Но это действительно уродливый Хак, и обработка результата запроса SELECT непосредственно в JDBC намного лучше.


изменить 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);
    }
}

Edit 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;
/

Это может быть обработано без избыточной памяти в JDBC, используя что-то вроде этого:

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 и может быть изменено. Но даже тогда у вас нет чрезмерного использования памяти.


@Rajat,

не могли бы вы попробовать ниже метод:

чтобы получить курсор, вы должны объявить его как курсор REF в спецификации пакета.

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

в обоих, spec и body, вам нужно объявить переменную курсора out REF в сигнатуре процедуры, как указано выше.

  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 call

как курсоры возвращаются процедурой, мы будем использовать java.sql.CallableStatement например:

CallableStatement cs = conn.prepareCall("{call PAC_CURSOR.PRO_RETURN_CARS(?,?)}");

на registerOutParameter получим oracle.jdbc.OracleTypes.CURSOR введите и верните java.sql.ResultSet экземпляра. Мы можем повторить ResultSet как общие Iterator.

каждый столбец строки, возвращаемый SELECT, будет представлен как карта, используя соответствующий геттер. Например, мы вызовем метод getString (), когда значение столбца является varchar, 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.


другие ответы здесь, кажется, очень сложно.

используя SYS_REFCURSOR

так как навсегда, вы можете получить SYS_REFCURSOR типы очень легко из JDBC:

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)) {
        ...
    }
}

конечно, вы также можете объявить свои собственные курсоры в пакетах, как предложил ПМР, но зачем вам, если вы запускаете анонимный блок от 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;

из-за ошибки (или "функции") в драйвере 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" никогда не может быть null как следствие бокса возвращаемого значения s.execute (), поэтому тест "i>0 && result==null" может быть "result==null"