Spring Data JPA @Query

Frühlingsdaten JPA @Query

1. Überblick

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 wirhow to use the @Query annotation in Spring Data JPA to execute both JPQL and native SQL queries.

Außerdem zeigen wir, wie eine dynamische Abfrage erstellt wird, wenn die Annotation von@Querynicht ausreicht.

Weitere Lektüre:

Abgeleitete Abfragemethoden in JPA-Repositorys für Frühjahrsdaten

Erforschen Sie den Abfrageableitungsmechanismus in Spring Data JPA.

Read more

Spring Data JPA @Modifying Annotation

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

Read more

2. Wählen Sie Abfrage

Um SQL für die Ausführung für eine Spring Data-Repository-Methode zu definieren, können wirannotate the method with the @Query annotation  — its value attribute contains the JPQL or SQL to execute. verwenden

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

Es ist ein guter Ansatz, eine Abfragedefinition direkt über der Methode im Repository und nicht als benannte Abfragen in unserem Domänenmodell 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 aktiveUser-Entitäten aus der Datenbank zurückgibt:

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

2.2. Einheimisch

Wir können auch native SQL verwenden, um unsere Abfrage zu definieren. Alles, was wir tun müssen, ist, den Wert des AttributsnativeQuery auftrue zu setzen und die native SQL-Abfrage im Attributvalue der Annotation zu definieren:

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

3. Reihenfolge in einer Abfrage definieren

Wir können einen zusätzlichen Parameter vom TypSort an eine Spring Data-Methodendeklaration übergeben, die die Annotation@Queryenthält. Es wird in dieORDER BY-Klausel übersetzt, die an die Datenbank übergeben wird.

3.1. Sortieren nach von JPA bereitgestellten und abgeleiteten Methoden

Für die Methoden erhalten wir sofort einsatzbereite wiefindAll(Sort) oder diejenigen, die durch das Analysieren von Methodensignaturen generiert werden,we can only use object properties to define our sort:

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

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

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

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

org.springframework.data.mapping.PropertyReferenceException: No property lENGTH(name) found for type User!

3.2. JPQL

When we use JPQL for a query definition, then Spring Data can handle sorting without any problem - Alles was wir tun müssen, ist einen Methodenparameter vom TypSort hinzuzufügen:

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

Wir können diese Methode aufrufen und einenSort-Parameter übergeben, der das Ergebnis nach dername-Eigenschaft desUser-Objekts ordnet:

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

Und weil wir die Annotation@Query verwendet haben,we can use the same method to get the sorted list of Users by the length of their names:

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

Es ist wichtig, dass wirJpaSort.unsafe() verwenden, um eineSort-Objektinstanz zu erstellen.

Wenn wir verwenden:

new Sort("LENGTH(name)");

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

Wenn Spring Data die unsichere Reihenfolge vonSortfür eine Methode erkennt, die die Annotation@Queryverwendet, hängt es einfach die Sortierklausel an die Abfrage an - es wird übersprungen, ob die zu sortierende Eigenschaft zum Domänenmodell gehört .

3.3. Einheimisch

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

In diesem Fall erhalten wir eine Ausnahme:

org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: Cannot use native queries with dynamic sorting and/or pagination

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 ermöglicht, und wir werden im nächsten Abschnitt darauf eingehen.

4. Seitennummerierung

Die Paginierung ermöglicht es uns, nur eine Teilmenge eines gesamten Ergebnisses inPage 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 Datenmenge, die vom Server zum Client gesendet wird, 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 einfach:

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

Wir können einenPageRequest-Parameter übergeben, um eine Datenseite zu erhalten. Die Paginierung wird auch für systemeigene Abfragen unterstützt, erfordert jedoch ein wenig zusätzliche Arbeit.

4.2. Einheimisch

Wir könnenenable pagination for native queries by declaring an additional attribute countQuery - 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 findAllUsersWithPagination(Pageable pageable);

4.3. Spring Data JPA-Versionen vor 2.0.4

Die obige Lösung für native Abfragen funktioniert problemlos für Spring Data JPA Version 2.0.4 und höher.

Wenn wir vor dieser Version versuchen, eine solche Abfrage auszuführen, erhalten wir eine Ausnahme - dieselbe, 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 unsere Abfrage einfügen:

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

”Als Platzhalter für den Paginierungsparameter. Auf diese Weise wird Spring Data JPA mitgeteilt, wie die Abfrage analysiert und der Parameter "Pageable" eingefügt werden soll. Diese Lösung funktioniert für die DatenbankH2.

Wir haben erläutert, wie Sie einfache Auswahlabfragen über JPQL und natives SQL erstellen. Als Nächstes zeigen wir, wie Sie zusätzliche Parameter definieren.

5. Indizierte Abfrageparameter

