Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Как безопасно выполнить SQL-запрос с параметрами

Короткий ответ

Пишите в SQL знак ? вместо значения и передавайте значения через PreparedStatement:

import java.sql.*;
 
void main() throws SQLException {
    try (Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:")) {
        try (Statement st = conn.createStatement()) {
            st.executeUpdate("CREATE TABLE users (name TEXT, city TEXT)");
            st.executeUpdate("INSERT INTO users VALUES ('Anna', 'Paris'), ('Oleg', 'Berlin')");
        }
 
        String city = "Paris";
        try (PreparedStatement ps = conn.prepareStatement("SELECT name FROM users WHERE city = ?")) {
            ps.setString(1, city);
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    IO.println(rs.getString("name"));
                }
            }
        }
    }
}

Результат:

Anna

Несколько параметров

Параметры нумеруются с 1 в порядке появления в запросе. Для изменяющих запросов executeUpdate() возвращает число затронутых строк:

import java.sql.*;
 
void main() throws SQLException {
    try (Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:")) {
        try (Statement st = conn.createStatement()) {
            st.executeUpdate("CREATE TABLE products (title TEXT, price REAL, stock INTEGER)");
            st.executeUpdate("INSERT INTO products VALUES ('Tea', 3.5, 10), ('Cake', 6.0, 0)");
        }
 
        try (PreparedStatement ps = conn.prepareStatement(
                "UPDATE products SET price = price * ? WHERE stock > ?")) {
            ps.setDouble(1, 0.9);
            ps.setInt(2, 0);
            IO.println("Updated: " + ps.executeUpdate());
        }
    }
}

Результат:

Updated: 1

Список значений в IN

Один ? не может заменить список. Создайте столько знаков, сколько значений:

import java.sql.*;
 
void main() throws SQLException {
    try (Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:")) {
        try (Statement st = conn.createStatement()) {
            st.executeUpdate("CREATE TABLE users (id INTEGER, name TEXT)");
            st.executeUpdate("INSERT INTO users VALUES (1, 'Anna'), (2, 'Oleg'), (3, 'Mia')");
        }
 
        List<Integer> ids = List.of(1, 3);
        String marks = String.join(", ", Collections.nCopies(ids.size(), "?"));
        String sql = "SELECT name FROM users WHERE id IN (" + marks + ") ORDER BY id";
 
        try (PreparedStatement ps = conn.prepareStatement(sql)) {
            for (int i = 0; i < ids.size(); i++) {
                ps.setInt(i + 1, ids.get(i));
            }
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    IO.println(rs.getString(1));
                }
            }
        }
    }
}

Результат:

Anna
Mia

Частая ошибка

Склеить SQL из строк. Специально подобранный ввод меняет смысл запроса — это SQL-инъекция:

import java.sql.*;
 
void main() throws SQLException {
    try (Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:");
         Statement st = conn.createStatement()) {
        st.executeUpdate("CREATE TABLE users (name TEXT, password TEXT)");
        st.executeUpdate("INSERT INTO users VALUES ('Anna', 'a1'), ('Oleg', 'o2')");
 
        String password = "x' OR '1'='1";
        String sql = "SELECT name FROM users WHERE password = '" + password + "'";
        try (ResultSet rs = st.executeQuery(sql)) {
            while (rs.next()) {
                IO.println("Logged in as " + rs.getString(1));
            }
        }
    }
}

Результат:

Logged in as Anna
Logged in as Oleg

С PreparedStatement та же строка — просто неверный пароль, и запрос не найдёт ни одной строки. Имена таблиц и столбцов параметром передать нельзя: если они зависят от ввода, выбирайте их из заранее известного списка.

Функции из этого ответа

  • PreparedStatement — Заранее подготовленный SQL-запрос с параметрами ?. Защищает от SQL-инъекций.
  • ResultSet — Результат SELECT-запроса: курсор, который по одной перебирает строки таблицы.
  • DriverManager.getConnection() — Открывает соединение с базой данных по JDBC-адресу.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.