REST Query Language con criteri Spring e JPA

REST Top

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> GUARDA IL CORSO Questo articolo fa parte di una serie: • Linguaggio di query REST con criteri Spring e JPA (articolo corrente) • Linguaggio di query REST con specifiche JPA di Spring Data

• REST Query Language con Spring Data JPA e Querydsl

• REST Query Language - Operazioni di ricerca avanzate

• REST Query Language - Implementazione dell'operazione OR

• REST Query Language con RSQL

• Linguaggio query REST con supporto Web Querydsl

1. Panoramica

In questo primo articolo di questa nuova serie, esploreremo un semplice linguaggio di query per un'API REST . Faremo un buon uso di Spring per l'API REST e dei criteri JPA 2 per gli aspetti di persistenza.

Perché un linguaggio di query? Perché, per qualsiasi API abbastanza complessa, cercare / filtrare le risorse tramite campi molto semplici semplicemente non è sufficiente. Un linguaggio di query è più flessibile e ti consente di filtrare esattamente le risorse di cui hai bisogno.

2. Entità utente

Innanzitutto, proponiamo l'entità semplice che utilizzeremo per la nostra API di filtro / ricerca: un utente di base :

@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. Filtra utilizzando CriteriaBuilder

Ora, entriamo nel vivo del problema: la query nel livello di persistenza.

La creazione di un'astrazione della query è una questione di equilibrio. Abbiamo bisogno di una buona dose di flessibilità da un lato e dobbiamo mantenere la complessità gestibile dall'altro. Di alto livello, la funzionalità è semplice: si superano alcuni vincoli e si ottengono risultati .

Vediamo come funziona:

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

Diamo un'occhiata alla classe 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 }

Come puoi vedere, l' API searchUser prende un elenco di vincoli molto semplici, compone una query basata su questi vincoli, esegue la ricerca e restituisce i risultati.

Anche la classe dei vincoli è abbastanza semplice:

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

L' implementazione di SearchCriteria contiene i nostri parametri di query :

  • chiave : utilizzata per contenere il nome del campo, ad esempio: firstName , age , ... ecc.
  • operazione : utilizzato per mantenere l'operazione, ad esempio: uguaglianza, minore di, ... ecc.
  • valore : utilizzato per contenere il valore del campo, ad esempio: giovanni, 25, ... ecc.

4. Testare le query di ricerca

Ora, testiamo il nostro meccanismo di ricerca per assicurarci che trattiene l'acqua.

Innanzitutto, inizializziamo il nostro database per il test aggiungendo due utenti, come nell'esempio seguente:

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

Ora, prendiamo un utente con firstName e lastName specifici , come nell'esempio seguente:

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

Successivamente, otteniamo un elenco di utenti con lo stesso 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)); }

Quindi, chiediamo agli utenti di età maggiore o uguale a 25 :

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

Successivamente, cerchiamo utenti che in realtà non esistono :

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

Infine, cerchiamo gli utenti con firstName solo parziale :

@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

Infine, colleghiamo ora il supporto di persistenza per questa ricerca flessibile alla nostra API REST.

Stiamo per configurare un semplice UserController - con findAll () utilizzando la " ricerca " per passare l'intera espressione di ricerca / filtro :

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

Nota come stiamo semplicemente creando i nostri oggetti criteri di ricerca dall'espressione di ricerca.

Siamo ora al punto in cui possiamo iniziare a giocare con l'API e assicurarci che tutto funzioni correttamente:

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

Ed ecco la sua risposta:

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

7. Conclusione

Questa implementazione semplice ma potente consente un po 'di filtraggio intelligente su un'API REST. Sì, è ancora approssimativo e può essere migliorato (e sarà migliorato nel prossimo articolo), ma è un solido punto di partenza per implementare questo tipo di funzionalità di filtro sulle tue API.

L' implementazione completa di questo articolo può essere trovata nel progetto GitHub: questo è un progetto basato su Maven, quindi dovrebbe essere facile da importare ed eseguire così com'è.

Avanti » Linguaggio di query REST con specifiche JPA Spring Data REST bottom

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO