Utilisation des paramètres de requête JPA

1. Introduction

Créer des requêtes à l'aide de JPA n'est pas difficile; cependant, nous oublions parfois des choses simples qui font une énorme différence.

L'une de ces choses est les paramètres de requête JPA, et c'est ce dont nous allons parler.

2. Que sont les paramètres de requête?

Commençons par expliquer ce que sont les paramètres de requête.

Les paramètres de requête sont un moyen de créer et d'exécuter des requêtes paramétrées. Donc, au lieu de:

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

Nous ferions:

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

En utilisant une instruction préparée JDBC, nous devons définir le paramètre avant d'exécuter la requête:

pStatement.setString(1, 123);

3. Pourquoi devrions-nous utiliser des paramètres de requête?

Au lieu d'utiliser des paramètres de requête, nous aurions pu choisir d'utiliser des littéraux, cependant, ce n'est pas la manière recommandée de le faire, comme nous le verrons maintenant.

Réécrivons la requête précédente pour obtenir les employés par emp_number à l'aide de l'API JPA, mais au lieu d'utiliser un paramètre, nous utiliserons un littéral afin de pouvoir illustrer clairement la situation:

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

Cette approche présente quelques inconvénients:

  • L'intégration des paramètres introduit un risque de sécurité qui nous rend vulnérables aux attaques par injection JPQL. Au lieu de la valeur attendue, un attaquant peut injecter toute expression JPQL inattendue et éventuellement dangereuse
  • Selon l'implémentation JPA que nous utilisons et l'heuristique de notre application, le cache de requêtes peut être épuisé. Une nouvelle requête peut être construite, compilée et mise en cache chaque fois que nous l'utilisons avec chaque nouvelle valeur / paramètre. Au minimum, cela ne sera pas efficace et cela peut également conduire à une OutOfMemoryError inattendue

4. Paramètres de requête JPA

Semblable aux paramètres d'instructions préparées JDBC, JPA spécifie deux manières différentes d'écrire des requêtes paramétrées en utilisant:

  • Paramètres de position
  • Paramètres nommés

Nous pouvons utiliser des paramètres positionnels ou nommés mais nous ne devons pas les mélanger dans la même requête.

4.1. Paramètres de position

L'utilisation des paramètres de position est un moyen d'éviter les problèmes susmentionnés énumérés précédemment.

Voyons comment nous écririons une telle requête à l'aide de paramètres de position:

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

Comme nous l'avons vu dans l'exemple précédent, nous déclarons ces paramètres dans la requête en tapant un point d'interrogation suivi d'un nombre entier positif . Nous allons commencer par 1 et avancer, en l'incrémentant de un à chaque fois.

Nous pouvons utiliser le même paramètre plusieurs fois dans la même requête, ce qui rend ces paramètres plus similaires aux paramètres nommés.

La numérotation des paramètres est une fonctionnalité très utile car elle améliore la convivialité, la lisibilité et la maintenance.

Il convient de mentionner que la liaison de paramètres positionnels est également prise en charge par les requêtes SQL natives .

4.2. Paramètres de position à valeur de collection

Comme indiqué précédemment, nous pouvons également utiliser des paramètres à valeur de collection:

TypedQuery query = 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. Paramètres nommés

Les paramètres nommés sont assez similaires aux paramètres de position; cependant, en les utilisant, nous rendons les paramètres plus explicites et la requête devient plus lisible:

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

L'exemple de requête précédent est le même que le premier, mais nous avons utilisé : number , un paramètre nommé, au lieu de ? 1 .

We can see we declared the parameter with a colon followed by a string identifier (JPQL identifier) which is a placeholder for the actual value that will be set at runtime. Before executing the query, the parameter or parameters have to be set by issuing the setParameter method.

One interesting thing to remark is that the TypedQuery supports method chaining which becomes very useful when multiple parameters have to be set.

Let's go ahead and create a variation of the previous query using two named parameters to illustrate the method chaining:

TypedQuery 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 employees = query .setParameter("name", empName) .setParameter("empAge", empAge) .getResultList();

Here, we're retrieving all employees with the given name and age. As we clearly see and one may expect, we can build queries with multiple parameters and as many occurrences of them as required.

If for some reason we do need to use the same parameter many times within the same query, we just need to set it once by issuing the “setParameter” method. At runtime, the specified values will replace each occurrence of the parameter.

Lastly, it's worth mentioning that the Java Persistence API specification does not mandate named parameters to be supported by native queries. Even when some implementations like Hibernate do support it, we need to take into account that if we do use it, the query will not be as portable.

4.4. Collection-Valued Named Parameters

For clarity, let's also demonstrate how this works with collection-valued parameters:

TypedQuery query = 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();

As we can see, it works in a similar way to positional parameters.

5. Criteria Query Parameters

A JPA query may be built by using the JPA Criteria API, which Hibernate's official documentation explains in great detail.

In this type of query, we represent parameters by using objects instead of names or indices.

Let's build the same query again but this time using the Criteria API to demonstrate how to handle query parameters when dealing with CriteriaQuery:

CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cQuery = 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();

For this type of query, the parameter's mechanic is a little bit different since we use a parameter object but in essence, there's no difference.

Within the previous example, we can see the usage of the Employee_ class. We generated this class with the Hibernate metamodel generator. These components are part of the static JPA metamodel, which allows criteria queries to be built in a strongly-typed manner.

6. Conclusion

Dans cet article, nous nous sommes concentrés sur les mécanismes de création de requêtes à l'aide de paramètres de requête JPA ou de paramètres d'entrée.

Nous avons appris que nous avons deux types de paramètres de requête, positionnels et nommés. C'est à nous de décider lequel correspond le mieux à nos objectifs.

Il convient également de noter que tous les paramètres de requête doivent avoir une valeur unique, à l'exception des expressions in . Pour en expressions, on peut utiliser des paramètres d'entrée contenant des collections, comme des tableaux ou liste d comme indiqué dans les exemples précédents.

Le code source de ce tutoriel, comme d'habitude, est disponible à l'adresse over sur GitHub.