«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.1. Позиционные параметры

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

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

Как мы видели в предыдущем примере, мы объявляем эти параметры в запросе, вводя вопросительный знак, за которым следует положительное целое число. Мы начнем с 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();

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

4.2. Позиционные параметры с набором значений

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

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

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

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

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();

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

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

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();

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

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

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

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

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();

4.4. Именованные параметры с коллекцией

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

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

5. Параметры запроса Criteria

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

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();

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

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

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

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

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

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();

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

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

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

Исходный код этого руководства, как обычно, доступен на GitHub.

«

It’s also worth noting that all query parameters must be single-valued except for in expressions. For in expressions, we may use collection-valued input parameters, such as arrays or Lists as shown within the previous examples.

The source code of this tutorial, as usual, is available over on GitHub.