REST Query Language met Spring Data JPA-specificaties

Dit artikel maakt deel uit van een reeks: • REST Query Language met Spring- en JPA-criteria

• REST Query Language met Spring Data JPA-specificaties (huidig ​​artikel) • 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

• REST Query-taal met Querydsl-webondersteuning

1. Overzicht

In deze tutorial zullen we een Zoek / filter REST API met behulp van Spring Data JPA en specificaties.

We begonnen te kijken naar een zoektaal in het eerste artikel van deze serie - met een op JPA-criteria gebaseerde oplossing.

Dus - waarom een ​​zoektaal? Omdat - voor elke API die complex genoeg is - het doorzoeken / filteren van uw bronnen op zeer eenvoudige velden gewoon niet voldoende is. Een zoektaal is flexibeler en stelt u in staat om te filteren op precies de bronnen die u nodig heeft.

2. Gebruiker Entiteit

Ten eerste - laten we beginnen met een simpele Gebruiker entiteit voor onze zoek-API:

@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; // standaard getters en setters}

3. Filter gebruiken Specificatie

Laten we nu - laten we meteen ingaan op het meest interessante deel van het probleem - vragen stellen met aangepaste Spring Data JPA Specificaties.

We maken een Gebruikersspecificatie die implementeert de Specificatie interface en we gaan pass in onze eigen beperking om de eigenlijke vraag te construeren:

openbare klasse UserSpecification implementeert Specification {private SearchCriteria-criteria; @Override public Predicate toPredicate (Root root, CriteriaQuery-query, CriteriaBuilder-builder) {if (criteria.getOperation (). EqualsIgnoreCase (">")) {return builder.greaterThanOrEqualTo (root. Get (criteria.getKey ()), criteria. getValue (). toString ()); } else if (criteria.getOperation (). equalsIgnoreCase ("<")) {return builder.lessThanOrEqualTo (root. get (criteria.getKey ()), criteria.getValue (). toString ()); } else if (criteria.getOperation (). equalsIgnoreCase (":")) {if (root.get (criteria.getKey ()). getJavaType () == String.class) {return builder.like (root.get ( criteria.getKey ()), "%" + criteria.getValue () + "%"); } else {return builder.equal (root.get (criteria.getKey ()), criteria.getValue ()); }} retourneer null; }}

Zoals we kunnen zien - we creëren een Specificatie gebaseerd op enkele eenvoudige beperkingen die we vertegenwoordigen in de volgende "Zoekcriteria”Klasse:

openbare klasse SearchCriteria {privé String-sleutel; privé String-bewerking; waarde van het privé-object; }

De Zoekcriteria implementatie bevat een basisvoorstelling van een beperking - en het is gebaseerd op deze beperking dat we de query gaan construeren:

  • sleutel: de veldnaam - bijvoorbeeld Voornaam, leeftijd, … enz.
  • operatie: de operatie - bijvoorbeeld gelijkheid, minder dan, ... enz.
  • waarde: de veldwaarde - bijvoorbeeld john, 25, ... enz.

De implementatie is natuurlijk simplistisch en kan worden verbeterd; het is echter een solide basis voor de krachtige en flexibele operaties die we nodig hebben.

4. Het UserRepository

Vervolgens - laten we eens kijken naar het UserRepository; we breiden gewoon de JpaSpecificationExecutor om de nieuwe specificatie-API's op te halen:

openbare interface UserRepository breidt JpaRepository, JpaSpecificationExecutor {} uit

5. Test de zoekopdrachten

Laten we nu de nieuwe zoek-API testen.

Laten we eerst een paar gebruikers aanmaken zodat ze gereed zijn wanneer de tests worden uitgevoerd:

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (classes = {PersistenceJPAConfig.class}) @Transactional @TransactionConfiguration openbare klasse JPASpecificationsTest {@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); }}

Laten we vervolgens kijken hoe we gebruikers kunnen vinden met opgegeven achternaam:

@Test openbare ongeldige gegevenLast_whenGettingListOfUsers_thenCorrect () {Specificatie gebruikersspecificatie = nieuwe gebruikersspecificatie (nieuwe zoekcriteria ("achternaam", ":", "doe")); Lijstresultaten = repository.findAll (spec); assertThat (userJohn, isIn (resultaten)); assertThat (userTom, isIn (resultaten)); }

Laten we nu eens kijken hoe we een gebruiker kunnen vinden met het gegeven zowel voor- als achternaam:

@Test public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect () {UserSpecification spec1 = nieuwe UserSpecification (nieuwe SearchCriteria ("firstName", ":", "john")); UserSpecification spec2 = nieuwe UserSpecification (nieuwe zoekcriteria ("lastName", ":", "doe")); Lijstresultaten = repository.findAll (Specification.where (spec1) .and (spec2)); assertThat (userJohn, isIn (resultaten)); assertThat (userTom, niet (isIn (resultaten))); }

Opmerking: we gebruikten 'waar"En"en" naar combineren Specificaties.

Laten we vervolgens kijken hoe we een gebruiker kunnen vinden met het gegeven zowel achternaam als minimumleeftijd:

