Bahasa Kueri REST dengan Kriteria Spring dan JPA

REST Top

Saya baru saja mengumumkan kursus Learn Spring baru , yang berfokus pada dasar-dasar Spring 5 dan Spring Boot 2:

>> PERIKSA KURSUS Artikel ini adalah bagian dari seri: • REST Query Language dengan Spring dan JPA Criteria (artikel saat ini) • REST Query Language dengan Spesifikasi JPA Spring Data

• REST Query Language dengan Spring Data JPA dan Querydsl

• REST Query Language - Operasi Pencarian Lanjutan

• REST Query Language - Menerapkan ATAU Operasi

• REST Query Language dengan RSQL

• REST Query Language dengan Querydsl Web Support

1. Ikhtisar

Di artikel pertama dari seri baru ini, kita akan menjelajahi bahasa kueri sederhana untuk REST API . Kami akan memanfaatkan Spring untuk REST API dan Kriteria JPA 2 untuk aspek persistensi.

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. Entitas Pengguna

Pertama - mari kita kemukakan entitas sederhana yang akan kita gunakan untuk filter / API pencarian kita - Pengguna dasar :

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

3. Filter Menggunakan CriteriaBuilder

Sekarang - mari masuk ke inti masalahnya - kueri di lapisan persistensi.

Membangun abstraksi kueri adalah masalah keseimbangan. Kita membutuhkan fleksibilitas yang baik di satu sisi, dan kita perlu menjaga kompleksitas tetap terkelola di sisi lain. Tingkat tinggi, fungsinya sederhana - Anda melewati beberapa kendala dan Anda mendapatkan kembali beberapa hasil .

Mari kita lihat cara kerjanya:

@Repository public class UserDAO implements IUserDAO { @PersistenceContext private EntityManager entityManager; @Override public List searchUser(List params) { CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery query = builder.createQuery(User.class); Root r = query.from(User.class); Predicate predicate = builder.conjunction(); UserSearchQueryCriteriaConsumer searchConsumer = new UserSearchQueryCriteriaConsumer(predicate, builder, r); params.stream().forEach(searchConsumer); predicate = searchConsumer.getPredicate(); query.where(predicate); List result = entityManager.createQuery(query).getResultList(); return result; } @Override public void save(User entity) { entityManager.persist(entity); } }

Mari kita lihat kelas UserSearchQueryCriteriaConsumer :

public class UserSearchQueryCriteriaConsumer implements Consumer{ private Predicate predicate; private CriteriaBuilder builder; private Root r; @Override public void accept(SearchCriteria param) { if (param.getOperation().equalsIgnoreCase(">")) { predicate = builder.and(predicate, builder .greaterThanOrEqualTo(r.get(param.getKey()), param.getValue().toString())); } else if (param.getOperation().equalsIgnoreCase("<")) { predicate = builder.and(predicate, builder.lessThanOrEqualTo( r.get(param.getKey()), param.getValue().toString())); } else if (param.getOperation().equalsIgnoreCase(":")) { if (r.get(param.getKey()).getJavaType() == String.class) { predicate = builder.and(predicate, builder.like( r.get(param.getKey()), "%" + param.getValue() + "%")); } else { predicate = builder.and(predicate, builder.equal( r.get(param.getKey()), param.getValue())); } } } // standard constructor, getter, setter }

Seperti yang Anda lihat, searchUser API mengambil daftar batasan yang sangat sederhana, menyusun kueri berdasarkan batasan ini, melakukan pencarian dan mengembalikan hasil.

Kelas kendala juga cukup sederhana:

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

The SearchCriteria pelaksanaan memegang kami Query parameter:

  • key : digunakan untuk menyimpan nama field - misalnya: firstName , age ,… dll.
  • operasi : digunakan untuk menahan operasi - misalnya: Kesetaraan, kurang dari,… dll.
  • nilai : digunakan untuk menampung nilai bidang - misalnya: john, 25,… dll.

4. Uji Permintaan Pencarian

Sekarang - mari kita uji mekanisme pencarian kita untuk memastikannya menahan air.

Pertama - mari kita inisialisasi database kita untuk pengujian dengan menambahkan dua pengguna - seperti pada contoh berikut:

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { PersistenceConfig.class }) @Transactional @TransactionConfiguration public class JPACriteriaQueryTest { @Autowired private IUserDAO userApi; private User userJohn; private User userTom; @Before public void init() { userJohn = new User(); userJohn.setFirstName("John"); userJohn.setLastName("Doe"); userJohn.setEmail("[email protected]"); userJohn.setAge(22); userApi.save(userJohn); userTom = new User(); userTom.setFirstName("Tom"); userTom.setLastName("Doe"); userTom.setEmail("[email protected]"); userTom.setAge(26); userApi.save(userTom); } }

