← Назад к вопросам
В чем разница между 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 параметры
Детальное сравнение
| Аспект | PreparedStatement | CallableStatement |
|---|---|---|
| Назначение | SQL запросы | Хранимые процедуры/функции |
| SQL синтаксис | SELECT, INSERT, UPDATE, DELETE | {call procedure(?, ?)} |
| Входные параметры | Да (IN) | Да (IN) |
| Выходные параметры | Нет | Да (OUT) |
| INOUT параметры | Нет | Да |
| Возвращаемое значение | ResultSet | ResultSet + 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