REST Query Language met RSQL
Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:
>> BEKIJK DE CURSUS Persistentie topIk heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:
>> BEKIJK DE CURSUS Dit artikel maakt deel uit van een reeks: • REST Query Language met Spring- en JPA-criteria• REST Query Language met Spring Data JPA-specificaties
• REST Query Language met Spring Data JPA en Querydsl
• REST Query Language - Geavanceerde zoekbewerkingen
• REST Query Language - OR-bewerking implementeren
• REST Query Language met RSQL (huidig artikel) • REST Query Language met Querydsl Web Support
1. Overzicht
In dit vijfde artikel van de serie illustreren we het bouwen van de REST API Query-taal met behulp van een coole bibliotheek - rsql-parser.
RSQL is een superset van de Feed Item Query Language (FIQL) - een schone en eenvoudige filtersyntaxis voor feeds; dus het past heel natuurlijk in een REST API. Laten we eerst een maven-afhankelijkheid aan de bibliotheek toevoegen: En ook definieer de hoofdentiteit we gaan door de voorbeelden heen werken - Gebruiker: De manier waarop RSQL-expressies intern worden weergegeven, is in de vorm van knooppunten en het bezoekerspatroon wordt gebruikt om de invoer te analyseren. Met dat in gedachten gaan we het RSQLBezoeker interface en maak onze eigen bezoekersimplementatie - CustomRsqlVisitor: Nu moeten we omgaan met persistentie en onze vraag construeren uit elk van deze knooppunten. We gaan de Spring Data JPA-specificaties gebruiken die we eerder hebben gebruikt - en we gaan een Specificatie bouwer aan construeer specificaties uit elk van deze knooppunten die we bezoeken: Merk op hoe: Voor een zoekopdracht 'naam == john" - we hebben: Bij het samenstellen van de query hebben we gebruik gemaakt van een Specificatie: Merk op hoe de specificatie generieke geneesmiddelen gebruikt en niet is gekoppeld aan een specifieke entiteit (zoals de gebruiker). Volgende - hier is onze opsomming 'RsqlSearchOperation“ die standaard rsql-parser-operators bevat: Laten we nu beginnen met het testen van onze nieuwe en flexibele operaties aan de hand van enkele realistische scenario's: Laten we eerst de gegevens initialiseren: Laten we nu de verschillende bewerkingen testen: In het volgende voorbeeld zoeken we gebruikers op basis van hun eerste en achternaam: Laten we vervolgens naar gebruikers zoeken die worden aangeduid met hun Voornaam niet "john": Vervolgens zullen we zoeken naar gebruikers met leeftijd groter dan "25”: Vervolgens zullen we zoeken naar gebruikers met hun Voornaam beginnend met "jo”: Vervolgens zullen we zoeken naar gebruikers hun Voornaam is "John"Of"jack“: Eindelijk - laten we het allemaal in verband brengen met de controller: Hier is een voorbeeld-URL: En het antwoord: Deze tutorial illustreerde hoe je een Query / Search Language voor een REST API opbouwt zonder de syntaxis opnieuw uit te vinden en in plaats daarvan FIQL / RSQL te gebruiken. De volledige implementatie van dit artikel is te vinden in het GitHub-project - dit is een op Maven gebaseerd project, dus het zou gemakkelijk te importeren en uit te voeren moeten zijn zoals het is.2. Voorbereidingen
cz.jirutka.rsql rsql-parser 2.1.0
@Entity openbare klasse Gebruiker {@Id @GeneratedValue (strategie = GenerationType.AUTO) privé Lange id; private String voornaam; private String achternaam; privé String-e-mail; privé int leeftijd; }
3. Analyseer het verzoek
openbare klasse CustomRsqlVisitor implementeert RSQLVisitor
openbare klasse GenericRsqlSpecBuilder {openbare specificatie createSpecification (knooppuntknooppunt) {if (knooppuntinstantie van LogicalNode) {terugkeer createSpecification ((LogicalNode) knooppunt); } if (knooppunt instanceof ComparisonNode) {return createSpecification ((ComparisonNode) knooppunt); } retourneer null; } openbare specificatie createSpecification (LogicalNode logicalNode) {Lijstspecificaties = logicalNode.getChildren () .stream () .map (knooppunt -> createSpecification (knooppunt)) .filter (Objects :: nonNull) .collect (Collectors.toList ()); Specificatie resultaat = specs.get (0); if (logicalNode.getOperator () == LogicalOperator.AND) {for (int i = 1; i <specs.size (); i ++) {result = Specification.where (resultaat) .en (specs.get (i)) ; }} else if (logicalNode.getOperator () == LogicalOperator.OR) {for (int i = 1; i <specs.size (); i ++) {result = Specification.where (resultaat) .or (specs.get ( ik)); }} resultaat retourneren; } openbare Specificatie createSpecification (ComparisonNode comparisonNode) {Specificatie resultaat = Specification.where (nieuwe GenericRsqlSpecification (comparisonNode.getSelector (), comparisonNode.getOperator (), comparisonNode.getArguments ())); resultaat teruggeven; }}
4. Maak een aangepast Specificatie
openbare klasse GenericRsqlSpecification implementeert Specification {private String-eigenschap; privé ComparisonOperator-operator; argumenten voor privélijst; @Override public Predicate toPredicate (Root root, CriteriaQuery-query, CriteriaBuilder-builder) {List args = castArguments (root); Object argument = args.get (0); switch (RsqlSearchOperation.getSimpleOperator (operator)) {case EQUAL: {if (argument instanceof String) {return builder.like (root.get (property), argument.toString (). replace ('*', '%')) ; } else if (argument == null) {return builder.isNull (root.get (eigenschap)); } else {return builder.equal (root.get (eigenschap), argument); }} geval NOT_EQUAL: {if (argument instanceof String) {return builder.notLike (root. get (property), argument.toString (). replace ('*', '%')); } else if (argument == null) {return builder.isNotNull (root.get (eigenschap)); } else {return builder.notEqual (root.get (eigenschap), argument); }} geval GREATER_THAN: {return builder.greaterThan (root. get (eigenschap), argument.toString ()); } geval GREATER_THAN_OR_EQUAL: {return builder.greaterThanOrEqualTo (root. get (eigenschap), argument.toString ()); } case LESS_THAN: {return builder.lessThan (root. get (eigenschap), argument.toString ()); } case LESS_THAN_OR_EQUAL: {return builder.lessThanOrEqualTo (root. get (eigenschap), argument.toString ()); } case IN: retourneer root.get (eigenschap) .in (args); case NOT_IN: return builder.not (root.get (property) .in (args)); } retourneer null; } privélijst castArguments (laatste root root) {Class type = root.get (eigenschap) .getJavaType (); Lijst args = arguments.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 ()); args teruggeven; } // standard constructor, getter, setter}
public 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); privé ComparisonOperator-operator; privé RsqlSearchOperation (operator ComparisonOperator) {this.operator = operator; } openbare statische RsqlSearchOperation getSimpleOperator (operator ComparisonOperator) {for (bewerking RsqlSearchOperation: waarden ()) {if (operation.getOperator () == operator) {retouroperatie; }} retourneer null; }}
5. Test zoekopdrachten
@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (classes = {PersistenceConfig.class}) @Transactional @TransactionConfiguration openbare klasse RsqlTest {@Autowired private UserRepository-repository; privé gebruiker userJohn; particuliere gebruiker userTom; @Before public void init () {userJohn = nieuwe gebruiker (); userJohn.setFirstName ("john"); userJohn.setLastName ("doe"); userJohn.setEmail ("[e-mail beschermd]"); userJohn.setAge (22); repository.save (userJohn); userTom = nieuwe gebruiker (); userTom.setFirstName ("tom"); userTom.setLastName ("doe"); userTom.setEmail ("[e-mail beschermd]"); userTom.setAge (26); repository.save (userTom); }}
5.1. Test gelijkheid
@Test openbare leegte gegevenFirstAndLastName_whenGettingListOfUsers_thenCorrect () {Node rootNode = nieuwe RSQLParser (). Parse ("firstName == john; lastName == doe"); Specificatie spec = rootNode.accept (nieuwe CustomRsqlVisitor ()); Lijstresultaten = repository.findAll (spec); assertThat (userJohn, isIn (resultaten)); assertThat (userTom, niet (isIn (resultaten))); }
5.2. Test ontkenning
@Test openbare ongeldige gegevenFirstNameInverse_whenGettingListOfUsers_thenCorrect () {Node rootNode = nieuwe RSQLParser (). Parse ("firstName! = John"); Specificatie spec = rootNode.accept (nieuwe CustomRsqlVisitor ()); Lijstresultaten = repository.findAll (spec); assertThat (userTom, isIn (resultaten)); assertThat (userJohn, not (isIn (results))); }
5.3. Test groter dan
@Test openbare ongeldig gegevenMinAge_whenGettingListOfUsers_thenCorrect () {Node rootNode = nieuwe RSQLParser (). Parse ("leeftijd> 25"); Specificatie spec = rootNode.accept (nieuwe CustomRsqlVisitor ()); Lijstresultaten = repository.findAll (spec); assertThat (userTom, isIn (resultaten)); assertThat (userJohn, not (isIn (results))); }
5.4. Test zoals
@Test openbare ongeldige gegevenFirstNamePrefix_whenGettingListOfUsers_thenCorrect () {Node rootNode = nieuwe RSQLParser (). Parse ("firstName == jo *"); Specificatie spec = rootNode.accept (nieuwe CustomRsqlVisitor ()); Lijstresultaten = repository.findAll (spec); assertThat (userJohn, isIn (resultaten)); assertThat (userTom, niet (isIn (resultaten))); }
5.5. Test IN
@Test openbare ongeldig gegevenListOfFirstName_whenGettingListOfUsers_thenCorrect () {Node rootNode = nieuwe RSQLParser (). Parse ("firstName = in = (john, jack)"); Specificatie spec = rootNode.accept (nieuwe CustomRsqlVisitor ()); Lijstresultaten = repository.findAll (spec); assertThat (userJohn, isIn (resultaten)); assertThat (userTom, niet (isIn (resultaten))); }
6. UserController
@RequestMapping (methode = RequestMethod.GET, value = "/ gebruikers") @ResponseBody openbare lijst findAllByRsql (@RequestParam (waarde = "zoeken") String zoeken) {Knooppunt rootNode = nieuwe RSQLParser (). Parse (zoeken); Specificatie spec = rootNode.accept (nieuwe CustomRsqlVisitor ()); retourneer dao.findAll (spec); }
// localhost: 8080 / users? search = firstName == jo *; leeftijd <25
[{"id": 1, "firstName": "john", "lastName": "doe", "email": "[email protected]", "age": 24}]
7. Conclusie
Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:
>> BEKIJK DE CURSUS Persistentie onderaan Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:
>> BEKIJK DE CURSUS