W3docs

Java JDBC CallableStatement

Вызывайте хранимые процедуры из Java с помощью CallableStatement.

CallableStatement вызывает хранимую процедуру или функцию, находящуюся внутри базы данных. Он расширяет PreparedStatement, поэтому поддерживает те же заполнители ? — плюс возможность регистрировать параметры OUT, в которые процедура записывает результат. Используйте его, когда бизнес-логика реализована в базе данных, а не в Java.

В этой главе рассматриваются escape-синтаксис JDBC для вызовов, три направления параметров (IN, OUT, INOUT), причина необходимости регистрировать тип для OUT-параметров и строгий порядок вызова методов.

Escape-синтаксис JDBC

Вызов записывается с помощью переносимого синтаксиса с фигурными скобками, который каждый драйвер переводит в диалект своего поставщика:

// procedure with two IN parameters
CallableStatement cs = conn.prepareCall("{call add_customer(?, ?)}");

// function returning a value, with one IN parameter
CallableStatement fn = conn.prepareCall("{? = call total_orders(?)}");

Фигурные скобки означают, что вам не нужно знать, как поставщик записывает команду: CALL, EXEC или BEGIN ... END.

Параметры IN, OUT и INOUT

Параметр процедуры имеет направление:

  • IN — вы задаёте его: cs.setInt(1, customerId), точно как в PreparedStatement.
  • OUT — процедура заполняет его; сначала необходимо зарегистрировать его тип, затем прочитать значение после выполнения.
  • INOUT — оба варианта: задайте значение, зарегистрируйте тип, затем прочитайте.
CallableStatement cs = conn.prepareCall("{call get_balance(?, ?)}");
cs.setInt(1, accountId);                              // IN
cs.registerOutParameter(2, java.sql.Types.DECIMAL);   // OUT — declare its type
cs.execute();
BigDecimal balance = cs.getBigDecimal(2);             // read it back

Когда процедура возвращает не одно значение, а полную таблицу, она возвращает ResultSet: вызовите cs.executeQuery() (или используйте boolean из cs.execute() вместе с cs.getResultSet()) и обойдите его так же, как ResultSet из обычного Statement.

Зачем registerOutParameter требует тип

JDBC должен знать, как интерпретировать байты, которые отправляет база данных, до выполнения вызова, поэтому тип SQL указывается с помощью константы java.sql.Types. Если тип указан неверно, последующий getXxx завершится ошибкой или выполнит некорректное преобразование. Порядок строго фиксирован: регистрация OUT → задание IN → execute → getXxx.

Практический пример: формирование вызова и регистрация OUT

Эта программа строит обе строки вызова с escape-синтаксисом и демонстрирует регистрацию OUT-параметров с кодами java.sql.Types — полный протокол вызова без реального подключения к базе данных.

java— editable, runs on the server

Что следует запомнить из этого примера:

  • Скобки {call proc(?, ?)} — переносимый escape-синтаксис. Вы пишете одну и ту же строку независимо от поставщика, а драйвер перезаписывает её — именно это делает вызовы хранимых процедур независимыми от базы данных.
  • Форма {? = call fn(?)} используется для функций, которые возвращают значение: ведущий ? — это слот возврата, зарегистрированный как OUT-параметр с индексом 1, а реальные аргументы идут следом.
  • registerOutParameter принимает константу java.sql.Types (INTEGER равен 4, DECIMAL равен 3). Это тот же словарь типов из предыдущих глав — JDBC переиспользует его везде, где нужно назвать тип SQL без Java-значения под рукой.
  • Тип, который вы регистрируете, должен соответствовать тому, что процедура фактически возвращает; иначе последующие getInt/getBigDecimal неверно интерпретируют байты. Регистрация — это обещание о форме результата.
  • Порядок протокола строго фиксирован и его стоит запомнить: сначала регистрируйте OUT-параметры, затем задавайте IN-параметры, вызывайте execute(), после чего читайте OUT-значения с помощью getXxx(index). Пример явно указывает эту последовательность, потому что нарушение порядка — обычная причина ошибок «parameter not registered».

Практика

Практика
Что необходимо сделать для OUT-параметра перед выполнением CallableStatement, содержащего такой параметр?
Что необходимо сделать для OUT-параметра перед выполнением CallableStatement, содержащего такой параметр?
Was this page helpful?