JPA / Hibernate-Projektionen

1. Übersicht

In diesem Lernprogramm erfahren Sie, wie Sie Entitätseigenschaften mithilfe von JPA und Hibernate projizieren .

2. Die Entität

Schauen wir uns zunächst die Entität an, die wir in diesem Artikel verwenden werden:

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

Dies ist eine einfache Entitätsklasse, die ein Produkt mit verschiedenen Eigenschaften darstellt.

3. JPA-Projektionen

Obwohl die JPA-Spezifikation Projektionen nicht explizit erwähnt, gibt es viele Fälle, in denen wir sie im Konzept finden.

In der Regel verfügt eine JPQL-Abfrage über eine Kandidatenentitätsklasse. Die Abfrage erstellt bei der Ausführung Objekte der Kandidatenklasse und füllt alle ihre Eigenschaften anhand der abgerufenen Daten.

Es ist jedoch möglich, eine Teilmenge der Eigenschaften der Entität oder eine Projektion von Spaltendaten abzurufen .

Neben Spaltendaten können wir auch die Ergebnisse von Gruppierungsfunktionen projizieren.

3.1. Einspaltige Projektionen

Nehmen wir an, wir möchten die Namen aller Produkte auflisten. In JPQL können wir dies tun, indem wir nur den Namen in die select- Klausel aufnehmen:

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

Oder wir können dasselbe mit CriteriaBuilder tun :

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

Weil wir eine einzelne Säule projizieren , die vom Typ sein geschieht String , erwarten wir eine Liste von bekommen String s im Ergebnis. Daher haben wir die Kandidatenklasse in der Methode createQuery () als String angegeben .

Da wir auf eine einzelne Eigenschaft projizieren möchten, haben wir die Query.select () -Methode verwendet. Hier geht es darum, welche Eigenschaft wir möchten. In unserem Fall verwenden wir also die Eigenschaft name aus unserer Produkteinheit .

Schauen wir uns nun eine Beispielausgabe an, die durch die beiden oben genannten Abfragen generiert wurde:

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

Beachten Sie, dass die Abfrage eine Liste mit langen Objekten zurückgegeben hätte , wenn wir die Eigenschaft id in der Projektion anstelle von name verwendet hätten .

3.2. Mehrspaltige Projektionen

Um mit JPQL auf mehrere Spalten zu projizieren, müssen wir nur alle erforderlichen Spalten zur select- Klausel hinzufügen :

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

Bei Verwendung eines CriteriaBuilder müssen wir die Dinge jedoch etwas anders machen:

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

Hier haben wir die Methode multiselect () anstelle von select () verwendet . Mit dieser Methode können wir mehrere Elemente angeben, die ausgewählt werden sollen.

Eine weitere wichtige Änderung ist die Verwendung von Object [] . Wenn wir mehrere Elemente auswählen, gibt die Abfrage ein Objektarray mit einem Wert für jedes projizierte Element zurück. Dies ist auch bei JPQL der Fall.

Mal sehen, wie die Daten aussehen, wenn wir sie drucken:

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

Wie wir sehen können, ist die Verarbeitung der zurückgegebenen Daten etwas umständlich. Glücklicherweise können wir JPA dazu bringen, diese Daten in eine benutzerdefinierte Klasse zu füllen.

Wir können auch CriteriaBuilder.tuple () oder CriteriaBuilder.construct () verwenden , um die Ergebnisse als Liste von Tuple- Objekten bzw. Objekten einer benutzerdefinierten Klasse abzurufen.

3.3. Projizieren von Aggregatfunktionen

Abgesehen von Spaltendaten möchten wir manchmal die Daten gruppieren und aggregierte Funktionen wie Anzahl und Durchschnitt verwenden.

Angenommen, wir möchten die Anzahl der Produkte in jeder Kategorie ermitteln. Wir können dies mit der Aggregatfunktion count () in JPQL tun :

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

Oder wir können CriteriaBuilder verwenden :

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

Hier haben wir verwendet CriteriaBuilder ‚s count () Methode.

Wenn Sie eine der oben genannten Methoden verwenden, wird eine Liste der Objektarrays erstellt:

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

Neben count () bietet CriteriaBuilder verschiedene andere Aggregatfunktionen:

  • Durchschnitt - Berechnet den Durchschnittswert für eine Spalte in einer Gruppe
  • max - Berechnet den Maximalwert für eine Spalte in einer Gruppe
  • min - Berechnet den Mindestwert für eine Spalte in einer Gruppe
  • Am wenigsten - Findet den kleinsten Spaltenwert (z. B. alphabetisch oder nach Datum).
  • sum - Berechnet die Summe der Spaltenwerte in einer Gruppe

4. Projektionen im Ruhezustand

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.

Es ist wichtig zu beachten, dass Hibernate seine Kriterien-API ab Version 5.2 zugunsten der JPA CriteriaQuery-API abgelehnt hat . Dies liegt jedoch nur daran, dass das Hibernate-Team nicht die Zeit hat, zwei verschiedene APIs, die so ziemlich dasselbe tun, synchron zu halten.

Und natürlich ist der in diesem Artikel verwendete Code auf GitHub zu finden.