REST upitni jezik s RSQL-om

OSTALO Vrh

Upravo sam najavio novo Uči proljeće tečaj, usredotočen na osnove Spring 5 i Spring Boot 2:

>> PROVJERITE TEČAJ Vrh postojanosti

Upravo sam najavio novo Uči proljeće tečaj, usredotočen na osnove Spring 5 i Spring Boot 2:

>> PROVJERITE TEČAJ Ovaj je članak dio serije: • REST jezik upita s proljetnim i JPA kriterijima

• REST jezik upita s Spring Data JPA specifikacijama

• REST jezik upita s Spring Data JPA i Querydsl

• REST jezik upita - Napredno pretraživanje

• REST upitni jezik - implementacija ILI operacija

• REST upitni jezik s RSQL-om (trenutni članak) • REST upitni jezik s web-podrškom Querydsl

1. Pregled

U ovom petom članku serije ilustrirat ćemo izgradnju jezika upita REST API uz pomoć cool knjižnica - rsql-parser.

RSQL je super set jezika upita stavki feed-a (FIQL) - čista i jednostavna sintaksa filtra za feedove; tako da se sasvim prirodno uklapa u REST API.

2. Pripreme

Prvo, dodamo maven ovisnost u knjižnicu:

 cz.jirutka.rsql rsql-parser 2.1.0 

I također definirati glavni entitet radit ćemo na svim primjerima - Korisnik:

@Entity javna klasa Korisnik {@Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; private String firstName; private String lastName; privatni String e-mail; privatno int doba; }

3. Analizirajte zahtjev

Način na koji su RSQL izrazi interno predstavljeni u obliku je čvorova, a obrazac posjetitelja koristi se za raščlanjivanje ulaza.

Imajući to na umu, mi ćemo implementirati RSQLVisitor sučelje i stvorimo vlastitu implementaciju posjetitelja - CustomRsqlVisitor:

javna klasa CustomRsqlVisitor implementira RSQLVisitor {privatni graditelj GenericRsqlSpecBuilder; javni CustomRsqlVisitor () {graditelj = novi GenericRsqlSpecBuilder (); } @Override public Specification visit (čvor AndNode, void param) {return builder.createSpecification (čvor); } @Override public Specification visit (OrNode nod, Void param) {return builder.createSpecification (node); } @Override public Specification posjet (čvor ComparisonNode, void params) {return builder.createSecification (čvor); }}

Sada se moramo nositi s upornošću i konstruirati svoj upit iz svakog od tih čvorova.

Koristit ćemo Spring Data JPA specifikacije koje smo koristili prije - i implementirat ćemo a Specifikacija graditelj do konstruirajte Specifikacije iz svakog od ovih čvorova koje posjetimo:

javna klasa GenericRsqlSpecBuilder {javna specifikacija createSpecification (čvor čvora) {if (čvor instanceof LogicalNode) {return createSpecification ((LogicalNode) čvor); } if (čvor instanceof ComparisonNode) {return createSpecification ((ComparisonNode) čvor); } return null; } javna specifikacija createSpecification (LogicalNode logicNode) {Specifikacije popisa = logicNode.getChildren () .stream () .map (node ​​-> createSpecification (node)) .filter (Objects :: nonNull) .collect (Collectors.toList ()); Rezultat specifikacije = specs.get (0); if (logicNode.getOperator () == LogicalOperator.AND) {for (int i = 1; i <specs.size (); i ++) {rezultat = Specifikacija.gdje (rezultat) .i (specs.get (i)) ; }} else if (logicNode.getOperator () == LogicalOperator.OR) {for (int i = 1; i <specs.size (); i ++) {result = Specification.where (result) .or (specs.get ( i)); }} vratiti rezultat; } javna specifikacija createSpecification (ComparisonNode порівняvanjeNode) {Rezultat specifikacije = Specifikacija.gdje (nova GenericRsqlSpecification (порівняvanjeNode.getSelector (), uspoređivanjeNode.getOperator (), uspoređivanjeNode.getArguments ())); povratni rezultat; }}

