Spring Data JPA @Query

Übersicht

Spring Data bietet viele Möglichkeiten, eine Abfrage zu definieren, die wir ausführen können. Eine davon ist die Annotation @Query .

In diesem Tutorial zeigen wir, wie Sie die Annotation @Query in Spring Data JPA verwenden, um sowohl JPQL- als auch native SQL-Abfragen auszuführen.

Wir zeigen auch, wie Sie eine dynamische Abfrage erstellen, wenn die Annotation @Query nicht ausreicht.

Weiterführende Literatur:

Abgeleitete Abfragemethoden in Spring Data JPA-Repositorys

Erkunden Sie den Mechanismus zur Ableitung von Abfragen in Spring Data JPA.
Lesen Sie mehr →

Spring Data JPA @Modifying Annotation

Erstellen Sie DML- und DDL-Abfragen in Spring Data JPA, indem Sie die Annotationen @Query und @Modifying kombinieren
Weiterlesen →

Select Query

Um SQL zu definieren, das für eine Spring Data Repository—Methode ausgeführt werden soll, können wir die Methode mit der @Query Annotation @Query – ihr value Attribut enthält die auszuführende JPQL oder SQL.

Die Annotation @Query hat Vorrang vor benannten Abfragen, die mit @NamedQuery annotiert oder in einem orm definiert sind.xml-Datei.

Es ist ein guter Ansatz, eine Abfragedefinition direkt über der Methode im Repository und nicht in unserem Domänenmodell als benannte Abfragen zu platzieren. Das Repository ist für die Persistenz verantwortlich, daher ist es ein besserer Ort, um diese Definitionen zu speichern.

2.1. JPQL

Standardmäßig verwendet die Abfragedefinition JPQL.

Schauen wir uns eine einfache Repository-Methode an, die aktive Benutzerentitäten aus der Datenbank zurückgibt:

@Query("SELECT u FROM User u WHERE u.status = 1")Collection<User> findAllActiveUsers();

2.2. Native

Wir können auch native SQL verwenden, um unsere Abfrage zu definieren. Wir müssen lediglich den Wert des Attributs nativeQuery auf true setzen und die native SQL-Abfrage im Attribut value der Annotation definieren:

@Query( value = "SELECT * FROM USERS u WHERE u.status = 1", nativeQuery = true)Collection<User> findAllActiveUsersNative();

Reihenfolge definieren in einer Abfrage

Wir können einen zusätzlichen Parameter vom Typ Sort an eine Spring Data Methodendeklaration übergeben, die die Annotation @Query . Es wird in die ORDER BY-Klausel übersetzt, die an die Datenbank übergeben wird.

3.1. Sortieren nach von JPA bereitgestellten und abgeleiteten Methoden

Für die Methoden, die wir standardmäßig erhalten, wie findAll(Sort) oder diejenigen, die durch Analysieren von Methodensignaturen generiert werden, können wir nur Objekteigenschaften verwenden, um unsere Sortierung zu definieren:

userRepository.findAll(Sort.by(Sort.Direction.ASC, "name"));

Stellen Sie sich nun vor, wir möchten nach der Länge einer Namenseigenschaft sortieren:

userRepository.findAll(Sort.by("LENGTH(name)"));

Wenn wir den obigen Code ausführen, erhalten wir eine Ausnahme:

org.springframework.Daten.Mapping.PropertyReferenceException: Für den Typ User wurde keine Eigenschaftslänge (name) gefunden!

3.2. JPQL

Wenn wir JPQL für eine Abfragedefinition verwenden, können Spring Data die Sortierung problemlos verarbeiten – wir müssen lediglich einen Methodenparameter vom Typ Sort hinzufügen:

@Query(value = "SELECT u FROM User u")List<User> findAllUsers(Sort sort);

Wir können diese Methode aufrufen und einen Sortierparameter übergeben, der das Ergebnis nach der Eigenschaft name des Benutzerobjekts sortiert:

userRepository.findAllUsers(Sort.by("name"));

Und da wir die Annotation @Query verwendet haben, können wir dieselbe Methode verwenden, um die sortierte Liste der Benutzer nach der Länge ihrer Namen abzurufen:

userRepository.findAllUsers(JpaSort.unsafe("LENGTH(name)"));

Es ist wichtig, dass wir JpaSort verwenden.unsafe() , um eine Sortierobjektinstanz zu erstellen.

