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 — полный протокол вызова без реального подключения к базе данных.
Что следует запомнить из этого примера:
- Скобки
{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».