Sekarang, mari kita dapatkan Pengguna dengan firstName dan lastName tertentu - seperti pada contoh berikut:

@Test public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() { List params = new ArrayList(); params.add(new SearchCriteria("firstName", ":", "John")); params.add(new SearchCriteria("lastName", ":", "Doe")); List results = userApi.searchUser(params); assertThat(userJohn, isIn(results)); assertThat(userTom, not(isIn(results))); }

Berikutnya, mari kita mendapatkan Daftar dari Pengguna dengan sama lastName :

@Test public void givenLast_whenGettingListOfUsers_thenCorrect() { List params = new ArrayList(); params.add(new SearchCriteria("lastName", ":", "Doe")); List results = userApi.searchUser(params); assertThat(userJohn, isIn(results)); assertThat(userTom, isIn(results)); }

Selanjutnya, mari kita dapatkan pengguna dengan usia lebih dari atau sama dengan 25 tahun :

@Test public void givenLastAndAge_whenGettingListOfUsers_thenCorrect() { List params = new ArrayList(); params.add(new SearchCriteria("lastName", ":", "Doe")); params.add(new SearchCriteria("age", ">", "25")); List results = userApi.searchUser(params); assertThat(userTom, isIn(results)); assertThat(userJohn, not(isIn(results))); }

Selanjutnya, mari cari pengguna yang sebenarnya tidak ada :

@Test public void givenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect() { List params = new ArrayList(); params.add(new SearchCriteria("firstName", ":", "Adam")); params.add(new SearchCriteria("lastName", ":", "Fox")); List results = userApi.searchUser(params); assertThat(userJohn, not(isIn(results))); assertThat(userTom, not(isIn(results))); }

Akhirnya, pencarian mari untuk pengguna diberikan hanya parsial firstName :

@Test public void givenPartialFirst_whenGettingListOfUsers_thenCorrect() { List params = new ArrayList(); params.add(new SearchCriteria("firstName", ":", "jo")); List results = userApi.searchUser(params); assertThat(userJohn, isIn(results)); assertThat(userTom, not(isIn(results))); }

6. UserController

Akhirnya, sekarang mari kita masukkan dukungan ketekunan untuk pencarian fleksibel ini ke REST API kita.

Kita akan menyiapkan UserController sederhana - dengan findAll () menggunakan " search " untuk meneruskan seluruh ekspresi pencarian / filter :

@Controller public class UserController { @Autowired private IUserDao api; @RequestMapping(method = RequestMethod.GET, value = "/users") @ResponseBody public List findAll(@RequestParam(value = "search", required = false) String search) { List params = new ArrayList(); if (search != null) { Pattern pattern = Pattern.compile("(\w+?)(:|)(\w+?),"); Matcher matcher = pattern.matcher(search + ","); while (matcher.find()) { params.add(new SearchCriteria(matcher.group(1), matcher.group(2), matcher.group(3))); } } return api.searchUser(params); } }

Perhatikan bagaimana kita hanya membuat objek kriteria pencarian kita dari ekspresi pencarian.

Kami sekarang berada pada titik di mana kami dapat mulai bermain dengan API dan memastikan semuanya bekerja dengan benar:

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

Dan inilah tanggapannya:

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

7. Kesimpulan

Implementasi yang sederhana namun kuat ini memungkinkan cukup banyak pemfilteran cerdas pada REST API. Ya - ini masih kasar di sekitar tepinya dan dapat ditingkatkan (dan akan ditingkatkan di artikel berikutnya) - tetapi ini adalah titik awal yang kokoh untuk menerapkan fungsi pemfilteran semacam ini pada API Anda.

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 dengan Spesifikasi JPA Spring Data REST bawah

Saya baru saja mengumumkan kursus Learn Spring baru , yang berfokus pada dasar-dasar Spring 5 dan Spring Boot 2:

>> LIHAT KURSUSnya