Wenn wir verwenden:

Sort.by("LENGTH(name)");

dann erhalten wir genau die gleiche Ausnahme wie oben für die findAll() -Methode.

Wenn Spring Data die unsichere Sortierreihenfolge für eine Methode ermittelt, die die Annotation @Query verwendet, wird nur die sort-Klausel an die Abfrage angehängt.

3.3. Native

Wenn die Annotation @Query natives SQL verwendet, ist es nicht möglich, eine Sortierung zu definieren.

Wenn wir dies tun, erhalten wir eine Ausnahme:

org.springframework.Daten.jpa.Repository.Abfrage.InvalidJpaQueryMethodException: Native Abfragen mit dynamischer Sortierung und / oder Paginierung können nicht verwendet werden

Wie in der Ausnahme angegeben, wird die Sortierung für native Abfragen nicht unterstützt. Die Fehlermeldung gibt uns einen Hinweis, dass die Paginierung auch eine Ausnahme verursacht.

Es gibt jedoch eine Problemumgehung, die die Paginierung aktiviert, und wir werden sie im nächsten Abschnitt behandeln.

Paginierung

Paginierung ermöglicht es uns, nur eine Teilmenge eines ganzen Ergebnisses auf einer Seite zurückzugeben. Dies ist beispielsweise nützlich, wenn Sie auf einer Webseite durch mehrere Datenseiten navigieren.

Ein weiterer Vorteil der Paginierung besteht darin, dass die vom Server zum Client gesendete Datenmenge minimiert wird. Durch das Senden kleinerer Daten können wir im Allgemeinen eine Verbesserung der Leistung feststellen.

4.1. JPQL

Die Verwendung der Paginierung in der JPQL-Abfragedefinition ist unkompliziert:

@Query(value = "SELECT u FROM User u ORDER BY id")Page<User> findAllUsersWithPagination(Pageable pageable);

Wir können einen PageRequest-Parameter übergeben, um eine Seite mit Daten abzurufen.

Paginierung wird auch für native Abfragen unterstützt, erfordert jedoch ein wenig zusätzliche Arbeit.

4.2. Native

Wir können die Paginierung für native Abfragen aktivieren, indem wir ein zusätzliches Attribut countQuery deklarieren.

Dies definiert die auszuführende SQL, um die Anzahl der Zeilen im gesamten Ergebnis zu zählen:

@Query( value = "SELECT * FROM Users ORDER BY id", countQuery = "SELECT count(*) FROM Users", nativeQuery = true)Page<User> findAllUsersWithPagination(Pageable pageable);

4.3. Spring Data JPA-Versionen vor 2.0.4

Die obige Lösung für native Abfragen funktioniert einwandfrei für Spring Data JPA-Versionen 2.0.4 und höher.

Wenn wir vor dieser Version versuchen, eine solche Abfrage auszuführen, erhalten wir dieselbe Ausnahme, die wir im vorherigen Abschnitt zum Sortieren beschrieben haben.

Wir können dies überwinden, indem wir einen zusätzlichen Parameter für die Paginierung in unserer Abfrage hinzufügen:

@Query( value = "SELECT * FROM Users ORDER BY id \n-- #pageable\n", countQuery = "SELECT count(*) FROM Users", nativeQuery = true)Page<User> findAllUsersWithPagination(Pageable pageable);

Im obigen Beispiel fügen wir „\n– #pageable\n“ als Platzhalter für den Paginierungsparameter hinzu. Dadurch wird Spring Data JPA mitgeteilt, wie die Abfrage analysiert und der auslagerbare Parameter eingefügt werden soll. Diese Lösung funktioniert für die H2-Datenbank.

Wir haben behandelt, wie Sie einfache Select-Abfragen über JPQL und native SQL erstellen. Als nächstes zeigen wir, wie Sie zusätzliche Parameter definieren.

Indizierte Abfrageparameter

Es gibt zwei Möglichkeiten, Methodenparameter an unsere Abfrage zu übergeben: indizierte und benannte Parameter.

In diesem Abschnitt behandeln wir indizierte Parameter.

5.1. JPQL

Für indizierte Parameter in JPQL übergeben Spring Data Methodenparameter in der gleichen Reihenfolge an die Abfrage, in der sie in der Methodendeklaration angezeigt werden:

@Query("SELECT u FROM User u WHERE u.status = ?1")User findUserByStatus(Integer status);@Query("SELECT u FROM User u WHERE u.status = ?1 and u.name = ?2")User findUserByStatusAndName(Integer status, String name);

