Proiezioni JPA / Hibernate

1. Panoramica

In questo tutorial, impareremo come proiettare le proprietà delle entità utilizzando JPA e Hibernate .

2. L'Entità

Per prima cosa, diamo un'occhiata all'entità che useremo in questo articolo:

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

Questa è una semplice classe di entità che rappresenta un prodotto con varie proprietà.

3. Proiezioni JPA

Sebbene la specifica JPA non menzioni esplicitamente le proiezioni, ci sono molti casi in cui le troviamo concettualmente.

In genere, una query JPQL ha una classe entità candidata. La query, in esecuzione, crea oggetti della classe candidata, popolando tutte le loro proprietà utilizzando i dati recuperati.

Tuttavia, è possibile recuperare un sottoinsieme delle proprietà dell'entità o, cioè, una proiezione dei dati della colonna.

Oltre ai dati delle colonne, possiamo anche proiettare i risultati delle funzioni di raggruppamento.

3.1. Proiezioni a colonna singola

Supponiamo di voler elencare i nomi di tutti i prodotti. In JPQL, possiamo farlo includendo solo il nome nella clausola select :

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

Oppure possiamo fare lo stesso con 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();

Poiché stiamo proiettando una singola colonna che sembra essere di tipo stringa , ci aspettiamo di ottenere un elenco di stringa s nel risultato. Quindi, abbiamo specificato la classe candidata come String nel metodo createQuery () .

Poiché vogliamo proiettare su una singola proprietà, abbiamo utilizzato il metodo Query.select () . Quello che va qui è la proprietà che vogliamo, quindi nel nostro caso, useremo la proprietà name dalla nostra entità Product .

Ora, diamo un'occhiata a un output di esempio generato dalle due query precedenti:

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

Nota che se avessimo usato la proprietà id nella proiezione invece di name , la query avrebbe restituito un elenco di oggetti Long .

3.2. Proiezioni a più colonne

Per proiettare su più colonne utilizzando JPQL, dobbiamo solo aggiungere tutte le colonne richieste alla clausola select :

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

Ma, quando si utilizza un CriteriaBuilder , dovremo fare le cose in modo leggermente diverso:

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

Qui, abbiamo usato il metodo multiselect () invece di select () . Utilizzando questo metodo, possiamo specificare più elementi da selezionare.

Un altro cambiamento significativo è l'uso di Object [] . Quando selezioniamo più elementi, la query restituisce un array di oggetti con un valore per ogni elemento proiettato. Questo è anche il caso di JPQL.

Vediamo come appaiono i dati quando li stampiamo:

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

Come possiamo vedere, i dati restituiti sono un po 'complicati da elaborare. Ma, fortunatamente, possiamo convincere JPA a popolare questi dati in una classe personalizzata.

Inoltre, possiamo usare CriteriaBuilder.tuple () o CriteriaBuilder.construct () per ottenere i risultati rispettivamente come un elenco di oggetti Tuple o oggetti di una classe personalizzata.

3.3. Proiezione di funzioni aggregate

Oltre ai dati delle colonne, a volte potremmo voler raggruppare i dati e utilizzare funzioni aggregate, come conteggio e media.

Supponiamo di voler trovare il numero di prodotti in ciascuna categoria. Possiamo farlo usando la funzione di aggregazione count () in JPQL:

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

Oppure possiamo usare 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"));

Qui, abbiamo usato CriteriaBuilder 's count () metodo.

L'utilizzo di uno dei metodi precedenti produrrà un elenco di array di oggetti:

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

Oltre a count () , CriteriaBuilder fornisce varie altre funzioni aggregate:

  • avg - Calcola il valore medio per una colonna in un gruppo
  • max : calcola il valore massimo per una colonna in un gruppo
  • min - Calcola il valore minimo per una colonna in un gruppo
  • minimo : trova il valore minimo della colonna (ad esempio, in ordine alfabetico o per data)
  • sum : calcola la somma dei valori di colonna in un gruppo

4. Proiezioni di ibernazione

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.

È importante notare che Hibernate ha deprecato la sua API Criteria dalla versione 5.2 in poi a favore dell'API CriteriaQuery JPA . Ma questo è solo perché il team di Hibernate non ha il tempo di mantenere due API diverse, che praticamente fanno la stessa cosa, sincronizzate.

E, naturalmente, il codice utilizzato in questo articolo può essere trovato su GitHub.