← Назад к вопросам

Что такое CallableStatement?

1.0 Junior🔥 121 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

CallableStatement в Java JDBC

CallableStatement — это интерфейс JDBC для вызова хранимых процедур в базе данных. Он расширяет функциональность PreparedStatement, добавляя возможность работы с OUT и INOUT параметрами.

Основная концепция

В отличие от обычного SQL запроса, хранимая процедура — это код, скомпилированный на стороне СУБД. CallableStatement позволяет:

  • Вызывать хранимые процедуры из Java приложения
  • Передавать параметры (IN, OUT, INOUT)
  • Получать результаты из OUT параметров и ResultSet
  • Получать коды возврата (Return value)

Синтаксис вызова

Для вызова хранимой процедуры используется синтаксис CALL:

// Синтаксис: {CALL procedure_name(?,?,?)}
// Для функций: {? = CALL function_name(?,?)}

Connection conn = DriverManager.getConnection(url, user, password);

// Вызов хранимой процедуры
String sql = "{CALL GetUserById(?)}"; // (?) для входного параметра
CallableStatement cstmt = conn.prepareCall(sql);

// Вызов функции, возвращающей значение
String sqlFunc = "{? = CALL CalculateBonus(?)}";
CallableStatement cstmtFunc = conn.prepareCall(sqlFunc);

Типы параметров

IN параметры — данные передаются в процедуру:

String sql = "{CALL InsertUser(?, ?)}"; // Две IN переменные
CallableStatement cstmt = conn.prepareCall(sql);

cstmt.setString(1, "John Doe");        // Первый параметр
cstmt.setInt(2, 30);                   // Второй параметр

cstmt.execute();

OUT параметры — данные возвращаются из процедуры:

String sql = "{CALL GetUserCount(?)}";
CallableStatement cstmt = conn.prepareCall(sql);

// Регистрируем OUT параметр
cstmt.registerOutParameter(1, Types.INTEGER); // Параметр 1 — INT результат

cstmt.execute();

// Получаем результат
int count = cstmt.getInt(1);
System.out.println("Всего пользователей: " + count);

INOUT параметры — передача и получение данных:

String sql = "{CALL ProcessBonus(?)}";
CallableStatement cstmt = conn.prepareCall(sql);

// Передаём начальное значение
cstmt.setDouble(1, 1000.0);

// Регистрируем как OUT (будет обновлено)
cstmt.registerOutParameter(1, Types.DOUBLE);

cstmt.execute();

// Получаем изменённое значение
double bonusAmount = cstmt.getDouble(1);
System.out.println("Бонус: " + bonusAmount);

Практический пример

Предположим, в БД есть хранимая процедура:

CREATE PROCEDURE GetEmployeeById(
    IN emp_id INT,
    OUT emp_name VARCHAR(100),
    OUT emp_salary DECIMAL(10,2)
)
BEGIN
    SELECT name, salary 
    INTO emp_name, emp_salary
    FROM employees 
    WHERE id = emp_id;
END;

Вызов из Java:

public void getEmployeeInfo(int employeeId) throws SQLException {
    String sql = "{CALL GetEmployeeById(?, ?, ?)}"; // 1 IN, 2 OUT
    
    try (Connection conn = getConnection();
         CallableStatement cstmt = conn.prepareCall(sql)) {
        
        // Устанавливаем IN параметр
        cstmt.setInt(1, employeeId);
        
        // Регистрируем OUT параметры
        cstmt.registerOutParameter(2, Types.VARCHAR);   // name
        cstmt.registerOutParameter(3, Types.DECIMAL);   // salary
        
        // Выполняем процедуру
        cstmt.execute();
        
        // Получаем результаты
        String name = cstmt.getString(2);
        BigDecimal salary = cstmt.getBigDecimal(3);
        
        System.out.println("Имя: " + name + ", Зарплата: " + salary);
    }
}

Работа с ResultSet

Хранимая процедура может возвращать несколько результирующих наборов:

String sql = "{CALL GetAllUsers()}"; // Процедура возвращает ResultSet
CallableStatement cstmt = conn.prepareCall(sql);

boolean isResultSet = cstmt.execute();

while (isResultSet) {
    try (ResultSet rs = cstmt.getResultSet()) {
        while (rs.next()) {
            int id = rs.getInt("id");
            String name = rs.getString("name");
            System.out.println(id + ": " + name);
        }
    }
    isResultSet = cstmt.getMoreResults();
}

Обработка исключений

public void callProcedureSafely() {
    String sql = "{CALL ProcessOrder(?)}"; 
    
    try (Connection conn = getConnection();
         CallableStatement cstmt = conn.prepareCall(sql)) {
        
        cstmt.setInt(1, 123);
        cstmt.execute();
        
    } catch (SQLException e) {
        System.err.println("Ошибка вызова процедуры: " + e.getMessage());
        throw new RuntimeException("Ошибка БД", e);
    }
}

Преимущества и недостатки

Преимущества:

  • Производительность — код выполняется на СУБД
  • Безопасность — бизнес-логика защищена на уровне БД
  • Переиспользуемость — процедура доступна для разных приложений

Недостатки:

  • Зависимость от СУБД — синтаксис отличается (MySQL, PostgreSQL, Oracle)
  • Сложность тестирования — нужна БД для тестов
  • Усложнение архитектуры — логика распределена между слоями

Современные альтернативы

В современных приложениях часто используют ORM (Hibernate, JPA) или SQL-фреймворки, которые упрощают работу:

// С помощью JPA
@NamedStoredProcedureQuery(
    name = "GetUserCount",
    procedureName = "GetUserCount",
    outputParameters = @StoredProcedureParameter(name = "count", type = Integer.class)
)
public class User {
    // ...
}

CallableStatement остаётся актуальным инструментом для сложной работы с БД и внедрения функционала на уровне СУБД.

Что такое CallableStatement? | PrepBro