← Назад к вопросам
Как вызвать хранимые процедуры через JDBC
2.0 Middle🔥 121 комментариев
#Базы данных и SQL
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Вызов хранимых процедур через JDBC
Краткий ответ
Для вызова хранимой процедуры через JDBC используется интерфейс CallableStatement, который расширает PreparedStatement и поддерживает параметры вывода (OUT) и возвращаемые значения.
Синтаксис
// Получить CallableStatement
CallableStatement cstmt = connection.prepareCall("{call procedure_name(?, ?, ?)}");
// Установить параметры входа (IN)
cstmt.setInt(1, value1);
cstmt.setString(2, value2);
// Зарегистрировать параметры выхода (OUT)
cstmt.registerOutParameter(3, Types.INTEGER);
// Выполнить процедуру
cstmt.execute();
// Получить результаты
int result = cstmt.getInt(3);
Полный пример 1: Простая процедура без параметров выхода
Хранимая процедура (PostgreSQL)
CREATE PROCEDURE insert_user(
p_name VARCHAR,
p_email VARCHAR
)
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO users (name, email) VALUES (p_name, p_email);
COMMIT;
END;
$$;
Java код
@Component
public class UserDAO {
@Autowired
private DataSource dataSource;
public void callInsertUserProcedure(String name, String email) {
String sql = "{call insert_user(?, ?)}";
try (Connection conn = dataSource.getConnection();
CallableStatement cstmt = conn.prepareCall(sql)) {
// Установка параметров входа
cstmt.setString(1, name);
cstmt.setString(2, email);
// Выполнение процедуры
cstmt.execute();
} catch (SQLException e) {
throw new DatabaseException("Ошибка при вызове процедуры", e);
}
}
}
Полный пример 2: Процедура с параметром выхода (OUT)
Хранимая процедура (PostgreSQL)
CREATE PROCEDURE get_user_count(
OUT p_count INTEGER
)
LANGUAGE plpgsql
AS $$
BEGIN
SELECT COUNT(*) INTO p_count FROM users;
END;
$$;
Java код
public int callGetUserCountProcedure() {
String sql = "{call get_user_count(?)}";
try (Connection conn = dataSource.getConnection();
CallableStatement cstmt = conn.prepareCall(sql)) {
// Зарегистрировать параметр выхода
cstmt.registerOutParameter(1, Types.INTEGER);
// Выполнить процедуру
cstmt.execute();
// Получить результат
return cstmt.getInt(1);
} catch (SQLException e) {
throw new DatabaseException("Ошибка при вызове процедуры", e);
}
}
Полный пример 3: Смешанные IN и OUT параметры
Хранимая процедура (SQL Server)
CREATE PROCEDURE calculate_discount(
@p_order_amount DECIMAL(10,2),
@p_customer_level INT,
@p_discount_percent OUTPUT,
@p_final_amount OUTPUT DECIMAL(10,2)
)
AS
BEGIN
DECLARE @discount DECIMAL(5,2)
-- Определение скидки на основе уровня клиента
IF @p_customer_level = 1
SET @discount = 5.0
ELSE IF @p_customer_level = 2
SET @discount = 10.0
ELSE
SET @discount = 15.0
-- Установка выходных параметров
SET @p_discount_percent = @discount
SET @p_final_amount = @p_order_amount * (1 - @discount / 100)
END;
Java код
public class DiscountCalculator {
@Autowired
private DataSource dataSource;
public DiscountResult calculateDiscount(BigDecimal orderAmount, int customerLevel) {
String sql = "{call calculate_discount(?, ?, ?, ?)}";
try (Connection conn = dataSource.getConnection();
CallableStatement cstmt = conn.prepareCall(sql)) {
// Параметры входа (IN)
cstmt.setBigDecimal(1, orderAmount);
cstmt.setInt(2, customerLevel);
// Параметры выхода (OUT)
cstmt.registerOutParameter(3, Types.DECIMAL);
cstmt.registerOutParameter(4, Types.DECIMAL);
// Выполнение
cstmt.execute();
// Получение результатов
BigDecimal discountPercent = cstmt.getBigDecimal(3);
BigDecimal finalAmount = cstmt.getBigDecimal(4);
return new DiscountResult(discountPercent, finalAmount);
} catch (SQLException e) {
throw new DatabaseException("Ошибка при вызове процедуры расчета скидки", e);
}
}
}
public record DiscountResult(BigDecimal discountPercent, BigDecimal finalAmount) {}
Полный пример 4: Процедура с возвращаемым значением
Хранимая процедура (MySQL)
DELIMITER //
CREATE PROCEDURE update_user_status(
IN p_user_id INT,
IN p_status VARCHAR(20),
OUT p_success INT
)
READS SQL DATA
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
SET p_success = 0;
END;
UPDATE users SET status = p_status WHERE id = p_user_id;
SET p_success = 1;
END//
DELIMITER ;
Java код
public boolean updateUserStatus(int userId, String status) {
String sql = "{call update_user_status(?, ?, ?)}";
try (Connection conn = dataSource.getConnection();
CallableStatement cstmt = conn.prepareCall(sql)) {
// IN параметры
cstmt.setInt(1, userId);
cstmt.setString(2, status);
// OUT параметр для успеха/ошибки
cstmt.registerOutParameter(3, Types.INTEGER);
cstmt.execute();
int success = cstmt.getInt(3);
return success == 1;
} catch (SQLException e) {
throw new DatabaseException("Ошибка при обновлении статуса пользователя", e);
}
}
Полный пример 5: Процедура с ResultSet
Хранимая процедура (Oracle)
CREATE PROCEDURE get_users_by_status(
p_status IN VARCHAR2,
p_cursor OUT SYS_REFCURSOR
)
IS
BEGIN
OPEN p_cursor FOR
SELECT id, name, email, status
FROM users
WHERE status = p_status;
END;
/
Java код
public List<User> getUsersByStatus(String status) {
String sql = "{call get_users_by_status(?, ?)}";
List<User> users = new ArrayList<>();
try (Connection conn = dataSource.getConnection();
CallableStatement cstmt = conn.prepareCall(sql)) {
// IN параметр
cstmt.setString(1, status);
// OUT параметр - результирующий набор
cstmt.registerOutParameter(2, OracleTypes.CURSOR);
cstmt.execute();
// Получение ResultSet из OUT параметра
ResultSet rs = (ResultSet) cstmt.getObject(2);
while (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setEmail(rs.getString("email"));
user.setStatus(rs.getString("status"));
users.add(user);
}
return users;
} catch (SQLException e) {
throw new DatabaseException("Ошибка при получении пользователей", e);
}
}
Типы параметров
// Регистрация разных типов OUT параметров
cstmt.registerOutParameter(1, Types.INTEGER); // INT
cstmt.registerOutParameter(2, Types.VARCHAR); // VARCHAR
cstmt.registerOutParameter(3, Types.DECIMAL); // DECIMAL
cstmt.registerOutParameter(4, Types.DATE); // DATE
cstmt.registerOutParameter(5, Types.TIMESTAMP); // TIMESTAMP
cstmt.registerOutParameter(6, Types.BOOLEAN); // BOOLEAN
cstmt.registerOutParameter(7, Types.ARRAY); // ARRAY
cstmt.registerOutParameter(8, Types.OTHER); // CURSOR (Oracle)
Обработка исключений
public void callProcedureWithErrorHandling(String param) {
String sql = "{call my_procedure(?)}";
try (Connection conn = dataSource.getConnection();
CallableStatement cstmt = conn.prepareCall(sql)) {
cstmt.setString(1, param);
cstmt.execute();
} catch (SQLException e) {
if (e.getErrorCode() == 1234) {
// Специфичная обработка
throw new CustomBusinessException("Ошибка бизнес-логики", e);
} else {
// Общая обработка
throw new DatabaseException("Ошибка БД при вызове процедуры", e);
}
}
}
Использование Spring JdbcTemplate (более удобно)
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
public int getUserCount() {
return jdbcTemplate.execute(
(CallableStatement cs) -> {
cs.registerOutParameter(1, Types.INTEGER);
cs.execute();
return cs.getInt(1);
},
false,
"{call get_user_count(?)}"
);
}
public List<User> getUsersByStatus(String status) {
return jdbcTemplate.query(
"{call get_users_by_status(?)}",
ps -> ps.setString(1, status),
(ResultSet rs, int rowNum) -> {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
return user;
}
);
}
}
Spring Data JPA (самый удобный способ)
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
@Procedure(name = "get_user_count")
Integer getUserCount();
@Procedure(name = "get_users_by_status")
List<User> getUsersByStatus(String status);
}
// Использование
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public int getCount() {
return userRepository.getUserCount();
}
}
Синтаксис вызова для разных БД
// PostgreSQL
String sql = "{call procedure_name(?, ?)}";
// MySQL
String sql = "{call procedure_name(?, ?)}";
// Oracle
String sql = "{? = call procedure_name(?, ?)}"; // Если есть RETURN
// SQL Server
String sql = "{call procedure_name(?, ?)}";
// DB2
String sql = "{call schema.procedure_name(?, ?)}";
Best Practices
- Используй try-with-resources для автоматического закрытия ресурсов
- Отлавливай SQLException и преобразуй в бизнес-исключения
- Логируй SQL-запросы для отладки
- Используй Spring JdbcTemplate или Spring Data JPA вместо сырого JDBC
- Параметризируй все значения для защиты от SQL-инъекций
- Документируй назначение процедур и их параметры
- Тестируй процедуры с помощью юнит-тестов
Вывод
CallableStatement позволяет вызывать хранимые процедуры из Java с поддержкой входных, выходных параметров и результирующих наборов. Для удобства рекомендуется использовать Spring JdbcTemplate или Spring Data JPA вместо прямого JDBC.