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

В чем разница между PreparedStatement и CallableStatement?

2.2 Middle🔥 61 комментариев
#Базы данных и SQL

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

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

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

# PreparedStatement vs CallableStatement в JDBC

Иерархия классов

Statement (интерфейс)
  ├─ PreparedStatement (интерфейс) — для SQL запросов с параметрами
  └─ CallableStatement (интерфейс) — extends PreparedStatement
        └─ для вызова хранимых процедур

Основные различия

PreparedStatement

Назначение: безопасное выполнение SQL запросов с параметрами

// Создание
String sql = "SELECT * FROM users WHERE id = ? AND email = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);

// Установка параметров
pstmt.setInt(1, userId);
pstmt.setString(2, userEmail);

// Выполнение
ResultSet rs = pstmt.executeQuery();

while (rs.next()) {
    String name = rs.getString("name");
    System.out.println(name);
}

pstmt.close();

Характеристики:

  • Используется для SELECT, INSERT, UPDATE, DELETE
  • Параметры указываются через ? (placeholders)
  • Поддерживает входные параметры (IN-параметры)
  • Нет выходных параметров
  • Защита от SQL injection
  • Pre-compilation на стороне БД
  • Кеширование prepared statements

CallableStatement

Назначение: вызов хранимых процедур и функций

// Хранимая процедура в БД:
// CREATE PROCEDURE GetUserEmail(IN userId INT, OUT userEmail VARCHAR(100))
// BEGIN
//     SELECT email INTO userEmail FROM users WHERE id = userId;
// END;

// Вызов из Java
String sql = "{call GetUserEmail(?, ?)}";
CallableStatement cstmt = connection.prepareCall(sql);

// Регистрация выходных параметров
cstmt.registerOutParameter(2, java.sql.Types.VARCHAR);

// Установка входных параметров
cstmt.setInt(1, userId);

// Выполнение
cstmt.execute();

// Получение выходных параметров
String email = cstmt.getString(2);
System.out.println("Email: " + email);

cstmt.close();

Характеристики:

  • Вызов хранимых процедур и функций
  • Поддерживает IN, OUT и INOUT параметры
  • Синтаксис: {call procedure_name(?, ?, ?)}
  • Для функций: {?= call function_name(?, ?)}
  • Требует регистрации выходных параметров
  • Может возвращать ResultSet и OUT параметры

Детальное сравнение

АспектPreparedStatementCallableStatement
НазначениеSQL запросыХранимые процедуры/функции
SQL синтаксисSELECT, INSERT, UPDATE, DELETE{call procedure(?, ?)}
Входные параметрыДа (IN)Да (IN)
Выходные параметрыНетДа (OUT)
INOUT параметрыНетДа
Возвращаемое значениеResultSetResultSet + OUT параметры
Регистрация параметровНет (просто set)Да (registerOutParameter)
Использование90% случаевИнтеграция с legacy системами

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

PreparedStatement для разных операций

SELECT с параметрами

String sql = "SELECT id, name, email FROM users WHERE age > ? AND status = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 18);
pstmt.setString(2, "ACTIVE");
ResultSet rs = pstmt.executeQuery();

INSERT с параметрами

String sql = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "John");
pstmt.setString(2, "john@example.com");
pstmt.setInt(3, 25);
pstmt.executeUpdate();

Batch операции

String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);

for (User user : users) {
    pstmt.setString(1, user.getName());
    pstmt.setString(2, user.getEmail());
    pstmt.addBatch();  // Добавить в batch
}

int[] results = pstmt.executeBatch();  // Выполнить все одновременно

CallableStatement с OUT параметрами

Процедура с OUT параметром

// SQL:
// CREATE PROCEDURE GetUserCount(OUT totalUsers INT)
// BEGIN
//     SELECT COUNT(*) INTO totalUsers FROM users;
// END;

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

// Регистрируем OUT параметр на позиции 1
cstmt.registerOutParameter(1, java.sql.Types.INTEGER);