@Test openbare ongeldige gegevenLastAndAge_whenGettingListOfUsers_thenCorrect () {UserSpecification spec1 = nieuwe gebruikersspecificatie (nieuwe zoekcriteria ("leeftijd", ">", "25")); UserSpecification spec2 = nieuwe UserSpecification (nieuwe zoekcriteria ("lastName", ":", "doe")); Lijstresultaten = repository.findAll (Specification.where (spec1) .and (spec2)); assertThat (userTom, isIn (resultaten)); assertThat (userJohn, not (isIn (results))); }

Laten we nu eens kijken hoe we kunnen zoeken naar Gebruiker dat bestaat niet echt:

@Test openbare ongeldig gegevenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect () {UserSpecification spec1 = nieuwe UserSpecification (nieuwe SearchCriteria ("firstName", ":", "Adam")); UserSpecification spec2 = nieuwe UserSpecification (nieuwe zoekcriteria ("lastName", ":", "Fox")); Lijstresultaten = repository.findAll (Specification.where (spec1) .and (spec2)); assertThat (userJohn, not (isIn (results))); assertThat (userTom, niet (isIn (resultaten))); }

Eindelijk - laten we eens kijken hoe we een Gebruiker slechts een deel van de voornaam gegeven:

@Test openbare ongeldigheid gegevenPartialFirst_whenGettingListOfUsers_thenCorrect () {UserSpecification spec = nieuwe UserSpecification (nieuwe SearchCriteria ("firstName", ":", "jo")); Lijstresultaten = repository.findAll (spec); assertThat (userJohn, isIn (resultaten)); assertThat (userTom, niet (isIn (resultaten))); }

6. Combineer Specificaties

Vervolgens - laten we eens kijken hoe we onze gewoonte combineren Specificaties om meerdere beperkingen te gebruiken en te filteren op basis van meerdere criteria.

We gaan een bouwer implementeren - GebruikerSpecificatiesBuilder - om gemakkelijk en vloeiend te combineren Specificaties:

openbare klasse UserSpecificationsBuilder {privé finalelijstparameters; openbare UserSpecificationsBuilder () {params = new ArrayList (); } openbare UserSpecificationsBuilder met (String-sleutel, String-bewerking, Objectwaarde) {params.add (nieuwe SearchCriteria (sleutel, bewerking, waarde)); dit teruggeven; } openbare specificatie build () {if (params.size () == 0) {return null; } Lijstspecificaties = params.stream () .map (UserSpecification :: new) .collect (Collectors.toList ()); Specificatie resultaat = specs.get (0); for (int i = 1; i <params.size (); i ++) {result = params.get (i) .isOrPredicate ()? Specification.where (resultaat). Of (specs.get (i)): Specification.where (resultaat) .en (specs.get (i)); } resultaat teruggeven; }}

7. UserController

Ten slotte - laten we deze nieuwe zoek- / filterfunctionaliteit voor persistentie gebruiken en stel de REST API in - door een UserController met een simpele zoeken operatie:

@Controller openbare klasse UserController {@Autowired privé UserRepository-opslagplaats; @RequestMapping (methode = RequestMethod.GET, value = "/ gebruikers") @ResponseBody openbare lijst zoeken (@RequestParam (waarde = "zoeken") String zoeken) {UserSpecificationsBuilder builder = nieuwe UserSpecificationsBuilder (); Patroonpatroon = Pattern.compile ("(\ w +?) (: |) (\ w +?),"); Matcher matcher = pattern.matcher (zoek + ","); while (matcher.find ()) {builder.with (matcher.group (1), matcher.group (2), matcher.group (3)); } Specificatie spec = builder.build (); retourneer repo.findAll (spec); }}

Merk op dat om andere niet-Engelse systemen te ondersteunen, de Patroon object kan worden gewijzigd als:

Patroonpatroon = Pattern.compile ("(\ w +?) (: |) (\ w +?),", Pattern.UNICODE_CHARACTER_CLASS);

Hier is een voorbeeld van een test-URL om de API te testen:

// localhost: 8080 / gebruikers? search = achternaam: doe, leeftijd> 25

En het antwoord:

[{"id": 2, "firstName": "tom", "lastName": "doe", "email": "[email protected]", "age": 26}]

Omdat de zoekopdrachten zijn gesplitst door een "," in onze Patroon De zoektermen mogen dit teken bijvoorbeeld niet bevatten. Het patroon komt ook niet overeen met witruimte.

Als we willen zoeken naar waarden die komma's bevatten, kunnen we overwegen om een ​​ander scheidingsteken te gebruiken, zoals ";".

Een andere optie zou zijn om het patroon te wijzigen om naar waarden tussen aanhalingstekens te zoeken en deze vervolgens van de zoekterm te verwijderen:

Patroonpatroon = Pattern.compile ("(\ w +?) (: |) (\" ([^ \ "] +) \") ");

8. Conclusie

Deze tutorial behandelde een eenvoudige implementatie die de basis kan zijn van een krachtige REST-querytaal. We hebben goed gebruik gemaakt van Spring Data Specificaties om ervoor te zorgen dat we de API uit de buurt van het domein houden en hebben de mogelijkheid om vele andere soorten bewerkingen uit te voeren.

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.

De volgende » REST Query Language met Spring Data JPA en Querydsl « Vorige REST-querytaal met Spring- en JPA-criteria