REST Query Language med RSQL

REST Top

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN Udholdenhedstop

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN Denne artikel er en del af en serie: • REST Query Language with Spring and JPA Criteria

• REST-forespørgselssprog med forårsdata JPA-specifikationer

• REST forespørgselssprog med Spring Data JPA og Querydsl

• REST-forespørgselssprog - Avanceret søgning

• REST Query Language - Implementering ELLER drift

• REST Query Language med RSQL (nuværende artikel) • REST Query Language med Querydsl Web Support

1. Oversigt

I denne femte artikel i serien illustrerer vi opbygning af REST API Query-sproget ved hjælp af et sejt bibliotek - rsql-parser.

RSQL er et supersæt af Feed Item Query Language (FIQL) - en ren og enkel filtersyntaks til feeds; så det passer helt naturligt ind i en REST API.

2. Forberedelser

Lad os først tilføje en maven-afhængighed til biblioteket:

 cz.jirutka.rsql rsql-parser 2.1.0 

Og også definere hovedenheden vi skal arbejde med igennem eksemplerne - Bruger:

@Entity offentlig klasse bruger {@Id @GeneratedValue (strategi = GenerationType.AUTO) privat Lang id; privat streng fornavn; privat streng efternavn; privat streng e-mail; privat int alder }

3. Parse anmodningen

Den måde, hvorpå RSQL-udtryk er repræsenteret internt, er i form af noder, og besøgsmønsteret bruges til at analysere input.

Med det i tankerne vil vi implementere RSQLVisitor interface og oprette vores egen besøgendeimplementering - CustomRsqlVisitor:

offentlig klasse CustomRsqlVisitor implementerer RSQLVisitor {privat GenericRsqlSpecBuilder-builder; 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); }}

Nu skal vi håndtere vedholdenhed og konstruere vores forespørgsel ud af hver af disse noder.

Vi bruger de forårsdata JPA-specifikationer, vi brugte før - og vi implementerer en Specifikation bygherre til konstruer specifikationer ud af hver af disse noder, vi besøger:

offentlig klasse GenericRsqlSpecBuilder {public Specification createSpecification (Node node) {if (node ​​instanceof LogicalNode) {return createSpecification ((LogicalNode) node); } hvis (node-forekomst af ComparisonNode) {return createSpecification ((ComparisonNode) node); } returnere null; } offentlig specifikation createSpecification (LogicalNode logicalNode) {List specs = logicalNode.getChildren () .stream () .map (node ​​-> createSpecification (node)) .filter (Objects :: nonNull) .collect (Collectors.toList ()); Specifikationsresultat = specs.get (0); hvis (logicalNode.getOperator () == LogicalOperator.AND) {for (int i = 1; i <specs.størrelse (); i ++) {resultat = Specification.where (resultat) .og (specs.get (i)) ; }} ellers hvis (logicalNode.getOperator () == LogicalOperator.OR) {for (int i = 1; i <specs.size (); i ++) {result = Specification.where (result) .or (specs.get ( jeg)); }} returner resultat; } offentlig Specifikation createSpecification (ComparisonNode comparisonNode) {Specification result = Specification.where (new GenericRsqlSpecification (comparisonNode.getSelector (), comparisonNode.getOperator (), comparisonNode.getArguments ())); returresultat }}

Bemærk hvordan:

  • LogicalNode er en OG/ELLERNode og har flere børn
  • ComparisonNode har ingen børn, og det holder Vælger, operatør og argumenter

For eksempel til en forespørgsel “navn == john" - vi har:

  1. Vælger: “Navn”
  2. Operatør: “==”
  3. Argumenter:[John]

4. Opret brugerdefineret Specifikation

Ved konstruktionen af ​​forespørgslen brugte vi en Specifikation:

offentlig klasse GenericRsqlSpecification implementerer specifikation {private strengegenskaber; 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 (). erstatte ('*', '%')) ; } ellers hvis (argument == null) {return builder.isNull (root.get (ejendom)); } andet {return builder.equal (root.get (ejendom), argument); }} sag NOT_EQUAL: {if (argument instanceof String) {return builder.notLike (root. get (property), argument.toString (). erstatte ('*', '%')); } ellers hvis (argument == null) {return builder.isNotNull (root.get (ejendom)); } ellers {return builder.notEqual (root.get (ejendom), argument); }} sag GREATER_THAN: {return builder.greaterThan (root. get (property), argument.toString ()); } sag GREATER_THAN_OR_EQUAL: {return builder.greaterThanOrEqualTo (root. get (property), argument.toString ()); } sag LESS_THAN: {return builder.lessThan (root. get (ejendom), argument.toString ()); } sag LESS_THAN_OR_EQUAL: {return builder.lessThanOrEqualTo (root. get (property), argument.toString ()); } sag IN: returner root.get (ejendom) .in (args); sag NOT_IN: returner builder.not (root.get (ejendom) .in (args)); } returnere null; } privat liste castArguments (endelig rodrod) {Klassetype = root.get (egenskab) .getJavaType (); Liste args = argumenter.stream (). Map (arg -> {if (type.equals (Integer.class)) {return Integer.parseInt (arg);} ellers if (type.equals (Long.class)) {return Long.parseLong (arg);} ellers {return arg;}}). Indsamle (Collectors.toList ()); tilbage args; } // standardkonstruktør, getter, setter}

