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 с помощью API JPA, но вместо параметра мы будем использовать литерал, чтобы четко проиллюстрировать ситуацию:
String empNumber = "A123"; TypedQueryquery = 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. Позиционные параметры
Использование позиционных параметров является одним из способов избежать вышеупомянутых проблем, перечисленных ранее.
Давайте посмотрим, как бы мы написали такой запрос с помощью позиционных параметров:
TypedQueryquery = 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. Позиционные Параметры С Коллекционным Значением
Как указывалось ранее, мы также можем использовать параметры, имеющие значение коллекции:
TypedQueryquery = entityManager.createQuery( "SELECT e FROM Employee e WHERE e.empNumber IN (?1)" , Employee.class); List empNumbers = Arrays.asList("A123", "A124"); List employees = query.setParameter(1, empNumbers).getResultList();
4.3. Именованные параметры
Именованные параметры очень похожи на позиционные параметры; однако, используя их, мы делаем параметры более явными, и запрос становится более читаемым:
TypedQueryquery = 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 поддерживает цепочку методов , которая становится очень полезной, когда необходимо задать несколько параметров.
Давайте продолжим и создадим вариант предыдущего запроса, используя два именованных параметра, чтобы проиллюстрировать цепочку методов:
TypedQueryquery = 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 employees = query .setParameter("name", empName) .setParameter("empAge", empAge) .getResultList();
Здесь мы извлекаем всех сотрудников с указанным именем и возрастом. Как мы ясно видим и можем ожидать, мы можем создавать запросы с несколькими параметрами и таким количеством их вхождений, сколько требуется.
Если по какой-то причине нам нужно использовать один и тот же параметр много раз в одном и том же запросе, нам просто нужно установить его один раз, выполнив метод ” setParameter “. Во время выполнения указанные значения будут заменять каждое вхождение параметра.
Наконец, стоит упомянуть, что спецификация API персистентности Java не требует, чтобы именованные параметры поддерживались собственными запросами . Даже когда некоторые реализации, такие как Hibernate, поддерживают его, мы должны учитывать, что если мы его используем, запрос не будет таким переносимым.
4.4. Именованные Параметры Со Значением Коллекции
Для ясности давайте также продемонстрируем, как это работает с параметрами, имеющими значение коллекции:
TypedQueryquery = entityManager.createQuery( "SELECT e FROM Employee e WHERE e.empNumber IN (:numbers)" , Employee.class); List empNumbers = Arrays.asList("A123", "A124"); List employees = query.setParameter("numbers", empNumbers).getResultList();
Как мы видим, он работает аналогично позиционным параметрам.
5. Параметры запроса критериев
Запрос JPA может быть построен с помощью API критериев JPA , который Hibernate официальная документация объясняет очень подробно.
В этом типе запросов мы представляем параметры, используя объекты вместо имен или индексов.
Давайте снова построим тот же запрос, но на этот раз с использованием API критериев, чтобы продемонстрировать, как обрабатывать параметры запроса при работе с CriteriaQuery :
CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuerycQuery = cb.createQuery(Employee.class); Root c = cQuery.from(Employee.class); ParameterExpression paramEmpNumber = cb.parameter(String.class); cQuery.select(c).where(cb.equal(c.get(Employee_.empNumber), paramEmpNumber)); TypedQuery query = em.createQuery(cQuery); String empNumber = "A123"; query.setParameter(paramEmpNumber, empNumber); Employee employee = query.getResultList();
Для этого типа запроса механика параметров немного отличается, так как мы используем объект параметров, но, по сути, разницы нет.
В предыдущем примере мы видим использование класса Employee_ . Мы создали этот класс с помощью генератора метамодели Hibernate. Эти компоненты являются частью статической метамодели JPA, которая позволяет создавать запросы критериев строго типизированным способом.
6. Заключение
В этой статье мы сосредоточились на механике построения запросов с использованием параметров запроса JPA или входных параметров.
Мы узнали, что у нас есть два типа параметров запроса: позиционные и именованные. От нас зависит, какой из них лучше всего соответствует нашим целям.
Также стоит отметить, что все параметры запроса должны быть однозначными, за исключением выражений in . Для выражений in мы можем использовать входные параметры со значением коллекции, такие как массивы или List s, как показано в предыдущих примерах.
Исходный код этого учебника, как обычно, доступен на GitHub .