Für die obigen Abfragen wird der Parameter status method dem Parameter query mit Index 1 und der Parameter name method dem Parameter query mit Index 2 zugewiesen.

5.2. Native

Indizierte Parameter für die nativen Abfragen funktionieren genauso wie für JPQL:

@Query( value = "SELECT * FROM Users u WHERE u.status = ?1", nativeQuery = true)User findUserByStatusNative(Integer status);

Im nächsten Abschnitt zeigen wir einen anderen Ansatz: Übergeben von Parametern über den Namen.

Benannte Parameter

Wir können auch Methodenparameter mit benannten Parametern an die Abfrage übergeben. Wir definieren diese mithilfe der Annotation @Param in unserer Repository-Methodendeklaration.

Jeder mit @Param annotierte Parameter muss eine Wertzeichenfolge haben, die mit dem entsprechenden JPQL- oder SQL-Abfrageparameternamen übereinstimmt. Eine Abfrage mit benannten Parametern ist einfacher zu lesen und weniger fehleranfällig, falls die Abfrage umgestaltet werden muss.

6.1. JPQL

Wie oben erwähnt, verwenden wir die Annotation @Param in der Methodendeklaration, um Parameter, die in JPQL nach Namen definiert sind, mit Parametern aus der Methodendeklaration abzugleichen:

@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")User findUserByStatusAndNameNamedParams( @Param("status") Integer status, @Param("name") String name);

Beachten Sie, dass wir im obigen Beispiel unsere SQL-Abfrage- und Methodenparameter so definiert haben, dass sie dieselben Namen haben, dies ist jedoch nicht erforderlich, solange die Wertezeichenfolgen identisch sind:

@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")User findUserByUserStatusAndUserName(@Param("status") Integer userStatus, @Param("name") String userName);

6.2. Native

Für die native Abfragedefinition gibt es im Vergleich zu JPQL keinen Unterschied darin, wie wir einen Parameter über den Namen an die Abfrage übergeben — wir verwenden die Annotation @Param:

@Query(value = "SELECT * FROM Users u WHERE u.status = :status and u.name = :name", nativeQuery = true)User findUserByStatusAndNameNamedParamsNative( @Param("status") Integer status, @Param("name") String name);

7. Sammlungsparameter

Betrachten wir den Fall, wenn die where-Klausel unserer JPQL- oder SQL-Abfrage das Schlüsselwort IN (oder NOT IN) enthält:

SELECT u FROM User u WHERE u.name IN :names

In diesem Fall können wir eine Abfragemethode definieren, die Collection als Parameter verwendet:

@Query(value = "SELECT u FROM User u WHERE u.name IN :names")List<User> findUserByNameList(@Param("names") Collection<String> names);

Da der Parameter eine Sammlung ist, kann er mit List , HashSet usw. verwendet werden.

Als nächstes zeigen wir, wie Daten mit der Annotation @Modifying geändert werden.

Abfragen mit @Modifying aktualisieren

Wir können die Annotation @Query verwenden, um den Status der Datenbank zu ändern, indem wir der Repository-Methode auch die Annotation @Modifying hinzufügen.

8.1. JPQL

Die Repository—Methode, mit der die Daten geändert werden, weist im Vergleich zur select-Abfrage zwei Unterschiede auf: Sie enthält die Annotation @Modifying und die JPQL-Abfrage verwendet natürlich update anstelle von select:

@Modifying@Query("update User u set u.status = :status where u.name = :name")int updateUserSetStatusForName(@Param("status") Integer status, @Param("name") String name);

Der Rückgabewert definiert, wie viele Zeilen die Ausführung der Abfrage aktualisiert. Sowohl indizierte als auch benannte Parameter können in Aktualisierungsabfragen verwendet werden.

8.2. Native

Wir können den Status der Datenbank auch mit einer nativen Abfrage ändern. Wir müssen nur die Annotation @Modifying hinzufügen:

@Modifying@Query(value = "update Users u set u.status = ? where u.name = ?", nativeQuery = true)int updateUserSetStatusForNameNative(Integer status, String name);

8.3. Inserts

Um eine Insert-Operation auszuführen, müssen wir sowohl @Insert anwenden als auch eine native Abfrage verwenden, da INSERT nicht Teil der JPA-Schnittstelle ist:

