Перейти к основному содержимому

Использование параметров запроса JPA

· 6 мин. чтения

1. Введение

Построение запросов с использованием JPA несложно; однако иногда мы забываем простые вещи, которые имеют огромное значение.

Одной из таких вещей являются параметры запроса JPA, и именно на них мы сосредоточимся в этом руководстве.

2. Что такое параметры запроса?

Начнем с объяснения того, что такое параметры запроса.

Параметры запроса — это способ построения и выполнения параметризованных запросов. Итак, вместо:

SELECT * FROM employees e WHERE e.emp_number = '123';

Мы бы сделали:

SELECT * FROM employees e WHERE e.emp_number = ?;

Используя подготовленный оператор JDBC, нам нужно установить параметр перед выполнением запроса:

pStatement.setString(1, 123);

3. Почему мы должны использовать параметры запроса?

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

Давайте перепишем предыдущий запрос, чтобы получить сотрудников по emp_number с помощью JPA API, но вместо параметра мы будем использовать литерал, чтобы наглядно проиллюстрировать ситуацию:

String empNumber = "A123";
TypedQuery<Employee> query = em.createQuery(
"SELECT e FROM Employee e WHERE e.empNumber = '" + empNumber + "'", Employee.class);
Employee employee = query.getSingleResult();

Этот подход имеет некоторые недостатки:

  • Параметры встраивания создают угрозу безопасности, делая нас уязвимыми для атак с внедрением JPQL . Вместо ожидаемого значения злоумышленник может внедрить любое неожиданное и, возможно, опасное выражение JPQL.
  • В зависимости от используемой реализации JPA и эвристики нашего приложения кэш запросов может быть исчерпан. Новый запрос может создаваться, компилироваться и кэшироваться каждый раз, когда мы используем его с каждым новым значением/параметром. Как минимум, это будет неэффективно, а также может привести к неожиданной ошибке OutOfMemoryError.

4. Параметры запроса JPA

Подобно параметрам подготовленных операторов JDBC, JPA определяет два разных способа написания параметризованных запросов, используя:

  • Позиционные параметры
  • Именованные параметры

Мы можем использовать либо позиционные, либо именованные параметры, но мы не должны смешивать их в одном запросе.

4.1. Позиционные параметры

Использование позиционных параметров — это один из способов избежать вышеупомянутых проблем, перечисленных ранее.

Давайте посмотрим, как бы мы написали такой запрос с помощью позиционных параметров:

TypedQuery<Employee> query = em.createQuery(
"SELECT e FROM Employee e WHERE e.empNumber = ?1", Employee.class);
String empNumber = "A123";
Employee employee = query.setParameter(1, empNumber).getSingleResult();

Как мы видели в предыдущем примере, мы объявляем эти параметры в запросе, вводя вопросительный знак, за которым следует положительное целое число . Мы начнем с 1 и будем двигаться вперед, каждый раз увеличивая его на единицу.

Мы можем использовать один и тот же параметр более одного раза в одном запросе, что делает эти параметры более похожими на именованные параметры.

Нумерация параметров — очень полезная функция, поскольку она повышает удобство использования, удобочитаемость и обслуживание.

Стоит отметить, что собственные SQL-запросы также поддерживают привязку позиционных параметров .

4.2. Значащие коллекцию позиционные параметры

Как указывалось ранее, мы также можем использовать параметры с коллекцией:

TypedQuery<Employee> query = entityManager.createQuery(
"SELECT e FROM Employee e WHERE e.empNumber IN (?1)" , Employee.class);
List<String> empNumbers = Arrays.asList("A123", "A124");
List<Employee> employees = query.setParameter(1, empNumbers).getResultList();

4.3. Именованные параметры

Именованные параметры очень похожи на позиционные параметры; однако, используя их, мы делаем параметры более явными, и запрос становится более читаемым:

TypedQuery<Employee> query = em.createQuery(
"SELECT e FROM Employee e WHERE e.empNumber = :number" , Employee.class);
String empNumber = "A123";
Employee employee = query.setParameter("number", empNumber).getSingleResult();

Предыдущий пример запроса такой же, как и первый, но мы использовали именованный параметр :number вместо ?1 .

Мы видим, что мы объявили параметр с двоеточием, за которым следует строковый идентификатор (идентификатор JPQL), который является заполнителем для фактического значения, которое мы установим во время выполнения. Перед выполнением запроса мы должны установить параметр или параметры, выполнив метод setParameter .

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

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

TypedQuery<Employee> query = em.createQuery(
"SELECT e FROM Employee e WHERE e.name = :name AND e.age = :empAge" , Employee.class);
String empName = "John Doe";
int empAge = 55;
List<Employee> employees = query
.setParameter("name", empName)
.setParameter("empAge", empAge)
.getResultList();

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

Если по какой-то причине нам нужно использовать один и тот же параметр много раз в одном и том же запросе, нам просто нужно установить его один раз, выполнив метод « setParameter ». Во время выполнения указанные значения заменят каждое вхождение параметра.

Наконец, стоит упомянуть, что спецификация Java Persistence API не требует, чтобы собственные запросы поддерживали именованные параметры . Даже когда некоторые реализации, такие как Hibernate, поддерживают его, мы должны учитывать, что если мы его используем, запрос не будет таким переносимым.

4.4. Значащие коллекцию именованные параметры

Для ясности давайте также продемонстрируем, как это работает с параметрами, возвращающими значение коллекции:

TypedQuery<Employee> query = entityManager.createQuery(
"SELECT e FROM Employee e WHERE e.empNumber IN (:numbers)" , Employee.class);
List<String> empNumbers = Arrays.asList("A123", "A124");
List<Employee> employees = query.setParameter("numbers", empNumbers).getResultList();

Как мы видим, это работает аналогично позиционным параметрам.

5. Критерии параметров запроса

Запрос JPA может быть построен с использованием JPA Criteria API , который очень подробно объясняется в официальной документации Hibernate .

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

Давайте снова создадим тот же запрос, но на этот раз с использованием Criteria API, чтобы продемонстрировать, как обрабатывать параметры запроса при работе с CriteriaQuery :

CriteriaBuilder cb = em.getCriteriaBuilder();

CriteriaQuery<Employee> cQuery = cb.createQuery(Employee.class);
Root<Employee> c = cQuery.from(Employee.class);
ParameterExpression<String> paramEmpNumber = cb.parameter(String.class);
cQuery.select(c).where(cb.equal(c.get(Employee_.empNumber), paramEmpNumber));

TypedQuery<Employee> query = em.createQuery(cQuery);
String empNumber = "A123";
query.setParameter(paramEmpNumber, empNumber);
Employee employee = query.getResultList();

Для этого типа запроса механика параметра немного отличается, поскольку мы используем объект параметра, но по сути разницы нет.

В предыдущем примере мы видим использование класса Employee_ . Мы сгенерировали этот класс с помощью генератора метамодели Hibernate. Эти компоненты являются частью статической метамодели JPA, которая позволяет создавать строго типизированные запросы критериев.

6. Заключение

В этой статье мы сосредоточились на механизме построения запросов с использованием параметров запроса JPA или входных параметров.

Мы узнали, что у нас есть два типа параметров запроса, позиционные и именованные, и нам решать, какой из них лучше всего соответствует нашим целям.

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

Как обычно, исходный код этой статьи доступен на GitHub .