Es gibt zwei Möglichkeiten, Methodenparameter an unsere Abfrage zu übergeben. In diesem Abschnitt werden indizierte Parameter behandelt.

5.1. JPQL

Für indizierte Parameter in JPQL wird Spring Datapass method parameters to the query in the same order they appear in the method declaration:

@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 Methodenparameterstatusdem Abfrageparameter mit dem Index1, und der Methodenparameternamedem Abfrageparameter mit dem Index2 zugewiesen .

5.2. Einheimisch

Indizierte Parameter für die systemeigenen 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.

6. Benannte Parameter

Wir können auchpass method parameters to the query using named parameters.. Wir definieren diese mithilfe der Annotation@Param in unserer Repository-Methodendeklaration.

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

6.1. JPQL

Wie oben erwähnt, verwenden wir die Annotation@Param in der Methodendeklaration, um in JPQL namentlich definierte Parameter 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 jedoch nicht erforderlich ist, solange die Wertzeichenfolgen 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. Einheimisch

Bei der nativen Abfragedefinition gibt es im Vergleich zu JPQL keinen Unterschied, 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. Erfassungsparameter

Betrachten wir den Fall, in dem diewhere-Klausel unserer JPQL- oder SQL-Abfrage das SchlüsselwortIN (oderNOT IN) enthält:

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

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

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

Da der ParameterCollection ist, kann er mitList, HashSet usw. verwendet werden.

Als Nächstes zeigen wir, wie Sie Daten mit der Annotation @Modifyingändern.

8. Aktualisieren Sie Abfragen mit@Modifying

Wir könnenuse the @Query annotation to modify the state of the database by also adding the @Modifying annotation zur Repository-Methode.

8.1. JPQL

Die Repository-Methode, mit der die Daten geändert werden, weist im Vergleich zur Abfrageselectzwei Unterschiede auf: Sie enthält die Annotation@Modifying, und natürlich verwendet die JPQL-Abfrageupdate anstelle vonselect. s:

@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 hat. Sowohl indizierte als auch benannte Parameter können in Aktualisierungsabfragen verwendet werden.

8.2. Einheimisch

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

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

8.3. Beilagen

Um eine Einfügeoperation auszuführen, müssen wir sowohl@Modifying anwenden als auch eine native Abfrage verwenden, daINSERT is not a part of the JPA interface:

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

9. Dynamische Abfrage

Oft müssen wir SQL-Anweisungen basierend auf Bedingungen oder Datensätzen erstellen, deren Werte nur zur Laufzeit bekannt sind. 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, deren E-Mail-AdresseLIKEist, aus einem zur Laufzeit definierten Satz auswählen müssen -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 aufgebaut ist, können wir zur Kompilierungszeit nicht wissen, wie vieleLIKE-Klauseln hinzugefügt werden müssen.

In diesem Fallwe can’t just use the @Query annotation since we can’t provide a static SQL statement.

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

9.2. Benutzerdefinierte Repositorys und die JPA-Kriterien-API

Luckily for us, Spring provides a way for extending the base repository through the use of custom fragment interfaces. Wir können sie dann miteinander verknüpfen, um eincomposite repository zu erstellen.

Zunächst erstellen wir eine benutzerdefinierte Fragmentschnittstelle:

public interface UserRepositoryCustom {
    List findUserByEmails(Set emails);
}

Und dann werden wir es implementieren:

public class UserRepositoryCustomImpl implements UserRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List findUserByEmails(Set emails) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery query = cb.createQuery(User.class);
        Root user = query.from(User.class);

        Path emailPath = user.get("email");

        List predicates = new ArrayList<>();
        for (String email : emails) {
            predicates.add(cb.like(emailPath, email));
        }
        query.select(user)
            .where(cb.or(predicates.toArray(new Predicate[predicates.size()])));

        return entityManager.createQuery(query)
            .getResultList();
    }
}

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

Also, we need to make sure to include the Impl postfix in the class name. Spring durchsucht die Implementierung vonUserRepositoryCustomalsUserRepositoryCustomImpl. Da Fragmente keine eigenständigen Repositorys sind, verwendet Spring diesen Mechanismus, um die Fragmentimplementierung zu finden.

9.3. Erweitern des vorhandenen Repositorys

Beachten Sie, dass sich alle Abfragemethoden aus Abschnitt 2 - Abschnitt 7 inUserRepository.befinden. Nun integrieren wir unser Fragment, indem wir die neue Schnittstelle inUserRepository erweitern:

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

9.4. Verwenden des Repository

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

Set emails = new HashSet<>();
// filling the set with any number of items

userRepository.findUserByEmails(emails);

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

10. Fazit

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

Außerdem haben wir gelernt, wie ein benutzerdefiniertes Repository implementiert und eine dynamische Abfrage erstellt wird.

Wie immer sind die vollständigen Codebeispiele in diesem Tutorialover on Github verfügbar.