При взаимодействии с базой данных в приложении важно учитывать безопасность и производительность запросов. Распространенной плохой практикой в ​​программировании является объединение значений непосредственно в SQL-запрос вместо параметризованных запросов. Также важно отметить, что параметризованные SQL-запросы могут улучшить планирование запросов (объяснить план) в таких механизмах баз данных, как Oracle. Когда используется составной запрос, ядро ​​базы данных не может эффективно спланировать его, поскольку оно не знает, какие значения будут включены в запрос во время выполнения. С другой стороны, когда используются параметризованные SQL-запросы, механизм базы данных может анализировать и планировать запрос более эффективно, повышая производительность запроса.

Что такое составной SQL-запрос?

Составной SQL-запрос — это запрос, в котором переменные добавляются непосредственно в запрос, а не с использованием параметров. Вот пример на Java, использующий JPA с createNativeQuery, чтобы проиллюстрировать это.

Предположим, у нас есть сущность под названием «Пользователи» со следующими атрибутами: «id», «name», «login» и «email».

/**
 * Returns a Users object corresponding to the user with the specified login
 * @param login the login of the user to retrieve
 * @return the Users object corresponding to the user with the specified login
 * @throws NoResultException if no user with the specified login is found
 */
public Users getUserByLogin(String login) throws NoResultException {
    EntityManager em = entityManagerFactory.createEntityManager();
    String query = "SELECT * FROM users WHERE login = " + login;
    Users user = em.createNativeQuery(query, Users.class).getSingleResult();
    em.close();
    return user;
}

В приведенном выше примере объединенный SQL-запрос извлекает пользователя с указанным логином. Проблема с этим запросом заключается в том, что он уязвим для атак SQL-инъекций. Если атрибут входа в систему является частью вспомогательного компонента, который получает свое значение из ввода веб-страницы, пользователь может ввести «Джон» или 1=1; Удалить все из пользователей», что позволяет злоумышленнику удалить все записи из таблицы пользователей.

Что такое параметризованный SQL-запрос?

Параметризованный SQL-запрос — это запрос, в котором параметры используются вместо конкатенации значений непосредственно в запросе. Например:

/**
 * Returns a Users object corresponding to the user with the specified login
 * @param login the login of the user to retrieve
 * @return the Users object corresponding to the user with the specified login
 * @throws NoResultException if no user with the specified login is found
 */
public Users getUserByLogin(String login) throws NoResultException {
    EntityManager em = entityManagerFactory.createEntityManager();
    String query = "SELECT u FROM Users u WHERE u.login = :login";
    TypedQuery<Users> typedQuery = em.createQuery(query, Users.class);
    typedQuery.setParameter("login", login);
    Users user = typedQuery.getSingleResult();
    em.close();
    return user;
}

Используя setParameter, JPA автоматически преобразует предоставленные значения в безопасные параметры запроса, неуязвимые для SQL-инъекций; это означает, что вместо объединения значений параметров непосредственно в строку запроса, как это делается в первой версии кода, мы можем использовать заполнитель в запросе, а затем привязать значение к этому заполнителю с помощью setParameter. JPA будет безопасно обрабатывать любое значение, предоставленное через setParameter, чтобы предотвратить внедрение SQL.

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