@Modifying@Query( value = "insert into Users (name, age, email, status) values (:name, :age, :email, :status)", nativeQuery = true)void insertUser(@Param("name") String name, @Param("age") Integer age, @Param("status") Integer status, @Param("email") String email);

Dynamische Abfrage

Häufig müssen SQL-Anweisungen basierend auf Bedingungen oder Datensätzen erstellt werden, deren Werte nur zur Laufzeit bekannt sind. Und in diesen Fällen können wir nicht nur eine statische Abfrage verwenden.

9.1. Beispiel einer dynamischen Abfrage

Stellen wir uns zum Beispiel eine Situation vor, in der wir alle Benutzer auswählen müssen, deren E-Mail-Adresse einer zur Laufzeit definierten Menge entspricht – email1, email2, …, emailn:

SELECT u FROM User u WHERE u.email LIKE '%email1%' or u.email LIKE '%email2%' ... or u.email LIKE '%emailn%'

Da die Menge dynamisch erstellt wird, können wir zur Kompilierungszeit nicht wissen, wie viele LIKE Klauseln hinzugefügt werden sollen.

In diesem Fall können wir nicht einfach die Annotation @Query verwenden, da wir keine statische SQL-Anweisung bereitstellen können.

Stattdessen können wir durch die Implementierung eines benutzerdefinierten zusammengesetzten Repositorys die JpaRepository-Basisfunktionalität erweitern und unsere eigene Logik zum Erstellen einer dynamischen Abfrage bereitstellen. Schauen wir uns an, wie das geht.

9.2. Benutzerdefinierte Repositorys und die JPA Criteria API

Zum Glück bietet Spring eine Möglichkeit, das Basis-Repository durch die Verwendung benutzerdefinierter Fragmentschnittstellen zu erweitern. Wir können sie dann miteinander verknüpfen, um ein zusammengesetztes Repository zu erstellen.

Wir beginnen mit der Erstellung einer benutzerdefinierten Fragmentschnittstelle:

public interface UserRepositoryCustom { List<User> findUserByEmails(Set<String> emails);}

Und dann werden wir es umsetzen:

public class UserRepositoryCustomImpl implements UserRepositoryCustom { @PersistenceContext private EntityManager entityManager; @Override public List<User> findUserByEmails(Set<String> emails) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<User> query = cb.createQuery(User.class); Root<User> user = query.from(User.class); Path<String> emailPath = user.get("email"); List<Predicate> predicates = new ArrayList<>(); for (String email : emails) { predicates.add(cb.like(emailPath, email)); } query.select(user) .where(cb.or(predicates.toArray(new Predicate))); return entityManager.createQuery(query) .getResultList(); }}

Wie oben gezeigt, haben wir die JPA Criteria API genutzt, um unsere dynamische Abfrage zu erstellen.

Außerdem müssen wir sicherstellen, dass der Impl-Postfix im Klassennamen enthalten ist. Spring durchsucht die UserRepositoryCustom-Implementierung als UserRepositoryCustomImpl . Da Fragmente selbst keine Repositorys sind, verlässt sich Spring auf diesen Mechanismus, um die Fragmentimplementierung zu finden.

9.3. Erweitern des vorhandenen Repositorys

Beachten Sie, dass sich alle Abfragemethoden von Abschnitt 2 bis Abschnitt 7 im UserRepository befinden.

Jetzt integrieren wir unser Fragment, indem wir die neue Schnittstelle im UserRepository erweitern:

public interface UserRepository extends JpaRepository<User, Integer>, UserRepositoryCustom { // query methods from section 2 - section 7}

9.4. Mit dem Repository

Und schließlich können wir unsere dynamische Abfragemethode aufrufen:

Set<String> emails = new HashSet<>();// filling the set with any number of itemsuserRepository.findUserByEmails(emails);

Wir haben erfolgreich ein zusammengesetztes Repository erstellt und unsere benutzerdefinierte Methode aufgerufen.

Fazit

In diesem Artikel haben wir verschiedene Möglichkeiten zum Definieren von Abfragen in Spring Data JPA-Repository-Methoden mithilfe der Annotation @Query behandelt.

Wir haben auch gelernt, wie man ein benutzerdefiniertes Repository implementiert und eine dynamische Abfrage erstellt.

Wie immer sind die vollständigen Codebeispiele, die in diesem Artikel verwendet werden, auf GitHub verfügbar.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.