// Выполняем
cstmt.execute();

// Получаем результат
int totalUsers = cstmt.getInt(1);
System.out.println("Total users: " + totalUsers);

Процедура с IN и OUT параметрами

// SQL:
// CREATE PROCEDURE UpdateUserAndGetNewBalance(
//     IN userId INT,
//     IN amount DECIMAL,
//     OUT newBalance DECIMAL
// )
// BEGIN
//     UPDATE accounts SET balance = balance + amount WHERE user_id = userId;
//     SELECT balance INTO newBalance FROM accounts WHERE user_id = userId;
// END;

String sql = "{call UpdateUserAndGetNewBalance(?, ?, ?)}";
CallableStatement cstmt = conn.prepareCall(sql);

// IN параметры
cstmt.setInt(1, userId);
cstmt.setBigDecimal(2, new BigDecimal("100.50"));

// OUT параметр
cstmt.registerOutParameter(3, java.sql.Types.DECIMAL);

// Выполнение
cstmt.execute();

// Получение результата
BigDecimal newBalance = cstmt.getBigDecimal(3);
System.out.println("New balance: " + newBalance);

Функция с возвращаемым значением

// SQL:
// CREATE FUNCTION CalculateDiscount(
//     IN totalAmount DECIMAL
// ) RETURNS DECIMAL
// BEGIN
//     RETURN totalAmount * 0.1;
// END;

String sql = "{? = call CalculateDiscount(?)}";
CallableStatement cstmt = conn.prepareCall(sql);

// Первый ? — возвращаемое значение
cstmt.registerOutParameter(1, java.sql.Types.DECIMAL);

// Второй ? — входной параметр
cstmt.setBigDecimal(2, new BigDecimal("1000.00"));

cstmt.execute();

BigDecimal discount = cstmt.getBigDecimal(1);
System.out.println("Discount: " + discount);

SQL Injection защита

PreparedStatement защищает от SQL injection

// ❌ Уязвимо (Statement с конкатенацией)
String sql = "SELECT * FROM users WHERE id = " + userId;
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
// Если userId = "1 OR 1=1", вернёт всех пользователей

// ✅ Безопасно (PreparedStatement с параметрами)
String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, userId);  // Параметр экранируется
ResultSet rs = pstmt.executeQuery();

Производительность

PreparedStatement:

  • Pre-compilation на стороне БД
  • Plan caching в БД
  • Переиспользование плана
  • Меньше сетевых запросов при batch

CallableStatement:

  • Дополнительная обработка процедур
  • OUT параметры требуют extra fetch
  • Может быть медленнее, чем SQL, но быстрее, чем множество SQL запросов

Когда использовать

PreparedStatement

  • Обычные CRUD операции
  • Когда нужна безопасность от SQL injection
  • Когда логика в application-слое
  • Больше 90% приложений

CallableStatement

  • Legacy системы с комплексной бизнес-логикой в БД
  • Интеграция со старым кодом
  • Когда вся логика в хранимых процедурах
  • Редко в современных приложениях (ORM + app logic)

Современный подход

// Вместо PreparedStatement, используй ORM или Query Builder

// JPA/Hibernate
User user = entityManager.createQuery(
    "SELECT u FROM User u WHERE u.id = :id",
    User.class
)
.setParameter("id", userId)
.getSingleResult();

// Spring Data
User user = userRepository.findById(userId).orElse(null);

// jOOQ
Result<UserRecord> users = dsl.selectFrom(USERS)
    .where(USERS.ID.eq(userId))
    .fetch();

Заключение

  • PreparedStatement — основной инструмент для JDBC запросов, безопасен и эффективен
  • CallableStatement — специализирован для хранимых процедур, редко используется в современных приложениях
  • В production используй ORM (Hibernate, JPA) или Query Builder (jOOQ) вместо raw JDBC
В чем разница между PreparedStatement и CallableStatement? | PrepBro