Læg mærke til, hvordan specifikationen bruger generiske produkter og ikke er bundet til nogen bestemt enhed (såsom brugeren).

Næste - her er vores enum “RsqlSearchOperation som indeholder standard rsql-parser-operatører:

offentlig enum RsqlSearchOperation {Equal (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øgeforespørgsler

Lad os nu teste vores nye og fleksible operationer gennem nogle virkelige scenarier:

Først - lad os initialisere dataene:

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (classes = {PersistenceConfig.class}) @ Transactional @ TransactionConfiguration public class RsqlTest {@Autowired private UserRepository repository; privat bruger brugerJohn; privat bruger userTom; @Før offentlig ugyldig init () {userJohn = ny bruger (); userJohn.setFirstName ("john"); userJohn.setLastName ("doe"); userJohn.setEmail ("[email protected]"); brugerJohn.setAge (22); repository.save (userJohn); userTom = ny bruger (); userTom.setFirstName ("tom"); userTom.setLastName ("doe"); userTom.setEmail ("[email protected]"); userTom.setAge (26); repository.save (userTom); }}

Lad os nu teste de forskellige operationer:

5.1. Test lighed

I det følgende eksempel - vi søger efter brugere efter deres først og efternavn:

@ Test offentlig ugyldighed givenFirstAndLastName_whenGettingListOfUsers_thenCorrect () {Node rootNode = ny RSQLParser (). Parse ("firstName == john; lastName == doe"); Specifikationsspecifikation = rootNode.accept (ny CustomRsqlVisitor ()); Listeresultater = repository.findAll (spec); assertThat (userJohn, isIn (resultater)); assertThat (userTom, not (isIn (results))); }

5.2. Test Negation

Lad os derefter søge efter brugere efter deres fornavn ikke "john":

@Test offentlig ugyldighed givenFirstNameInverse_whenGettingListOfUsers_thenCorrect () {Node rootNode = ny RSQLParser (). Parse ("firstName! = John"); Specifikationsspecifikation = rootNode.accept (ny CustomRsqlVisitor ()); Listeresultater = repository.findAll (spec); assertThat (userTom, isIn (resultater)); assertThat (brugerJohn, ikke (isIn (resultater))); }

5.3. Test større end

Dernæst - vi vil søge efter brugere med alder bedre end "25”:

@ Test offentlig ugyldighed givenMinAge_whenGettingListOfUsers_thenCorrect () {Node rootNode = ny RSQLParser (). Parse ("alder> 25"); Specifikationsspecifikation = rootNode.accept (ny CustomRsqlVisitor ()); Listeresultater = repository.findAll (spec); assertThat (userTom, isIn (resultater)); assertThat (brugerJohn, ikke (isIn (resultater))); }

5.4. Test som

Dernæst - vi vil søge efter brugere med deres fornavn startende med “jo”:

@ Test offentlig ugyldighed givenFirstNamePrefix_whenGettingListOfUsers_thenCorrect () {Node rootNode = ny RSQLParser (). Parse ("firstName == jo *"); Specifikation spec = rootNode.accept (ny CustomRsqlVisitor ()); Listeresultater = repository.findAll (spec); assertThat (userJohn, isIn (resultater)); assertThat (userTom, not (isIn (results))); }

5.5. Prøve I

Dernæst - vi vil søge efter brugere deres fornavn er “John”Eller“jack“:

@Test offentlig ugyldighed givenListOfFirstName_whenGettingListOfUsers_thenCorrect () {Node rootNode = ny RSQLParser (). Parse ("firstName = in = (john, jack)"); Specifikationsspecifikation = rootNode.accept (ny CustomRsqlVisitor ()); Listeresultater = repository.findAll (spec); assertThat (userJohn, isIn (resultater)); assertThat (userTom, not (isIn (results))); }

6. UserController

Endelig - lad os binde det hele sammen med controlleren:

@RequestMapping (metode = RequestMethod.GET, værdi = "/ brugere") @ResponseBody offentlig Liste findAllByRsql (@RequestParam (værdi = "søg") String-søgning) {Node rootNode = ny RSQLParser (). Parse (søg); Specifikationsspecifikation = rootNode.accept (ny CustomRsqlVisitor ()); returner dao.findAll (spec); }

Her er en eksempel-URL:

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

Og svaret:

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

7. Konklusion

Denne vejledning illustrerede, hvordan man bygger et forespørgsel / søgesprog til en REST API uden at skulle genopfinde syntaksen og i stedet bruge FIQL / RSQL.

Det fuld implementering af denne artikel kan findes i GitHub-projektet - dette er et Maven-baseret projekt, så det skal være let at importere og køre som det er.

Næste » REST forespørgselssprog med Querydsl Web Support « Forrige REST-forespørgselssprog - Implementering ELLER Operation REST nederst

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN Persistens bund

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN

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