REST Query Language dengan Spring Data JPA dan Querydsl

Artikel ini adalah bagian dari serial: • REST Query Language dengan Kriteria Spring dan JPA

• Bahasa Kueri REST dengan Spesifikasi JPA Spring Data

• Bahasa Kueri REST dengan Spring Data JPA dan Querydsl (artikel saat ini) • Bahasa Kueri REST - Operasi Pencarian Lanjutan

• REST Query Language - Menerapkan ATAU Operasi

• REST Query Language dengan RSQL

• REST Query Language dengan Querydsl Web Support

1. Ikhtisar

Dalam tutorial ini, kami sedang membuat bahasa kueri untuk REST API menggunakan Spring Data JPA dan Querydsl .

Di dua artikel pertama dari seri ini, kami membuat fungsionalitas pencarian / pemfilteran yang sama menggunakan Kriteria JPA dan Spesifikasi JPA Data Musim Semi.

Jadi - mengapa bahasa kueri? Karena - untuk API yang cukup kompleks - mencari / memfilter sumber daya Anda dengan bidang yang sangat sederhana tidaklah cukup. Bahasa kueri lebih fleksibel , dan memungkinkan Anda untuk memfilter sumber daya yang Anda butuhkan.

2. Konfigurasi Querydsl

Pertama - mari kita lihat bagaimana mengkonfigurasi proyek kita untuk menggunakan Querydsl.

Kita perlu menambahkan dependensi berikut ke pom.xml :

 com.querydsl querydsl-apt 4.2.2   com.querydsl querydsl-jpa 4.2.2 

Kita juga perlu mengkonfigurasi APT - Alat pemrosesan anotasi - plugin sebagai berikut:

 com.mysema.maven apt-maven-plugin 1.1.3    process   target/generated-sources/java com.mysema.query.apt.jpa.JPAAnnotationProcessor    

Ini akan menghasilkan tipe-Q untuk entitas kita.

3. Entitas MyUser

Selanjutnya - mari kita lihat entitas " MyUser " yang akan kita gunakan di API Pencarian:

@Entity public class MyUser { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String firstName; private String lastName; private String email; private int age; }

4. Predikat Kustom dengan PathBuilder

Sekarang - mari buat Predikat khusus berdasarkan beberapa batasan arbitrer.

Kami menggunakan PathBuilder di sini daripada tipe-Q yang dibuat secara otomatis karena kami perlu membuat jalur secara dinamis untuk penggunaan yang lebih abstrak:

public class MyUserPredicate { private SearchCriteria criteria; public BooleanExpression getPredicate() { PathBuilder entityPath = new PathBuilder(MyUser.class, "user"); if (isNumeric(criteria.getValue().toString())) { NumberPath path = entityPath.getNumber(criteria.getKey(), Integer.class); int value = Integer.parseInt(criteria.getValue().toString()); switch (criteria.getOperation()) { case ":": return path.eq(value); case ">": return path.goe(value); case "<": return path.loe(value); } } else { StringPath path = entityPath.getString(criteria.getKey()); if (criteria.getOperation().equalsIgnoreCase(":")) { return path.containsIgnoreCase(criteria.getValue().toString()); } } return null; } }

Perhatikan bagaimana implementasi predikat secara umum menangani berbagai jenis operasi . Ini karena bahasa kueri menurut definisi adalah bahasa terbuka di mana Anda berpotensi dapat memfilter menurut bidang apa pun, menggunakan operasi yang didukung.

Untuk merepresentasikan kriteria pemfilteran terbuka semacam itu, kami menggunakan implementasi yang sederhana namun cukup fleksibel - Kriteria Pencarian :

public class SearchCriteria { private String key; private String operation; private Object value; }

The SearchCriteria memegang rincian yang kita butuhkan untuk mewakili kendala:

  • key : nama field - misalnya: firstName , age ,… dll
  • operasi : operasi - misalnya: Kesetaraan, kurang dari,… dll
  • nilai : nilai bidang - misalnya: john, 25,… dll

5. MyUserRepository

Sekarang - mari kita lihat MyUserRepository kami .

Kita membutuhkan MyUserRepository untuk memperluas QuerydslPredicateExecutor sehingga kita dapat menggunakan Predicates nanti untuk memfilter hasil pencarian:

public interface MyUserRepository extends JpaRepository, QuerydslPredicateExecutor, QuerydslBinderCustomizer { @Override default public void customize( QuerydslBindings bindings, QMyUser root) { bindings.bind(String.class) .first((SingleValueBinding) StringExpression::containsIgnoreCase); bindings.excluding(root.email); } }

Perhatikan bahwa di sini kita menggunakan tipe-Q yang dihasilkan untuk entitas MyUser , yang akan dinamai QMyUser.

6. Gabungkan Predikat

Berikutnya– mari kita lihat menggabungkan Predikat untuk menggunakan beberapa batasan dalam pemfilteran hasil.

