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