REST Query Language med RSQL
Jeg kunngjorde nettopp det nye Lær våren kurs, med fokus på det grunnleggende i vår 5 og vårstøvel 2:
>> KONTROLLER KURSET UtholdenhetstoppJeg kunngjorde nettopp det nye Lær våren kurs, med fokus på det grunnleggende i vår 5 og vårstøvel 2:
>> KONTROLLER KURSET Denne artikkelen er en del av en serie: • REST Query Language with Spring and JPA Criteria• REST-spørrespråk med vårdata JPA-spesifikasjoner
• REST Query Language med vårdata JPA og Querydsl
• REST-spørrespråk - Avanserte søkeoperasjoner
• REST Query Language - Implementering ELLER drift
• REST Query Language med RSQL (nåværende artikkel) • REST Query Language med Querydsl Web Support
1. Oversikt
I denne femte artikkelen i serien illustrerer vi å bygge REST API Query-språket ved hjelp av et kult bibliotek - rsql-parser.
RSQL er et super-sett av Feed Item Query Language (FIQL) - en ren og enkel filtersyntaks for feeds; så det passer ganske naturlig inn i et REST API. Først, la oss legge til en avhengig avhengighet til biblioteket: Og også definere hovedenheten vi skal jobbe med gjennom eksemplene - Bruker: Måten RSQL-uttrykk er representert internt, er i form av noder og besøkende mønster brukes til å analysere inngangen. Med det i tankene skal vi implementere RSQLVisitor grensesnitt og lage vår egen besøkendeimplementering - CustomRsqlVisitor: Nå må vi håndtere utholdenhet og konstruere forespørselen vår ut av hver av disse nodene. Vi skal bruke Spring Data JPA-spesifikasjonene vi brukte før - og vi skal implementere en Spesifikasjon byggmester til konstruer spesifikasjoner ut av hver av disse nodene vi besøker: Legg merke til hvordan: For eksempel for et spørsmål “navn == john" - vi har: Ved konstruksjon av spørringen brukte vi en Spesifikasjon: Legg merke til hvordan spesifikasjonen bruker generikk og ikke er knyttet til noen spesifikk enhet (for eksempel brukeren). Neste - her er vår enum “RsqlSearchOperation“ som har standard rsql-parser-operatører: La oss nå begynne å teste våre nye og fleksible operasjoner gjennom noen virkelige scenarier: Først - la oss initialisere dataene: La oss nå teste de forskjellige operasjonene: I det følgende eksemplet - vi søker etter brukere etter deres først og etternavn: Deretter, la oss søke etter brukere som av deres fornavn ikke “john”: Neste - vi vil søke etter brukere med alder større enn "25”: Neste - vi vil søke etter brukere med deres fornavn starter med "jo”: Neste - vi vil søke etter brukere deres fornavn er "john”Eller“knekt“: Til slutt - la oss knytte alt sammen med kontrolleren: Her er en eksempel-URL: Og svaret: Denne opplæringen illustrerte hvordan du bygger et spørrings- / søkespråk for et REST API uten å måtte oppfinne syntaksen og i stedet bruke FIQL / RSQL. De full gjennomføring av denne artikkelen finnes i GitHub-prosjektet - dette er et Maven-basert prosjekt, så det skal være enkelt å importere og kjøre som det er.2. Forberedelser
cz.jirutka.rsql rsql-parser 2.1.0
@Entity offentlig klasse bruker {@Id @GeneratedValue (strategi = GenerationType.AUTO) privat Lang id; privat streng fornavn; privat streng etternavn; privat streng e-post; privat alder; }
3. Analyser forespørselen
offentlig klasse CustomRsqlVisitor implementerer RSQLVisitor
offentlig klasse GenericRsqlSpecBuilder {public Specification createSpecification (Node node) {if (node instanceof LogicalNode) {return createSpecification ((LogicalNode) node); } if (node instanceof ComparisonNode) {return createSpecification ((ComparisonNode) node); } returner null; } offentlig spesifikasjon createSpecification (LogicalNode logicalNode) {List specs = logicalNode.getChildren () .stream () .map (node -> createSpecification (node)) .filter (Objects :: nonNull) .collect (Collectors.toList ()); Spesifikasjonsresultat = specs.get (0); hvis (logicalNode.getOperator () == LogicalOperator.AND) {for (int i = 1; i <specs.size (); i ++) {result = Specification.where (result) .and (specs.get (i)) ; }} annet hvis (logicalNode.getOperator () == LogicalOperator.OR) {for (int i = 1; i <specs.size (); i ++) {result = Specification.where (result) .or (specs.get ( Jeg)); }} returner resultat; } offentlig spesifikasjon createSpecification (ComparisonNode comparisonNode) {Specification result = Specification.where (new GenericRsqlSpecification (comparisonNode.getSelector (), comparisonNode.getOperator (), comparisonNode.getArguments ())); returresultat; }}
4. Opprett egendefinert Spesifikasjon
offentlig klasse GenericRsqlSpecification implementerer spesifikasjon {private strengegenskap; privat ComparisonOperator-operatør; private Liste argumenter; @Override public Predicate toPredicate (Root root, CriteriaQuery query, CriteriaBuilder builder) {List args = castArguments (root); Objektargument = args.get (0); switch (RsqlSearchOperation.getSimpleOperator (operator)) {case EQUAL: {if (argument instanceof String) {return builder.like (root.get (property), argument.toString (). erstatt ('*', '%')) ; } annet hvis (argument == null) {return builder.isNull (root.get (eiendom)); } annet {return builder.equal (root.get (eiendom), argument); }} tilfelle NOT_EQUAL: {if (argument instance of String) {return builder.notLike (root. get (property), argument.toString (). erstatt ('*', '%')); } annet hvis (argument == null) {return builder.isNotNull (root.get (eiendom)); } annet {return builder.notEqual (root.get (eiendom), argument); }} tilfelle GREATER_THAN: {return builder.greaterThan (root. get (property), argument.toString ()); } tilfelle GREATER_THAN_OR_EQUAL: {return builder.greaterThanOrEqualTo (root. get (property), argument.toString ()); } sak LESS_THAN: {return builder.lessThan (root. get (property), argument.toString ()); } sak LESS_THAN_OR_EQUAL: {return builder.lessThanOrEqualTo (root. get (property), argument.toString ()); } case IN: return root.get (property) .in (args); case NOT_IN: return builder.not (root.get (eiendom) .in (args)); } returner null; } privat liste castArguments (endelig rotrot) {Klassetype = root.get (eiendom) .getJavaType (); Liste args = argumenter.stream (). Kart (arg -> {if (type.equals (Integer.class)) {return Integer.parseInt (arg);} else if (type.equals (Long.class)) {return Long.parseLong (arg);} else {return arg;}}). Collect (Collectors.toList ()); retur args; } // standard konstruktør, getter, setter}
offentlig enum {RsqlSearchOperation like (RSQLOperators.EQUAL), NOT_EQUAL (RSQLOperators.NOT_EQUAL), GREATER_THAN (RSQLOperators.GREATER_THAN), GREATER_THAN_OR_EQUAL (RSQLOperators.GREATER_THAN_OR_EQUAL), LESS_THAN (RSQLOperators.LESS_THAN), LESS_THAN_OR_EQUAL (RSQLOperators.LESS_THAN_OR_EQUAL), IN (RSQLOperators. IN), NOT_IN (RSQLOperators.NOT_IN); privat ComparisonOperator-operatør; private RsqlSearchOperation (ComparisonOperator operator) {this.operator = operator; } offentlig statisk RsqlSearchOperation getSimpleOperator (ComparisonOperator operator) {for (RsqlSearchOperation operation: values ()) {if (operation.getOperator () == operator) {return operation; }} returner null; }}
5. Test søk
@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (classes = {PersistenceConfig.class}) @ Transactional @ TransactionConfiguration public class RsqlTest {@Autowired private UserRepository repository; privat bruker brukerJohn; privat bruker userTom; @Før offentlig ugyldig init () {userJohn = ny bruker (); userJohn.setFirstName ("john"); userJohn.setLastName ("doe"); userJohn.setEmail ("[email protected]"); userJohn.setAge (22); repository.save (userJohn); userTom = ny bruker (); userTom.setFirstName ("tom"); userTom.setLastName ("doe"); userTom.setEmail ("[email protected]"); userTom.setAge (26); repository.save (userTom); }}
5.1. Test likhet
@Test public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect () {Node rootNode = new RSQLParser (). Parse ("firstName == john; lastName == doe"); Spesifikasjon spesifikasjon = rootNode.accept (ny CustomRsqlVisitor ()); Listeresultater = repository.findAll (spesifikasjon); assertThat (userJohn, isIn (resultater)); assertThat (userTom, not (isIn (results))); }
5.2. Test Negasjon
@Test public void givenFirstNameInverse_whenGettingListOfUsers_thenCorrect () {Node rootNode = new RSQLParser (). Parse ("firstName! = John"); Spesifikasjon spesifikasjon = rootNode.accept (ny CustomRsqlVisitor ()); Listeresultater = repository.findAll (spesifikasjon); assertThat (userTom, isIn (resultater)); assertThat (userJohn, not (isIn (results))); }
5.3. Test større enn
@Test offentlig ugyldig givenMinAge_whenGettingListOfUsers_thenCorrect () {Node rootNode = ny RSQLParser (). Parse ("alder> 25"); Spesifikasjon spesifikasjon = rootNode.accept (ny CustomRsqlVisitor ()); Listeresultater = repository.findAll (spesifikasjon); assertThat (userTom, isIn (resultater)); assertThat (userJohn, not (isIn (results))); }
5.4. Test som
@Test public void givenFirstNamePrefix_whenGettingListOfUsers_thenCorrect () {Node rootNode = new RSQLParser (). Parse ("firstName == jo *"); Spesifikasjon spesifikasjon = rootNode.accept (ny CustomRsqlVisitor ()); Listeresultater = repository.findAll (spesifikasjon); assertThat (userJohn, isIn (resultater)); assertThat (userTom, not (isIn (results))); }
5.5. Test I
@Test offentlig ugyldig givenListOfFirstName_whenGettingListOfUsers_thenCorrect () {Node rootNode = ny RSQLParser (). Parse ("firstName = in = (john, jack)"); Spesifikasjon spesifikasjon = rootNode.accept (ny CustomRsqlVisitor ()); Listeresultater = repository.findAll (spesifikasjon); assertThat (userJohn, isIn (resultater)); assertThat (userTom, not (isIn (results))); }
6. UserController
@RequestMapping (method = RequestMethod.GET, value = "/ users") @ResponseBody public List findAllByRsql (@RequestParam (value = "search") String search) {Node rootNode = new RSQLParser (). Parse (search); Spesifikasjon spesifikasjon = rootNode.accept (ny CustomRsqlVisitor ()); returner dao.findAll (spesifikasjon); }
// localhost: 8080 / brukere? search = firstName == jo *; alder <25
[{"id": 1, "firstName": "john", "lastName": "doe", "email": "[email protected]", "age": 24}]
7. Konklusjon
Jeg kunngjorde nettopp det nye Lær våren kurs, med fokus på det grunnleggende i vår 5 og vårstøvel 2:
>> KONTROLLER KURSET Persistensbunn Jeg kunngjorde nettopp det nye Lær våren kurs, med fokus på det grunnleggende i vår 5 og vårstøvel 2:
>> KONTROLLER KURSET