REST Query Language dengan Spring Data JPA dan Querydsl

Artikel ini adalah sebahagian daripada siri: • REST Query Language dengan Kriteria Spring dan JPA

• Bahasa Pertanyaan REST dengan Spesifikasi JPA Spring Data

• REST Query Language dengan Spring Data JPA dan Querydsl (artikel semasa) • REST Query Language - Operasi Carian Lanjutan

• Bahasa Pertanyaan REST - Melaksanakan ATAU Operasi

• REST Query Language dengan RSQL

• REST Query Language dengan Sokongan Web Querydsl

1. Gambaran keseluruhan

Dalam tutorial ini, kami sedang mencari untuk membina bahasa pertanyaan untuk REST API menggunakan Spring Data JPA dan Querydsl .

Dalam dua artikel pertama siri ini, kami membina fungsi carian / penapisan yang sama menggunakan Kriteria JPA dan Spesifikasi JPA Spring Data.

Jadi - mengapa bahasa pertanyaan? Kerana - untuk API yang cukup rumit - mencari / menapis sumber anda dengan medan yang sangat sederhana tidak cukup. Bahasa pertanyaan lebih fleksibel , dan membolehkan anda menapis sumber yang anda perlukan.

2. Konfigurasi Querydsl

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

Kita perlu menambahkan kebergantungan berikut ke pom.xml :

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

Kita juga perlu mengkonfigurasi APT - alat pemprosesan anotasi - plugin seperti berikut:

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

Ini akan menghasilkan jenis Q untuk entiti kami.

3. Entiti MyUser

Seterusnya - mari kita lihat entiti " MyUser " yang akan kita gunakan di API Carian kita:

@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. Custom Predikat W engan PathBuilder

Sekarang - mari buat Predicate tersuai berdasarkan beberapa kekangan sewenang-wenangnya.

Kami menggunakan PathBuilder di sini dan bukannya jenis Q yang dihasilkan secara automatik kerana kita perlu membuat jalan secara dinamik 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 pelaksanaan predikat secara amnya menangani pelbagai jenis operasi . Ini kerana bahasa pertanyaan secara definisi adalah bahasa terbuka di mana anda berpotensi menyaring mengikut bidang apa pun, menggunakan operasi yang disokong.

Untuk mewakili kriteria penyaringan terbuka seperti itu, kami menggunakan pelaksanaan yang mudah tetapi cukup fleksibel - SearchCriteria :

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

The SearchCriteria memegang butiran kita perlu untuk mewakili kekangan:

  • kunci : nama medan - contohnya: nama pertama , umur , ... dll
  • operasi : operasi - contohnya: Persamaan, kurang daripada, ... dll
  • nilai : nilai medan - contohnya: john, 25,… dll

5. MyUserRepository

Sekarang - mari lihat MyUserRepository kami .

Kami memerlukan MyUserRepository kami untuk memperluas QuerydslPredicateExecutor supaya kami dapat menggunakan Predicates kemudian untuk menyaring hasil carian:

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 bahawa kami menggunakan di sini jenis Q yang dihasilkan untuk entiti MyUser , yang akan diberi nama QMyUser.

6. Gabungkan Predikat

Seterusnya - mari kita lihat menggabungkan Predicates untuk menggunakan pelbagai kekangan dalam penyaringan hasil.

Dalam contoh berikut - kami bekerjasama dengan pembangun - MyUserPredicatesBuilder - untuk menggabungkan Predicates :

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 Pertanyaan Carian

Seterusnya - mari uji API Carian kami.

Kami akan memulakan dengan menginisialisasi pangkalan data dengan beberapa pengguna - untuk menyediakannya dan tersedia untuk diuji:

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

Seterusnya, mari kita lihat bagaimana mencari 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 mencari pengguna dengan nama depan dan belakang :

@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 merangkumi langkah pertama untuk membina bahasa pertanyaan untuk REST API , memanfaatkan perpustakaan Querydsl dengan baik.

Pelaksanaannya sudah tentu awal, tetapi dapat dengan mudah dikembangkan untuk menyokong operasi tambahan.

The pelaksanaan penuh artikel ini boleh didapati dalam projek GitHub - ini adalah projek berasaskan Maven, jadi ia harus mudah untuk import dan berjalan kerana ia adalah.

Seterusnya » Bahasa Pertanyaan REST - Operasi Carian Lanjutan « Bahasa Pertanyaan REST sebelumnya dengan Spesifikasi JPA Spring Data