CallableStatement

У JDBC есть еще один интерфейс для еще более сложных сценариев. Он унаследован от PreparedStatement и называется CallableStatement.

Он используется для вызова (Call) хранимых процедур в базе данных. Особенность такого вызова в том, что кроме результата ResultSet такой хранимой процедуре можно еще и передать параметры.

А что тут нового, спросишь ты? PreparedStatement тоже имеет результат ResultSet и в него тоже можно передавать параметры. Да, все верно, но особенность хранимых процедур в том, что через параметры они могут не только получать, но и возвращать данные.

Хранимая процедура вызывается с параметрами IN, OUT и INOUT. Она возвращает один или несколько объектов ResultSet. Для создания объекта CallableStatement предназначен метод Connection.prepareCall().

Вот представь, что у тебя есть хранимая процедура ADD, которая принимает параметры a, b и c. Эта процедура складывает a и b и помещает результат сложения в переменную с.

Давай напишем код, где попробуем ее вызвать:

// Подключение к серверу
Connection connection = DriverManager.getConnection("jdbc:as400://mySystem");

// Создание объекта CallableStatement. Он выполняет предварительную обработку
// вызова хранимой процедуры. Знаки вопроса
// указывают, где должны быть подставлены входные параметры, а где выходные
// Первые два параметра являются входными,
// а третий — выходным.
CallableStatement statement = connection.prepareCall("CALL MYLIBRARY.ADD (?, ?, ?)");

// Настройка входных параметров. Передаем в процедуру 123 и 234
statement.setInt (1, 123);
statement.setInt (2, 234);

// Регистрация типа выходного параметра
statement.registerOutParameter (3, Types.INTEGER);

// Запуск хранимой процедуры
statement.execute();

// Получение значения выходного параметра
int sum = statement.getInt(3);

// Закрытие CallableStatement и Connection
statement.close();
connection.close();

Работа почти как с PreparedStatement, только есть нюанс. Наша функция ADD возвращает в третьем параметре результат сложения. Только вот объект CallableStatement об этом ничего не знает. Поэтому мы говорим ему об это явно, вызвав метод registerOutParameter():

registerOutParameter(номерПараметра, типПараметра)

После этого можно вызывать процедуру через метод execute() и затем читать данные из третьего параметра с помощью методa getInt().

Batching запросов

В реальных проектах часто возникает ситуация, когда необходимо сделать очень много однотипных запросов (наиболее часто в этом случае встречается PreparedStatement), например, надо вставить несколько десятков или сотен записей.

Если выполнять каждый запрос отдельно, то это займет кучу времени и снизит производительность приложения. Чтобы не допустить этого, можно использовать batch-режим вставки. Он заключается в том, что ты накапливаешь некоторый буфер своими запросами, а потом выполняешь их сразу.

В качестве примера приведу кусочек кода:

PreparedStatement stmt = con.prepareStatement(
        	"INSERT INTO jc_contact (first_name, last_name, phone, email) VALUES (?, ?, ?, ?)");

for (int i = 0; i < 10; i++) {
	// Заполняем параметры запроса
	stmt.setString(1, "FirstName_" + i);
    stmt.setString(2, "LastNAme_" + i);
    stmt.setString(3, "phone_" + i);
    stmt.setString(4, "email_" + i);
	// Запрос не выполняется, а укладывается в буфер,
	//  который потом выполняется сразу для всех команд
	stmt.addBatch();
}
// Выполняем все запросы разом
int[] results = stmt.executeBatch();

Вместо того, чтобы выполнять запрос методом execute(), мы складываем его в пакет с помощью метода addBatch().

А затем, когда набралось несколько сотен запросов, можно их разом отправить на сервер, вызвав команду executeBatch().

Полезно. Метод executeBatch() возвращает массив целых чисел — int[]. Каждая ячейка этого массива содержит число, которое означает количество строк, измененных соответствующим запросом. Если запрос номер 3 в batch’е изменил 5 строк, то 3-я ячейка массива будет содержать число 5.