REST Query Language med RSQL

REST Topp

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 Utholdenhetstopp

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

2. Forberedelser

Først, la oss legge til en avhengig avhengighet til biblioteket:

 cz.jirutka.rsql rsql-parser 2.1.0 

Og også definere hovedenheten vi skal jobbe med gjennom eksemplene - Bruker:

@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

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:

offentlig klasse CustomRsqlVisitor implementerer RSQLVisitor {private GenericRsqlSpecBuilder byggherre; offentlig CustomRsqlVisitor () {builder = ny GenericRsqlSpecBuilder (); } @Override public Specification visit (AndNode node, Void param) {return builder.createSpecification (node); } @Override public Specification visit (OrNode node, Void param) {return builder.createSpecification (node); } @Override public Specification visit (ComparisonNode node, Void params) {return builder.createSecification (node); }}

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:

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

Legg merke til hvordan:

  • LogicalNode er en OG/ELLERNode og har flere barn
  • ComparisonNode har ingen barn, og det holder Velger, operatør og argumentene

For eksempel for et spørsmål “navn == john" - vi har:

  1. Velger: "Navn"
  2. Operatør: “==”
  3. Argumenter: [john]

4. Opprett egendefinert Spesifikasjon

Ved konstruksjon av spørringen brukte vi en 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}

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:

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

La oss nå begynne å teste våre nye og fleksible operasjoner gjennom noen virkelige scenarier:

Først - la oss initialisere dataene:

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

La oss nå teste de forskjellige operasjonene:

5.1. Test likhet

I det følgende eksemplet - vi søker etter brukere etter deres først og etternavn:

@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

Deretter, la oss søke etter brukere som av deres fornavn ikke “john”:

@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

Neste - vi vil søke etter brukere med alder større enn "25”:

@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

Neste - vi vil søke etter brukere med deres fornavn starter med "jo”:

@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

Neste - vi vil søke etter brukere deres fornavn er "john”Eller“knekt“:

@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

Til slutt - la oss knytte alt sammen med kontrolleren:

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

Her er en eksempel-URL:

// localhost: 8080 / brukere? search = firstName == jo *; alder <25

Og svaret:

[{"id": 1, "firstName": "john", "lastName": "doe", "email": "[email protected]", "age": 24}]

7. Konklusjon

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.

Neste » REST Query Language med Querydsl Web Support « Previous REST Query Language - Implementing OR Operation REST bottom

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

$config[zx-auto] not found$config[zx-overlay] not found