0Pricing
Java Academy · Урок

PreparedStatement

Безопасные параметризованные запросы

«PreparedStatement» — бесплатный урок Java Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.

Зачем нужен PreparedStatement?

Составлять SQL, присоединяя к нему пользовательский ввод, опасно и медленно.

PreparedStatement решает обе проблемы:

  • Безопасность — значения передаются отдельно от SQL, что предотвращает внедрение SQL.
  • Производительность — база данных может один раз разобрать и спланировать запрос, а затем повторно использовать результат.

Проблема внедрения SQL

Представьте, что запрос формируется объединением строк. Если пользователь введёт '; DROP TABLE users; -- в качестве имени, объединённый SQL выполнит это как команду.

Это внедрение SQL — одна из самых старых и опасных уязвимостей веб-приложений. PreparedStatement делает такую атаку невозможной по своей конструкции.

public class Main {
    public static void main(String[] args) {
        String userInput = "'; DROP TABLE users; --";
        // Dangerous: never do this
        String unsafe = "SELECT * FROM users WHERE name = '" + userInput + "'";
        System.out.println("Injected SQL becomes:");
        System.out.println(unsafe);
    }
}

Заполнители

В PreparedStatement укажите ? для каждого значения, которое зададите позже.

Затем привяжите значения с помощью типизированных методов установки:

  • ps.setString(1, name)
  • ps.setInt(2, age)

Индексы параметров начинаются с 1 и соответствуют порядку знаков ?.

Безопасный запрос

Создайте оператор с помощью prepareStatement, задайте параметры, затем вызовите executeQuery.

Значение никогда не попадает в текст SQL, поэтому внедрение невозможно, что бы пользователь ни ввёл.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "SELECT id FROM users WHERE name = ?";
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, "Ada");
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) System.out.println(rs.getInt("id"));
            }
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

Привязка нескольких параметров

Для каждого ? нужен отдельный вызов метода установки. Порядок важен: параметр 1 соответствует первому ?, параметр 2 — второму и так далее.

Используйте метод установки, соответствующий типу столбца: setInt, setString, setDouble, setBoolean и так далее.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "INSERT INTO users(name, age, active) VALUES (?, ?, ?)";
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, "Grace");
            ps.setInt(2, 42);
            ps.setBoolean(3, true);
            int rows = ps.executeUpdate();
            System.out.println("Inserted " + rows + " row");
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

Обработка NULL

Чтобы привязать SQL-значение NULL, используйте setNull(index, type) с константой java.sql.Types.

Не передавайте Java-значение null в setString в надежде, что всё сработает, — явно укажите тип:

  • ps.setNull(2, java.sql.Types.INTEGER)

Получение сгенерированных ключей

После INSERT часто требуется автоматически созданный идентификатор.

При подготовке передайте Statement.RETURN_GENERATED_KEYS, а затем прочитайте ключи с помощью ps.getGeneratedKeys().

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "INSERT INTO users(name) VALUES (?)";
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
            ps.setString(1, "Linus");
            ps.executeUpdate();
            try (ResultSet keys = ps.getGeneratedKeys()) {
                if (keys.next()) System.out.println("New id: " + keys.getLong(1));
            }
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

Пакетная вставка

Вставка тысяч строк по одному оператору за раз выполняется медленно. Используйте пакетную обработку:

  • Задайте параметры, затем вызовите ps.addBatch().
  • Повторите это для каждой строки.
  • Один раз вызовите ps.executeBatch().

Строки отправляются вместе, что значительно сокращает количество сетевых обменов.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "INSERT INTO tags(label) VALUES (?)";
        String[] labels = {"java", "jdbc", "sql"};
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql)) {
            for (String label : labels) {
                ps.setString(1, label);
                ps.addBatch();
            }
            int[] counts = ps.executeBatch();
            System.out.println("Batched " + counts.length + " inserts");
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

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

Один PreparedStatement можно выполнять много раз с разными параметрами.

Задайте заполнители заново и снова вызовите execute. База данных повторно использует кэшированный план выполнения, поэтому подготовленные операторы быстрее при повторяющихся запросах.

Никогда не объединяйте строки

Главное правило: любое изменяющееся значение должно быть параметром ?.

Объединять можно только фиксированные идентификаторы, контролируемые кодом (например, имя столбца из списка разрешённых значений), и даже в этом случае их нужно проверять по списку разрешённых значений. Данные пользователя всегда передавайте как параметры, никогда не присоединяйте к строке.

Закрытие и область повторного использования

PreparedStatement связан с объектом Connection, поэтому после завершения работы его следует закрыть.

Внутри блока try с ресурсами объявляйте оператор так, чтобы он закрылся раньше соединения. Если вы многократно используете оператор в цикле, подготовьте его один раз за пределами цикла, а внутри только задавайте параметры заново — повторная подготовка на каждой итерации приводит к потере кэшированного плана.

Быстрая проверка

Почему PreparedStatement предотвращает SQL-инъекции?

Итоги

Вы научились составлять безопасные и быстрые запросы:

  • Используйте заполнители ? и типизированные методы установки значений (индексация с 1).
  • Привязывайте значения с помощью setString, setInt, setNull и других методов.
  • Получайте автоматически сгенерированные ключи с помощью RETURN_GENERATED_KEYS и getGeneratedKeys.
  • Обрабатывайте множество операций записи пакетно с помощью addBatch и executeBatch.
  • Никогда не объединяйте пользовательские входные данные со строкой SQL.

Часто задаваемые вопросы

Урок «PreparedStatement» бесплатный?

Да — полный текст урока «PreparedStatement» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.

Чему я научусь в уроке «PreparedStatement»?

Безопасные параметризованные запросы Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Java Academy?

Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «PreparedStatement»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Java Academy?

Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Основы JDBC
  2. PreparedStatement
  3. Транзакции
  4. Пулы соединений с HikariCP
← Назад к Java Academy