Dalam contoh berikut - kami bekerja dengan pembuat - MyUserPredicatesBuilder - untuk menggabungkan Predikat :

public class MyUserPredicatesBuilder { private List params; public MyUserPredicatesBuilder() { params = new ArrayList(); } public MyUserPredicatesBuilder with( String key, String operation, Object value) { params.add(new SearchCriteria(key, operation, value)); return this; } public BooleanExpression build() { if (params.size() == 0) { return null; } List predicates = params.stream().map(param -> { MyUserPredicate predicate = new MyUserPredicate(param); return predicate.getPredicate(); }).filter(Objects::nonNull).collect(Collectors.toList()); BooleanExpression result = Expressions.asBoolean(true).isTrue(); for (BooleanExpression predicate : predicates) { result = result.and(predicate); } return result; } }

7. Uji Permintaan Pencarian

Selanjutnya - mari kita uji API Penelusuran kami.

Kami akan mulai dengan menginisialisasi database dengan beberapa pengguna - agar ini siap dan tersedia untuk pengujian:

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { PersistenceConfig.class }) @Transactional @Rollback public class JPAQuerydslIntegrationTest { @Autowired private MyUserRepository repo; private MyUser userJohn; private MyUser userTom; @Before public void init() { userJohn = new MyUser(); userJohn.setFirstName("John"); userJohn.setLastName("Doe"); userJohn.setEmail("[email protected]"); userJohn.setAge(22); repo.save(userJohn); userTom = new MyUser(); userTom.setFirstName("Tom"); userTom.setLastName("Doe"); userTom.setEmail("[email protected]"); userTom.setAge(26); repo.save(userTom); } }

Selanjutnya, mari kita lihat bagaimana menemukan pengguna dengan nama belakang yang diberikan :

@Test public void givenLast_whenGettingListOfUsers_thenCorrect() { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("lastName", ":", "Doe"); Iterable results = repo.findAll(builder.build()); assertThat(results, containsInAnyOrder(userJohn, userTom)); }

Sekarang, mari kita lihat bagaimana menemukan pengguna dengan nama depan dan belakang yang diberikan :

@Test public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder() .with("firstName", ":", "John").with("lastName", ":", "Doe"); Iterable results = repo.findAll(builder.build()); assertThat(results, contains(userJohn)); assertThat(results, not(contains(userTom))); }

Next, let’s see how to find user with given both last name and minimum age

@Test public void givenLastAndAge_whenGettingListOfUsers_thenCorrect() { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder() .with("lastName", ":", "Doe").with("age", ">", "25"); Iterable results = repo.findAll(builder.build()); assertThat(results, contains(userTom)); assertThat(results, not(contains(userJohn))); }

Now, let’s see how to search for MyUser that doesn’t actually exist:

@Test public void givenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect() { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder() .with("firstName", ":", "Adam").with("lastName", ":", "Fox"); Iterable results = repo.findAll(builder.build()); assertThat(results, emptyIterable()); }

Finally – let’s see how to find a MyUser given only part of the first name – as in the following example:

@Test public void givenPartialFirst_whenGettingListOfUsers_thenCorrect() { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("firstName", ":", "jo"); Iterable results = repo.findAll(builder.build()); assertThat(results, contains(userJohn)); assertThat(results, not(contains(userTom))); }

8. UserController

Finally, let's put everything together and build the REST API.

We're defining a UserController that defines a simple method findAll() with a “search“ parameter to pass in the query string:

@Controller public class UserController { @Autowired private MyUserRepository myUserRepository; @RequestMapping(method = RequestMethod.GET, value = "/myusers") @ResponseBody public Iterable search(@RequestParam(value = "search") String search) { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder(); if (search != null) { Pattern pattern = Pattern.compile("(\w+?)(:|)(\w+?),"); Matcher matcher = pattern.matcher(search + ","); while (matcher.find()) { builder.with(matcher.group(1), matcher.group(2), matcher.group(3)); } } BooleanExpression exp = builder.build(); return myUserRepository.findAll(exp); } }

Here is a quick test URL example:

//localhost:8080/myusers?search=lastName:doe,age>25

And the response:

[{ "id":2, "firstName":"tom", "lastName":"doe", "email":"[email protected]", "age":26 }]

9. Conclusion

Artikel ketiga ini membahas langkah-langkah pertama dalam membangun bahasa kueri untuk REST API , memanfaatkan pustaka Querydsl dengan baik.

Penerapannya tentu saja sejak awal, tetapi dapat dengan mudah dikembangkan untuk mendukung operasi tambahan.

The implementasi penuh dari artikel ini dapat ditemukan dalam proyek GitHub - ini adalah proyek berbasis Maven, sehingga harus mudah untuk impor dan berjalan seperti itu.

Berikutnya » Bahasa Kueri REST - Operasi Pencarian Lanjutan « Bahasa Kueri REST Sebelumnya dengan Spesifikasi JPA Data Musim Semi