1. Gambaran keseluruhan
Spring Data menyediakan banyak cara untuk menentukan pertanyaan yang dapat kita laksanakan. Salah satunya ialah anotasi @Query .
Dalam tutorial ini, kami akan menunjukkan cara menggunakan anotasi @Query di JPA Spring Data untuk melaksanakan pertanyaan JPQL dan SQL asli.
Kami juga akan menunjukkan cara membina pertanyaan dinamik apabila anotasi @Query tidak mencukupi.
2. Pilih Pertanyaan
Untuk menentukan SQL yang akan dijalankan untuk kaedah repositori Spring Data, kita dapat memberi anotasi kaedah dengan anotasi @Query - atribut nilainya berisi JPQL atau SQL untuk dilaksanakan.
The @query anotasi diberi keutamaan berbanding pertanyaan yang bernama dan dijelaskan dengan @NamedQuery atau ditakrifkan dalam orm.xml fail.
Ini adalah pendekatan yang baik untuk meletakkan definisi pertanyaan tepat di atas kaedah di dalam repositori dan bukan di dalam model domain kami seperti pertanyaan yang dinamakan. Repositori bertanggungjawab untuk kegigihan, jadi tempat yang lebih baik untuk menyimpan definisi ini.
2.1. JPQL
Secara lalai, definisi pertanyaan menggunakan JPQL.
Mari lihat kaedah repositori mudah yang mengembalikan entiti Pengguna aktif dari pangkalan data:
@Query("SELECT u FROM User u WHERE u.status = 1") Collection findAllActiveUsers();
2.2. Orang Asli
Kita juga boleh menggunakan SQL asli untuk menentukan pertanyaan kita. Apa yang perlu kita lakukan adalah menetapkan nilai nativeQuery atribut untuk benar dan menentukan Pertanyaan SQL asli dalam nilai sifat anotasi:
@Query( value = "SELECT * FROM USERS u WHERE u.status = 1", nativeQuery = true) Collection findAllActiveUsersNative();
3. Tentukan Pesanan dalam Pertanyaan
Kita boleh meneruskan parameter tambahan jenis Sort ke deklarasi kaedah Spring Data yang mempunyai anotasi @Query . Ia akan diterjemahkan ke dalam klausa ORDER BY yang dihantar ke pangkalan data.
3.1. Menyusun Kaedah JPA yang Disediakan dan Berasal
Untuk kaedah yang kita keluar dari kotak seperti findAll (Sort) atau kaedah yang dihasilkan dengan menghuraikan tandatangan kaedah, kita hanya dapat menggunakan sifat objek untuk menentukan jenis kita :
userRepository.findAll(new Sort(Sort.Direction.ASC, "name"));
Sekarang bayangkan bahawa kita ingin menyusun mengikut panjang nama nama:
userRepository.findAll(new Sort("LENGTH(name)"));
Apabila kami melaksanakan kod di atas, kami akan menerima pengecualian:
org.springframework.data.mapping.PropertyReferenceException: Tiada harta PANJANG (nama) dijumpai untuk jenis Pengguna!
3.2. JPQL
Apabila kita menggunakan JPQL untuk definisi pertanyaan, maka Spring Data dapat menangani penyortiran tanpa masalah - yang harus kita lakukan adalah menambahkan parameter kaedah jenis Susun :
@Query(value = "SELECT u FROM User u") List findAllUsers(Sort sort);
Kita boleh memanggil kaedah ini dan lulus Jenis parameter, yang akan memerintahkan hasilnya dengan yang nama harta Pengguna objek:
userRepository.findAllUsers(new Sort("name"));
Dan kerana kami menggunakan anotasi @Query , kami dapat menggunakan kaedah yang sama untuk mendapatkan senarai Pengguna yang disusun mengikut panjang nama mereka:
userRepository.findAllUsers(JpaSort.unsafe("LENGTH(name)"));
Sangat penting untuk kita menggunakan JpaSort.unsafe () untuk membuat contoh objek Sort .
Apabila kita menggunakan:
new Sort("LENGTH(name)");
maka kita akan menerima pengecualian yang sama seperti yang kita lihat di atas untuk kaedah findAll () .
Apabila Spring Data mendapati yang tidak selamat Jenis agar satu kaedah yang menggunakan @query anotasi, maka ia hanya Melampirkan fasal jenis kepada pertanyaan - ia melangkau memeriksa sama ada harta itu kepada ditapis oleh tergolong dalam model domain.
3.3. Orang Asli
Apabila @query anotasi menggunakan SQL native speakers, maka ia tidak mungkin untuk menentukan Jenis .
Sekiranya berlaku, kami akan menerima pengecualian:
org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: Tidak dapat menggunakan pertanyaan asli dengan penyortiran dan / atau penomboran dinamik
Seperti kata pengecualian, jenis ini tidak disokong untuk pertanyaan asli. Mesej ralat memberi kita petunjuk bahawa penomboran akan menyebabkan pengecualian juga.
Namun, ada jalan penyelesaian yang membolehkan penomboran, dan kami akan membahasnya di bahagian seterusnya.
4. Penomboran
Penomboran membolehkan kita mengembalikan hanya sebahagian daripada keseluruhan hasil dalam Halaman . Ini berguna, misalnya, semasa menavigasi beberapa halaman data di halaman web.
Kelebihan lain dari penomboran adalah bahawa jumlah data yang dihantar dari pelayan ke pelanggan diminimumkan. Dengan mengirimkan kepingan data yang lebih kecil, secara umum kita dapat melihat peningkatan dalam prestasi.
4.1. JPQL
Menggunakan penomboran dalam definisi pertanyaan JPQL adalah mudah:
@Query(value = "SELECT u FROM User u ORDER BY id") Page findAllUsersWithPagination(Pageable pageable);
Kita dapat melewati parameter PageRequest untuk mendapatkan halaman data.
Penomboran juga disokong untuk pertanyaan asli tetapi memerlukan sedikit kerja tambahan.
4.2. Orang Asli
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
Dalam artikel ini, kami membahas beberapa cara untuk menentukan pertanyaan dalam kaedah repositori Spring Data JPA menggunakan anotasi @Query .
Kami juga belajar bagaimana menerapkan repositori khusus dan membuat pertanyaan dinamik.
Seperti biasa, contoh kod lengkap yang digunakan dalam artikel ini terdapat di GitHub.