Spring Data JPA @Query

1. Ü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 Lernprogramm wird gezeigt, wie die Annotation @Query in Spring Data JPA verwendet wird, 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.

2. Wählen Sie Abfrage

Um SQL zu definieren , für eine Spring Data Repository Methode auszuführen, können wir die Methode mit der mit Anmerkungen versehen @Query Anmerkung - sein Wert Attribut enthält die JPQL oder SQL auszuführen.

Die @Query Anmerkung hat Vorrang vor benannten Abfragen, die mit Anmerkungen versehen werden @NamedQuery oder in einer definierten orm.xml Datei.

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 aktive Benutzerentitä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 natives SQL verwenden, um unsere Abfrage zu definieren. Alles, was wir tun müssen, ist , den Wert des nativeQuery- Attributs auf true zu setzen und die native SQL-Abfrage im value- Attribut der Annotation zu definieren:

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

3. Definieren Sie die Reihenfolge in einer Abfrage

Wir können einen zusätzlichen Parameter vom Typ Sort an eine Spring Data-Methodendeklaration mit der Annotation @Query übergeben . 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 sofort einsatzbereiten Methoden wie findAll (Sort) oder diejenigen, die durch das Analysieren von Methodensignaturen generiert werden, können wir nur Objekteigenschaften verwenden, um unsere Sortierung zu definieren :

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: Keine Eigenschaft LENGTH (Name) für Typ User gefunden!

3.2. JPQL

Wenn wir JPQL für eine Abfragedefinition verwenden, kann Spring Data problemlos sortieren. Wir müssen lediglich einen Methodenparameter vom Typ Sortieren hinzufügen :

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

Wir können diese Methode aufrufen und einen Sort- Parameter übergeben, der das Ergebnis nach der name- Eigenschaft des User- Objekts ordnet :

userRepository.findAllUsers(new Sort("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.unsafe () verwenden , um eine Sort- Objektinstanz zu erstellen .

Wenn wir verwenden:

new Sort("LENGTH(name)"); 

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

Wenn Spring Data die unsichere entdeckt sortieren , um für eine Methode, die verwendet @Query Anmerkung, dann hängt es nur die Sortier - Klausel der Abfrage - es überspringt die Überprüfung , ob die Eigenschaft zu sortieren nach auf dem Domänenmodell gehört.

3.3. Einheimisch

Wenn die Annotation @Query natives SQL verwendet, kann keine Sortierung definiert werden .

In diesem Fall erhalten wir eine Ausnahme:

org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: Native Abfragen mit dynamischer Sortierung und / oder Paginierung können nicht verwendet werden

Wie die Ausnahme besagt, 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 sie im nächsten Abschnitt behandeln.

4. Paginierung

Durch die Paginierung können wir nur eine Teilmenge eines gesamten Ergebnisses auf einer Seite zurückgeben . Dies ist beispielsweise nützlich, wenn Sie durch mehrere Datenseiten auf einer Webseite navigieren.

Ein weiterer Vorteil der Paginierung besteht darin, dass die vom Server an den Client gesendete Datenmenge minimiert wird. Durch das Senden kleinerer Daten können wir im Allgemeinen eine Leistungsverbesserung 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 findAllUsersWithPagination(Pageable pageable); 

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

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

4.2. Einheimisch

We can enable pagination for native queries by declaring an additional attribute countQuery.

This defines the SQL to execute to count the number of rows in the whole result:

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

4.3. Spring Data JPA Versions Prior to 2.0.4

The above solution for native queries works fine for Spring Data JPA versions 2.0.4 and later.

Prior to that version, when we try to execute such a query, we'll receive the same exception we described in the previous section on sorting.

We can overcome this by adding an additional parameter for pagination inside our query:

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

In the above example, we add “\n– #pageable\n” as the placeholder for the pagination parameter. This tells Spring Data JPA how to parse the query and inject the pageable parameter. This solution works for the H2 database.

We've covered how to create simple select queries via JPQL and native SQL. Next, we'll show how to define additional parameters.

5. Indexed Query Parameters

There are two possible ways that we can pass method parameters to our query: indexed and named parameters.

In this section, we'll cover indexed parameters.

5.1. JPQL

For indexed parameters in JPQL, Spring Data will pass 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); 

For the above queries, the status method parameter will be assigned to the query parameter with index 1, and the name method parameter will be assigned to the query parameter with index 2.

5.2. Native

Indexed parameters for the native queries work exactly in the same way as for JPQL:

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

In the next section, we'll show a different approach: passing parameters via name.

6. Named Parameters

We can also pass method parameters to the query using named parameters. We define these using the @Param annotation inside our repository method declaration.

Each parameter annotated with @Param must have a value string matching the corresponding JPQL or SQL query parameter name. A query with named parameters is easier to read and is less error-prone in case the query needs to be refactored.

6.1. JPQL

As mentioned above, we use the @Param annotation in the method declaration to match parameters defined by name in JPQL with parameters from the method declaration:

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

Note that in the above example, we defined our SQL query and method parameters to have the same names, but it's not required as long as the value strings are the same:

@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

For the native query definition, there is no difference in how we pass a parameter via the name to the query in comparison to JPQL — we use the @Param annotation:

@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. Collection Parameter

Let's consider the case when the where clause of our JPQL or SQL query contains the IN (or NOT IN) keyword:

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

In this case, we can define a query method that takes Collection as a parameter:

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

As the parameter is a Collection, it can be used with List, HashSet, etc.

Next, we'll show how to modify data with the @Modifying annotation.

8. Update Queries With @Modifying

We can use the @Query annotation to modify the state of the database by also adding the @Modifying annotation to the repository method.

8.1. JPQL

The repository method that modifies the data has two differences in comparison to the select query — it has the @Modifying annotation and, of course, the JPQL query uses update instead of select:

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

The return value defines how many rows the execution of the query updated. Both indexed and named parameters can be used inside update queries.

8.2. Native

We can modify the state of the database also with a native query. We just need to add the @Modifying annotation:

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

8.3. Inserts

To perform an insert operation, we have to both apply @Modifying and use a native query since INSERT 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. Dynamic Query

Often, we'll encounter the need for building SQL statements based on conditions or data sets whose values are only known at runtime. And in those cases, we can't just use a static query.

9.1. Example of a Dynamic Query

For example, let's imagine a situation where we need to select all the users whose email is LIKE one from a set defined at runtime — email1, email2, …, emailn:

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

Since the set is dynamically constructed, we can't know at compile-time how many LIKE clauses to add.

In this case, we can't just use the @Query annotation since we can't provide a static SQL statement.

Instead, by implementing a custom composite repository, we can extend the base JpaRepository functionality and provide our own logic for building a dynamic query. Let's take a look at how to do this.

9.2. Custom Repositories and the JPA Criteria API

Luckily for us, Spring provides a way for extending the base repository through the use of custom fragment interfaces. We can then link them together to create a composite repository.

We'll start by creating a custom fragment interface:

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

And then we'll implement it:

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

As shown above, we leveraged the JPA Criteria API to build our dynamic query.

Also, we need to make sure to include the Impl postfix in the class name. Spring will search the UserRepositoryCustom implementation as UserRepositoryCustomImpl. Since fragments are not repositories by themselves, Spring relies on this mechanism to find the fragment implementation.

9.3. Extending the Existing Repository

Notice that all the query methods from section 2 through section 7 are in the UserRepository.

So, now we'll integrate our fragment by extending the new interface in the UserRepository:

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

9.4. Using the Repository

And finally, we can call our dynamic query method:

Set emails = new HashSet(); // filling the set with any number of items userRepository.findUserByEmails(emails); 

We've successfully created a composite repository and called our custom method.

10. Conclusion

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 Sie ein benutzerdefiniertes Repository implementieren und eine dynamische Abfrage erstellen.

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