В чем разница между Statement и PreparedStatement в JDBC?

Ответ

Statement и PreparedStatement — это интерфейсы JDBC для выполнения SQL-запросов, но с ключевыми различиями в безопасности и производительности.

Statement

  • Выполняет статические SQL-запросы, переданные в виде строки.
  • Уязвим к SQL-инъекциям, так как параметры конкатенируются напрямую в строку запроса.
  • Менее эффективен при многократном выполнении одного и того же запроса с разными параметрами, так как каждый раз запрос компилируется заново.

PreparedStatement

  • Использует параметризованные запросы с плейсхолдерами (?).
  • Защищает от SQL-инъекций, так как параметры передаются отдельно и экранируются драйвером БД.
  • Повышает производительность при повторных вызовах: запрос компилируется один раз, а план выполнения может кэшироваться СУБД.

Пример использования:

// Statement (небезопасно и неэффективно)
String userInput = "123 OR 1=1";
Statement stmt = connection.createStatement();
String sql = "DELETE FROM users WHERE id = " + userInput; // Опасная конкатенация!
stmt.executeUpdate(sql); // Может удалить всех пользователей

// PreparedStatement (безопасно и эффективно)
int userId = 123;
String sql = "DELETE FROM users WHERE id = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setInt(1, userId); // Параметр безопасно подставляется
pstmt.executeUpdate();

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

Ответ 18+ 🔞

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

Statement — это как если бы ты взял и прибил эту дверь наглухо, оставив только форточку. А в форточку ты пропихиваешь записку с командой. Любой проходящий мимо мудак может написать на этой записке что угодно. Например: «Удали всех пользователей». И система, такая доверчивая, выполнит. Это и есть SQL-инъекция — хуй с горы, а не защита. Каждый раз, когда ты шлёшь новую записку, база данных с нуля её читает и думает: «А что это тут такое?». Производительность — пиздец, терпения ебать ноль.

PreparedStatement — это уже нормальная дверь с кодовым замком. Ты заранее говоришь базе: «Слушай, будет запрос вида "удали пользователя с ID = ?"». База его компилирует, запоминает план и ждёт. А когда нужно выполнить, ты просто передаёшь цифру в этот плейсхолдер (?). База уже знает, что на этом месте должна быть только цифра, а не какая-нибудь манда с ушами вроде 123 OR 1=1. Она эту цифру безопасно подставляет. И если тебе нужно удалить десять пользователей подряд, запрос не компилируется десять раз — он один раз подготовлен и просто жрёт новые параметры. Овердохуища быстрее и безопаснее.

Смотри на примере, тут всё видно:

// Statement (это просто пиздец, как так можно)
String userInput = "123 OR 1=1"; // Вот этот пидарас шерстяной хочет всё сломать
Statement stmt = connection.createStatement();
String sql = "DELETE FROM users WHERE id = " + userInput; // Конкатенация строк — привет, инъекция!
stmt.executeUpdate(sql); // База выполнит это как "удали ВСЕХ, у кого id=123 ИЛИ 1=1". Всех пользователей накрыли медным тазом.

// PreparedStatement (вот так надо, по-человечески)
int userId = 123; // Нормальный, ожидаемый параметр
String sql = "DELETE FROM users WHERE id = ?"; // Вопрос — наш плейсхолдер
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setInt(1, userId); // Безопасно подставляем цифру на место первого знака вопроса
pstmt.executeUpdate(); // Выполнится ТОЛЬКО "удали пользователя с id=123"

Так что когда тестируешь или код пишешь, смотри в оба. Если видишь, что строки склеиваются плюсиками и туда суётся что попало с фронта — это красная тряпка. Доверия ебать ноль такому коду. Надо везде пихать PreparedStatement. Иначе будет вам не хиросима, а полноценный нигерсраки в продакшене.