Зарегистрируйтесь, чтобы продолжить обучение

Стейтменты Java JDBC: Работа с базой данных

Как правило, запросы в базу формируются динамически на основе данных, которые поступают извне. Например, создание пользователя чаще всего происходит во время регистрации. В таком случае код вставки выглядит так:

var name = ...;
var phone = ...;

var sql = "INSERT INTO users (username, phone) VALUES ('" + name + "', '" + phone + "')";
try (var statement = conn.createStatement()) {
    statement.executeUpdate(sql);
}

Составленные таким образом запросы несут в себе серьезную опасность — SQL-инъекции. Вместо обычных данных злоумышленник может отправить текст, который изменит SQL-запрос. Представьте, что внутри переменной хранилась бы такая строка:

'); INSERT INTO users (username, phone) VALUES ('i am hacker', '777777);

В таком случае после подстановки мы бы получили следующий запрос:

INSERT INTO users (username, phone) VALUES ('somename', '');
INSERT INTO users (username, phone) VALUES ('i am hacker', '777777');

Фактически, вместо одного запроса получилось два. Таким образом, злоумышленник может выполнить произвольный запрос в базу данных — то есть удалить, добавить или изменить все, что ему хочется.

В разработке существует правило «Никогда не доверяй пользовательским данным» — оно особенно важно в работе с базами данных. Любые данные перед вставкой нужно экранировать с помощью подготовленных запросов:

var sql = "INSERT INTO users (username, phone) VALUES (?, ?)";
try (var preparedStatement = conn.prepareStatement(sql)) {
    preparedStatement.setString(1, "Tommy");
    preparedStatement.setString(2, "33333333");
    preparedStatement.executeUpdate();
}

Подготовленные запросы создаются с помощью плейсхолдеров. Это заполнители, которые расставляются в местах, где ожидается подстановка данных. При этом плейсхолдеры не нужно оборачивать в кавычки, если это строки. JDBC работает так:

  • Автоматически выполняет подстановку
  • Создает стейтмент preparedStatement
  • Передает в него подготовленный SQL-шаблон
  • Заполняет шаблон данными
  • Вызывает его

Заполнение данными выполняется с помощью методов, похожих на те, которые используются в ResultSet. Разница только в том, что здесь конвертация происходит в обратную сторону. Каждый метод наподобие setString() принимает на вход два параметра:

  • Порядковый номер плейсхолдера
  • Данные, которые нужно подставить

Ускорение запросов

Кроме того, preparedStatement может ускорять запросы. Дело в том, что мы можем выполнять множество запросов подряд в рамках одного preparedStatement. За счет этого запрос готовится предварительно, что ускоряет работу:

var sql = "INSERT INTO users (username, phone) VALUES (?, ?)";
try (var preparedStatement = conn.prepareStatement(sql)) {
    preparedStatement.setString(1, "Tommy");
    preparedStatement.setString(2, "33333333");
    preparedStatement.executeUpdate();

    preparedStatement.setString(1, "Maria");
    preparedStatement.setString(2, "44444444");
    preparedStatement.executeUpdate();
}

Возврат идентификатора

Когда мы вставляем данные в базу, иногда нам нужно получить идентификатор вставленной записи и потом использовать его в коде. Это происходит, когда мы создаем какую-то сущность и хотим потом ее использовать:

var user = new User();
// Сохраняем пользователя в базу данных
// После этого становится доступен id
user.getId();
// Его можно использовать для формирования ссылок или вставки связанных записей

Чтобы выполнить эту задачу, немного изменим запрос:

// Импортируем
import java.sql.Statement;

var sql = "INSERT INTO users (username, phone) VALUES (?, ?)";
try (var preparedStatement = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
    preparedStatement.setString(1, "Sarah");
    preparedStatement.setString(2, "333333333");
    preparedStatement.executeUpdate();
    // Если ключ составной, значений может быть несколько
    // В нашем случае, ключ всего один
    var generatedKeys = preparedStatement.getGeneratedKeys();
    if (generatedKeys.next()) {
        System.out.println(generatedKeys.getLong(1));
    } else {
        throw new SQLException("DB have not returned an id after saving the entity");
    }
}

Самостоятельная работа

  1. Выполните шаги из урока у себя на компьютере
  2. Добавьте в базу данных несколько пользователей, используя один preparedStatement
  3. Используйте preparedStatement и добавьте в приложение запрос, который удалит одного из созданных пользователей по его имени

Дополнительные материалы

  1. Класс PreparedStatement

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff