Projections JPA / Hibernate

1. Vue d'ensemble

Dans ce didacticiel, nous allons apprendre à projeter les propriétés d'entité à l'aide de JPA et Hibernate .

2. L'entité

Tout d'abord, regardons l'entité que nous utiliserons tout au long de cet article:

@Entity public class Product { @Id private long id; private String name; private String description; private String category; private BigDecimal unitPrice; // setters and getters }

Il s'agit d'une classe d'entité simple représentant un produit avec diverses propriétés.

3. Projections JPA

Bien que la spécification JPA ne mentionne pas explicitement les projections, il existe de nombreux cas où nous les trouvons dans le concept.

En règle générale, une requête JPQL a une classe d'entité candidate. La requête, lors de l'exécution, crée des objets de la classe candidate - remplissant toutes leurs propriétés à l'aide des données récupérées.

Mais, il est possible de récupérer un sous-ensemble des propriétés de l'entité, ou, c'est-à-dire une projection de données de colonne.

Outre les données de colonne, nous pouvons également projeter les résultats des fonctions de regroupement.

3.1. Projections sur une seule colonne

Supposons que nous voulions lister les noms de tous les produits. Dans JPQL, nous pouvons le faire en incluant uniquement le nom dans la clause select :

Query query = entityManager.createQuery("select name from Product"); List resultList = query.getResultList();

Ou, nous pouvons faire la même chose avec CriteriaBuilder :

CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery query = builder.createQuery(String.class); Root product = query.from(Product.class); query.select(product.get("name")); List resultList = entityManager.createQuery(query).getResultList();

Étant donné que nous projetons une seule colonne qui se trouve être de type String , nous nous attendons à obtenir une liste de String s dans le résultat. Par conséquent, nous avons spécifié la classe candidate comme String dans la méthode createQuery () .

Puisque nous voulons projeter sur une seule propriété, nous avons utilisé la méthode Query.select () . Ce qui va ici est la propriété que nous voulons, donc dans notre cas, nous utiliserions la propriété name de notre entité Product .

Maintenant, regardons un exemple de sortie généré par les deux requêtes ci-dessus:

Product Name 1 Product Name 2 Product Name 3 Product Name 4

Notez que si nous avions utilisé la propriété id dans la projection au lieu de name , la requête aurait renvoyé une liste d' objets Long .

3.2. Projections multi-colonnes

Pour projeter sur plusieurs colonnes en utilisant JPQL, il suffit d'ajouter toutes les colonnes requises à la clause select :

Query query = session.createQuery("select id, name, unitPrice from Product"); List resultList = query.getResultList();

Mais, lors de l'utilisation d'un CriteriaBuilder , nous devrons faire les choses un peu différemment:

CriteriaBuilder builder = session.getCriteriaBuilder(); CriteriaQuery query = builder.createQuery(Object[].class); Root product = query.from(Product.class); query.multiselect(product.get("id"), product.get("name"), product.get("unitPrice")); List resultList = entityManager.createQuery(query).getResultList();

Ici, nous avons utilisé la méthode multiselect () au lieu de select () . En utilisant cette méthode, nous pouvons spécifier plusieurs éléments à sélectionner.

Un autre changement important est l'utilisation d' Object [] . Lorsque nous sélectionnons plusieurs éléments, la requête renvoie un tableau d'objets avec une valeur pour chaque élément projeté. C'est également le cas avec JPQL.

Voyons à quoi ressemblent les données lorsque nous les imprimons:

[1, Product Name 1, 1.40] [2, Product Name 2, 4.30] [3, Product Name 3, 14.00] [4, Product Name 4, 3.90]

Comme nous pouvons le voir, les données renvoyées sont un peu lourdes à traiter. Mais, heureusement, nous pouvons demander à JPA de remplir ces données dans une classe personnalisée.

De plus, nous pouvons utiliser CriteriaBuilder.tuple () ou CriteriaBuilder.construct () pour obtenir les résultats sous la forme d'une liste d' objets Tuple ou d'objets d'une classe personnalisée respectivement.

3.3. Projection de fonctions d'agrégation

Outre les données de colonne, nous pouvons parfois souhaiter regrouper les données et utiliser des fonctions d'agrégation, telles que le nombre et la moyenne.

Disons que nous voulons trouver le nombre de produits dans chaque catégorie. Nous pouvons le faire en utilisant la fonction d'agrégation count () dans JPQL:

Query query = entityManager.createQuery("select p.category, count(p) from Product p group by p.category");

Ou nous pouvons utiliser CriteriaBuilder :

CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery query = builder.createQuery(Object[].class); Root product = query.from(Product.class); query.multiselect(product.get("category"), builder.count(product)); query.groupBy(product.get("category"));

Ici, nous avons utilisé CriteriaBuilder de count () méthode.

L'utilisation de l'un ou l'autre des éléments ci-dessus produira une liste de tableaux d'objets:

[category1, 2] [category2, 1] [category3, 1]

Outre count () , CriteriaBuilder fournit diverses autres fonctions d'agrégation:

  • avg - Calcule la valeur moyenne d'une colonne dans un groupe
  • max - Calcule la valeur maximale d'une colonne dans un groupe
  • min - Calcule la valeur minimale d'une colonne dans un groupe
  • moins - Recherche la plus petite des valeurs de colonne (par exemple, par ordre alphabétique ou par date)
  • sum - Calcule la somme des valeurs de colonne dans un groupe

4. Projections Hibernate

Unlike JPA, Hibernate provides org.hibernate.criterion.Projection for projecting with a Criteria query. It also provides a class called org.hibernate.criterion.Projections, a factory for Projection instances.

4.1. Single-Column Projections

First, let's see how we can project a single column. We'll use the example we saw earlier:

Criteria criteria = session.createCriteria(Product.class); criteria = criteria.setProjection(Projections.property("name")); 

We've used the Criteria.setProjection() method to specify the property that we want in the query result. Projections.property() does the same work for us as Root.get() did when indicating the column to select.

4.2. Multi-Column Projections

To project multiple columns, we'll have to first create a ProjectionList. ProjectionList is a special kind of Projection that wraps other projections to allow selecting multiple values.

We can create a ProjectionListusing the Projections.projectionList() method, like showing the Product‘s id and name:

Criteria criteria = session.createCriteria(Product.class); criteria = criteria.setProjection( Projections.projectionList() .add(Projections.id()) .add(Projections.property("name")));

4.3. Projecting Aggregate Functions

Just like CriteriaBuilder, the Projections class also provides methods for aggregate functions.

Let's see how we can implement the count example we saw earlier:

Criteria criteria = session.createCriteria(Product.class); criteria = criteria.setProjection( Projections.projectionList() .add(Projections.groupProperty("category")) .add(Projections.rowCount()));

It's important to note that we didn't directly specify the GROUP BY in the Criteria object. Calling groupProperty triggers this for us.

Apart from the rowCount() function, Projections also provides the aggregate functions we saw earlier.

4.4. Using an Alias for a Projection

An interesting feature of the Hibernate Criteria API is the use of an alias for a projection.

This is especially useful when using an aggregate function, as we can then refer to the alias in the Criterion and Order instances:

Criteria criteria = session.createCriteria(Product.class); criteria = criteria.setProjection(Projections.projectionList() .add(Projections.groupProperty("category")) .add(Projections.alias(Projections.rowCount(), "count"))); criteria.addOrder(Order.asc("count"));

5. Conclusion

In this article, we saw how to project entity properties using JPA and Hibernate.

Il est important de noter qu'Hibernate a déconseillé son API Criteria à partir de la version 5.2 au profit de l'API JPA CriteriaQuery . Mais c'est uniquement parce que l'équipe Hibernate n'a pas le temps de garder deux API différentes, qui font à peu près la même chose, synchronisées.

Et bien sûr, le code utilisé dans cet article se trouve à l'adresse over sur GitHub.