Imajte na umu kako:

  • Logički čvor je I/ILIČvor i ima više djece
  • ComparisonNode nema djece i drži ga Selektor, operator i argumenti

Na primjer, za upit "ime == Ivan" - imamo:

  1. Selektor: "Ime"
  2. Operater: “==”
  3. Argumenti:[Ivan]

4. Stvorite prilagođeno Specifikacija

Prilikom izrade upita koristili smo a Specifikacija:

javna klasa GenericRsqlSpecification implementira specifikaciju {private String property; privatni operator ComparisonOperator; argumenti privatnog popisa; @Override public Predicate toPredicate (root root, CriteriaQuery query, CriteriaBuilder builder) {List args = castArguments (root); Argument objekta = args.get (0); switch (RsqlSearchOperation.getSimpleOperator (operator)) {case EQUAL: {if (argument instanceof String) {return builder.like (root.get (svojstvo), argument.toString (). replace ('*', '%')) ; } else if (argument == null) {return builder.isNull (root.get (svojstvo)); } else {vratiti graditelj.equal (root.get (svojstvo), argument); }} slučaj NOT_EQUAL: {if (argument instanceof String) {return builder.notLike (root. get (svojstvo), argument.toString (). replace ('*', '%')); } else if (argument == null) {return builder.isNotNull (root.get (svojstvo)); } else {return builder.notEqual (root.get (svojstvo), argument); }} case GREATER_THAN: {return builder.greaterThan (root. get (property), argument.toString ()); } slučaj GREATER_THAN_OR_EQUAL: {return builder.greaterThanOrEqualTo (root. get (svojstvo), argument.toString ()); } case LESS_THAN: {return builder.lessThan (root. get (property), argument.toString ()); } slučaj LESS_THAN_OR_EQUAL: {return builder.lessThanOrEqualTo (root. get (svojstvo), argument.toString ()); } case IN: vratiti root.get (svojstvo) .in (args); case NOT_IN: return builder.not (root.get (svojstvo) .in (args)); } return null; } privatni popis castArguments (konačni korijenski korijen) {Vrsta klase = root.get (svojstvo) .getJavaType (); Navedi args = argument.stream (). Map (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 ()); vratiti argumente; } // standardni konstruktor, getter, setter}

Primijetite kako se specifikacija koristi generičkim lijekovima i nije vezana uz bilo koji entitet (kao što je Korisnik).

Dalje - evo našeg nabrajati “RsqlSearchOperation koji sadrži zadane rsql-parser operatore:

javni enum {RsqlSearchOperation jednako (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); privatni operator ComparisonOperator; private RsqlSearchOperation (operator ComparisonOperator) {this.operator = operator; } javni statički RsqlSearchOperation getSimpleOperator (operator ComparisonOperator) {za (operacija RsqlSearchOperation: values ​​()) {if (operation.getOperator () == operator) {return operation; }} return null; }}

5. Testirajte upite za pretraživanje

Počnimo sada testirati naše nove i fleksibilne operacije kroz neke stvarne scenarije:

Prvo - inicijalizirajmo podatke:

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (classes = {PersistenceConfig.class}) @Transactional @TransactionConfiguration javna klasa RsqlTest {@Autowired private UserRepository repository; privatni korisnik userJohn; privatni korisnik userTom; @Prije javne void init () {userJohn = novi korisnik (); userJohn.setFirstName ("john"); userJohn.setLastName ("srna"); userJohn.setEmail ("[e-pošta zaštićena]"); userJohn.setAge (22); repository.save (userJohn); userTom = novi korisnik (); userTom.setFirstName ("tom"); userTom.setLastName ("srna"); userTom.setEmail ("[e-pošta zaštićena]"); userTom.setAge (26); repository.save (userTom); }}

Sada testirajmo različite operacije:

5.1. Testirajte jednakost

U sljedećem primjeru - tražit ćemo korisnike prema njihovim prvi i prezime:

@Test public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect () {Node rootNode = new RSQLParser (). Parse ("firstName == john; lastName == doe"); Specifikacija spec = rootNode.accept (novi CustomRsqlVisitor ()); Popis rezultata = repozitorij.findAll (spec.); assertThat (userJohn, isIn (rezultati)); assertThat (userTom, ne (isIn (rezultati))); }

5.2. Test negacija

Dalje, potražimo korisnike koji prema njihovim ime ne "john":

@Test javna praznina givenFirstNameInverse_whenGettingListOfUsers_thenCorrect () {Node rootNode = new RSQLParser (). Parse ("firstName! = John"); Specifikacija spec = rootNode.accept (novi CustomRsqlVisitor ()); Popis rezultata = repozitorij.findAll (spec.); assertThat (userTom, isIn (rezultati)); assertThat (userJohn, ne (isIn (rezultati))); }

5.3. Testirajte veće od

Dalje - tražit ćemo korisnike s dob veće od “25”:

@Test javna praznina givenMinAge_whenGettingListOfUsers_thenCorrect () {Node rootNode = new RSQLParser (). Parse ("age> 25"); Specifikacija spec = rootNode.accept (novi CustomRsqlVisitor ()); Popis rezultata = repozitorij.findAll (spec.); assertThat (userTom, isIn (rezultati)); assertThat (userJohn, ne (isIn (rezultati))); }

5.4. Testirajte lajk

Dalje - tražit ćemo korisnike sa svojim ime počevši sa "jo”:

@Test javna praznina givenFirstNamePrefix_whenGettingListOfUsers_thenCorrect () {Node rootNode = new RSQLParser (). Parse ("firstName == jo *"); Specifikacija spec = rootNode.accept (novi CustomRsqlVisitor ()); Popis rezultata = repozitorij.findAll (spec.); assertThat (userJohn, isIn (rezultati)); assertThat (userTom, ne (isIn (rezultati))); }

5.5. Test U

Dalje - tražit ćemo korisnike njihove ime je "Ivan" ili "utičnica“:

@Test javna praznina givenListOfFirstName_whenGettingListOfUsers_thenCorrect () {Node rootNode = new RSQLParser (). Parse ("firstName = in = (john, jack)"); Specifikacija spec = rootNode.accept (novi CustomRsqlVisitor ()); Popis rezultata = repozitorij.findAll (spec.); assertThat (userJohn, isIn (rezultati)); assertThat (userTom, ne (isIn (rezultati))); }

6. UserController

Napokon - povežimo sve s kontrolerom:

@RequestMapping (method = RequestMethod.GET, value = "/ users") @ResponseBody javni popis findAllByRsql (@RequestParam (value = "search") String search) {Node rootNode = new RSQLParser (). Parse (search); Specifikacija spec = rootNode.accept (novi CustomRsqlVisitor ()); return dao.findAll (spec); }

Evo primjera URL-a:

// localhost: 8080 / users? search = firstName == jo *; dob <25

I odgovor:

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

7. Zaključak

Ovaj je vodič ilustrirao kako izraditi jezik upita / pretraživanja za REST API bez potrebe za ponovnim izmišljanjem sintakse i umjesto toga pomoću FIQL / RSQL.

The puna provedba ovog članka može se naći u projektu GitHub - ovo je projekt zasnovan na Mavenu, pa bi ga trebalo lako uvesti i pokrenuti kakav jest.

Sljedeći » OSTALI jezik upita s web podrškom Querydsl « Prethodni jezik upita REST - Implementacija ILI operacija REST dno

Upravo sam najavio novo Uči proljeće tečaj, usredotočen na osnove Spring 5 i Spring Boot 2:

>> PROVJERITE TEČAJ Dno postojanosti

Upravo sam najavio novo Uči proljeće tečaj, usredotočen na osnove Spring 5 i Spring Boot 2:

>> PROVJERITE TEČAJ