1. Види statements

Найпростіший інтерфейс Statement ми вже бачили. І хоча він цілком придатний для роботи, для складних запитів він погано підходить. У деяких джерелах висловлюється думка, що використовувати Statement взагалі не треба: натомість краще обрати складніші та більш функціонально насичені інтерфейси.

  • PreparedStatement
  • CallableStatement

Виникає цілком слушне питання: а навіщо ці інтерфейси потрібні? Давай розбиратись.

Для початку ми розглянемо інтерфейс PreparedStatement та інші можливості JDBC. До інтерфейсу CallableStatement звернемося пізніше — його використання, по-перше, не так часто зустрічається, а по-друге, після всього розглянутого про нього розмова може бути досить короткою.

До того ж, PreparedStatement чудово допомагає від популярного підходу до злому бази даних, який називається SQL Injection.

Але докладніше про це трохи пізніше.

2. PreparedStatement

Якщо спробувати перекласти назву PreparedStatement, то можна отримати щось на кшталт "підготовлений оператор". Найважливішим тут є слово "підготовлений". У чому полягає “підготовленість”?

Перш ніж ми розглянемо це питання, пропоную подивитися на досить важливий з погляду зручності момент, який виникає дуже часто. Отже, в певній програмі нам треба вставити дані про контакт до таблиці CONTACT. Для цього нам потрібно підготувати запит на кшталт такого:

INSERT INTO
  JC_CONTACT (FIRST_NAME, LAST_NAME, PHONE, EMAIL)
VALUES
  (
    'Harry',
    'Potter',
    '+79112345678',
    'harry@example.com'
  );

На перший погляд здається, що все не так уже й складно і страшно. Треба написати код, який збиратиме потрібний нам рядок з параметрів: ім'я, прізвище, адреса та телефон. Треба тільки не забути, що всі рядкові дані треба огорнути символом ординарної лапки.

Якщо ми це робимо в окремій функції, виходить щось таке:


public String buildInsert(String firstName,, String lastName, String phone, String email) {
    String sql = "INSERT INTO JC_CONTACT (FIRST_NAME, LAST_NAME, PHONE, EMAIL) “+
             ”VALUES ('"+firstName+"','"+lastName+"','"+phone+"','"+email+")";
    return sql;
}

Ми передаємо до функції параметрів ім'я, прізвище, телефон та адресу, і з них складаємо рядок SQL-запиту. Лапки трохи псують картину, але поки не страшно.

Ок, а що робити з числами? Їх не треба брати в лапки. Оце так, в одному випадку треба лапки, в іншому — не треба. Ситуація ускладнюється.

Тепер додамо ще одну проблему: а що якщо всередині рядка є ординарна лапка (і навіть не одна)? Потрібно попередньо шукати такі лапки та обробляти їх. МДА-АА. Якось незатишно починаємо почуватися.

Якщо тепер додати обробку дат, то завдання стає зовсім нудним — треба робити величезну кількість роботи. З датами взагалі неприємно — різні SQL-сервери приймають різні формати для дат.

Отже, що ми бачимо? Якщо нам треба використовувати параметри всередині запиту, то в ручному режимі побудова запиту стає дуже неприємною справою. Причому не просто неприємною — я навіть сказав би занудною. Треба враховувати величезну кількість випадків, і це дуже нецікаво. В основному саме для таких випадків і був запропонований PreparedStatement.

Цей запит дозволяє зробити дві речі:

  • Заздалегідь підготувати запит із зазначенням місць, де будуть підставлятися параметри
  • Встановити параметри певного типу та виконати після цього запит із уже встановленими параметрами

3. Приклад використання PreparedStatement

Конструкція для PreparedStatement для нашого варіанта встановлення параметрів виглядатиме ось так:


// Змінні для прикладу
String firstName = "Harry";
String lastName = "Potter";
String phone = "+12871112233";
String email = "harry@example.com";
 
// Запит із зазначенням місць для параметрів у вигляді знака "?"
String sql = "INSERT INTO JC_CONTACT (FIRST_NAME, LAST_NAME, PHONE, EMAIL) VALUES (?, ?, ?, ?)";
 
// Створення запиту. Змінна con — це об'єкт типу Connection
PreparedStatement stmt = con.prepareStatement(sql);
 
// Встановлення параметрів
stmt.setString(1, firstName);
stmt.setString(2, lastName);
stmt.setString(3, phone);
stmt.setString(4, email);
 
// Виконання запиту
stmt.executeUpdate();

Як бачиш, все досить нескладно.

По-перше, під час написання SQL-запиту на місця, куди треба буде підставляти параметри, записуються знаки питання — "?".

По-друге, запит створюється через виклик con.prepareStatement().

По-третє, встановлення параметрів відбувається через зазначення номера та значення. Зверни увагу, що номери параметрів починаються з 1, а не з 0, як ми звикли під час роботи з масивами та колекціями.

Інтерфейс PreparedStatement містить методи для зазначення рядків — setString(), для встановлення чисел — setInt(), setLong (), setDouble(), для встановлення дат — setDate(). І складніших типів — це можна побачити у документації.

По-четверте, виклик stmt.executeUpdate() виконується вже без зазначення рядка запиту.

Вкрай наполегливо рекомендую подружитися з PreparedStatement — це дуже ефективний інструмент.