- Übersicht
- Weiterführende Literatur:
- Abgeleitete Abfragemethoden in Spring Data JPA-Repositorys
- Spring Data JPA @Modifying Annotation
- Select Query
- 2.1. JPQL
- 2.2. Native
- Reihenfolge definieren in einer Abfrage
- 3.1. Sortieren nach von JPA bereitgestellten und abgeleiteten Methoden
- 3.2. JPQL
- 3.3. Native
- Paginierung
- 4.1. JPQL
- 4.2. Native
- 4.3. Spring Data JPA-Versionen vor 2.0.4
- Indizierte Abfrageparameter
- 5.1. JPQL
- 5.2. Native
- Benannte Parameter
- 6.1. JPQL
- 6.2. Native
- 7. Sammlungsparameter
- Abfragen mit @Modifying aktualisieren
- 8.1. JPQL
- 8.2. Native
- 8.3. Inserts
- Dynamische Abfrage
- 9.1. Beispiel einer dynamischen Abfrage
- 9.2. Benutzerdefinierte Repositorys und die JPA Criteria API
- 9.3. Erweitern des vorhandenen Repositorys
- 9.4. Mit dem Repository
- Fazit
Ü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
Spring Data JPA @Modifying Annotation
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.