REST-Abfragesprache mit Spring- und JPA-Kriterien

REST Top

Ich habe gerade den neuen Learn Spring- Kurs angekündigt , der sich auf die Grundlagen von Spring 5 und Spring Boot 2 konzentriert:

>> KURS PRÜFEN Dieser Artikel ist Teil einer Reihe: • REST-Abfragesprache mit Spring- und JPA-Kriterien (aktueller Artikel) • REST-Abfragesprache mit Spring-Daten-JPA-Spezifikationen

• REST-Abfragesprache mit Spring Data JPA und Querydsl

• REST-Abfragesprache - Erweiterte Suchvorgänge

• REST-Abfragesprache - Implementierung einer ODER-Operation

• REST-Abfragesprache mit RSQL

• REST-Abfragesprache mit Querydsl-Webunterstützung

1. Übersicht

In diesem ersten Artikel dieser neuen Reihe werden wir eine einfache Abfragesprache für eine REST-API untersuchen . Wir werden Spring für die REST-API und JPA 2-Kriterien für die Persistenzaspekte gut nutzen.

Warum eine Abfragesprache? Weil - für jede ausreichend komplexe API - das Suchen / Filtern Ihrer Ressourcen nach sehr einfachen Feldern einfach nicht ausreicht. Eine Abfragesprache ist flexibler und ermöglicht es Ihnen, genau auf die benötigten Ressourcen zu filtern.

2. Benutzer Entity

Lassen Sie uns zunächst die einfache Entität vorschlagen, die wir für unsere Filter- / Such-API verwenden werden - einen Basisbenutzer :

@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. Filtern mit CriteriaBuilder

Nun - kommen wir zum Kern des Problems - der Abfrage in der Persistenzschicht.

Das Erstellen einer Abfrageabstraktion ist eine Frage des Gleichgewichts. Wir brauchen einerseits ein gutes Maß an Flexibilität und andererseits die Komplexität überschaubar. Auf hoher Ebene ist die Funktionalität einfach - Sie übergeben einige Einschränkungen und erhalten einige Ergebnisse zurück .

Mal sehen, wie das funktioniert:

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

Schauen wir uns die UserSearchQueryCriteriaConsumer- Klasse an:

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 }

Wie Sie sehen können, erstellt die searchUser- API eine Liste sehr einfacher Einschränkungen, erstellt eine Abfrage basierend auf diesen Einschränkungen, führt die Suche durch und gibt die Ergebnisse zurück.

Die Einschränkungsklasse ist ebenfalls recht einfach:

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

Die such Implementierung hält unsere Query - Parameter:

  • Schlüssel : Wird verwendet, um den Feldnamen zu speichern - zum Beispiel: Vorname , Alter usw.
  • Operation : Wird verwendet, um die Operation zu halten - zum Beispiel: Gleichheit, kleiner als, ... usw.
  • Wert : Wird verwendet, um den Feldwert zu halten - zum Beispiel: John, 25,… usw.

4. Testen Sie die Suchanfragen

Testen Sie jetzt unseren Suchmechanismus, um sicherzustellen, dass er Wasser enthält.

Zunächst initialisieren wir unsere Datenbank zum Testen, indem wir zwei Benutzer hinzufügen - wie im folgenden Beispiel:

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

Lassen Sie uns nun einen Benutzer mit einem bestimmten Vor- und Nachnamen erhalten - wie im folgenden Beispiel:

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

Als nächstes wollen sie erhalten eine Liste von Benutzern mit dem gleichen Nachnamen :

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

Als nächstes erhalten wir Benutzer mit einem Alter von mindestens 25 Jahren :

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

Als nächstes suchen wir nach Benutzern, die es tatsächlich nicht gibt :

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

Lassen Sie uns abschließend nach Benutzern suchen, denen nur ein Teil des Vornamens gegeben wurde :

@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. Der UserController

Lassen Sie uns nun die Persistenzunterstützung für diese flexible Suche mit unserer REST-API verbinden.

Wir werden einen einfachen UserController einrichten - mit einem findAll () , der die " Suche " verwendet, um den gesamten Such- / Filterausdruck zu übergeben :

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

Beachten Sie, wie wir einfach unsere Suchkriterienobjekte aus dem Suchausdruck erstellen.

Wir sind jetzt an dem Punkt angelangt, an dem wir mit der API spielen und sicherstellen können, dass alles richtig funktioniert:

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

Und hier ist seine Antwort:

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

7. Fazit

Diese einfache, aber leistungsstarke Implementierung ermöglicht einiges an intelligenter Filterung in einer REST-API. Ja - es ist immer noch rau an den Rändern und kann verbessert werden (und wird im nächsten Artikel verbessert) - aber es ist ein solider Ausgangspunkt, um diese Art von Filterfunktionalität in Ihren APIs zu implementieren.

Die vollständige Implementierung dieses Artikels finden Sie im GitHub-Projekt - dies ist ein Maven-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.

Weiter » REST-Abfragesprache mit Spring Data JPA-Spezifikationen REST unten

Ich habe gerade den neuen Learn Spring- Kurs angekündigt , der sich auf die Grundlagen von Spring 5 und Spring Boot 2 konzentriert:

>> Überprüfen Sie den Kurs