REST Query Language met Spring Data JPA en Querydsl

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 (huidig ​​artikel) • 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 zelfstudie kijken we naar het bouwen van een zoektaal voor een REST API met Spring Data JPA en Querydsl.

In de eerste twee artikelen van deze serie hebben we dezelfde zoek- / filterfunctionaliteit gebouwd met behulp van JPA-criteria en Spring Data JPA-specificaties.

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

2. Querydsl-configuratie

Laten we eerst eens kijken hoe we ons project kunnen configureren om Querydsl te gebruiken.

We moeten de volgende afhankelijkheden toevoegen aan pom.xml:

 com.querydsl querydsl-apt 4.2.2 com.querydsl querydsl-jpa 4.2.2 

We moeten ook de APT - Annotation processing tool - plug-in als volgt configureren:

 com.mysema.maven apt-maven-plugin 1.1.3 proces doel / gegenereerde-bronnen / java com.mysema.query.apt.jpa.JPAAnnotationProcessor 

Dit genereert de Q-types voor onze entiteiten.

3. Het Mijngebruiker Entiteit

Vervolgens - laten we eens kijken naar de "Mijngebruiker"Entiteit die we gaan gebruiken in onze zoek-API:

@Entity openbare klasse MyUser {@Id @GeneratedValue (strategy = GenerationType.AUTO) privé Lange id; private String voornaam; private String achternaam; privé String-e-mail; privé int leeftijd; }

4. Aangepast Predicaat With PathBuilder

Laten we nu een aangepast Predikaat gebaseerd op enkele willekeurige beperkingen.

We gebruiken PathBuilder hier in plaats van de automatisch gegenereerde Q-types omdat we paden dynamisch moeten creëren voor meer abstract gebruik:

openbare klasse MyUserPredicate {privé SearchCriteria-criteria; openbare BooleanExpression getPredicate () {PathBuilder entityPath = nieuwe PathBuilder (MyUser.class, "gebruiker"); if (isNumeric (criteria.getValue (). toString ())) {NumberPath path = entityPath.getNumber (criteria.getKey (), Integer.class); int waarde = Integer.parseInt (criteria.getValue (). toString ()); switch (criteria.getOperation ()) {case ":": return path.eq (waarde); case ">": return path.goe (waarde); case "<": return path.loe (waarde); }} else {StringPath path = entityPath.getString (criteria.getKey ()); if (criteria.getOperation (). equalsIgnoreCase (":")) {retourpad.containsIgnoreCase (criteria.getValue (). toString ()); }} retourneer null; }}

Merk op hoe de implementatie van het predikaat is generiek omgaan met meerdere soorten bewerkingen. Dit komt doordat de querytaal per definitie een open taal is waarin u mogelijk op elk veld kunt filteren, met behulp van elke ondersteunde bewerking.

Om dat soort open filtercriteria weer te geven, gebruiken we een eenvoudige maar vrij flexibele implementatie - Zoekcriteria:

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

De Zoekcriteria bevat de details die we nodig hebben om een ​​beperking te vertegenwoordigen:

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

5. MyUserRepository

Laten we nu eens kijken naar onze MyUserRepository.

We hebben onze nodig MyUserRepository uitbreiden QuerydslPredicateExecutor zodat we kunnen gebruiken Predikaten later om zoekresultaten te filteren:

openbare interface MyUserRepository breidt JpaRepository, QuerydslPredicateExecutor, QuerydslBinderCustomizer {@Override standaard openbare leegte aanpassen uit (QuerydslBindings bindings, QMyUser root) {bindings.bind (String.class) .first ((SingleValueBinding) StringExpressionase :: bevat) StringExpressionase :: bevat); bindings.exclusief (root.email); }}

Merk op dat we hier het gegenereerde Q-type gebruiken voor de Mijngebruiker entiteit, die zal worden genoemd QMyUser.

6. Combineer Predikaten

Nu - laten we eens kijken naar het combineren van predikaten om meerdere beperkingen te gebruiken bij het filteren van resultaten.

In het volgende voorbeeld - we werken met een bouwer - MyUserPredicatesBuilder - combineren Predikaten:

openbare klasse MyUserPredicatesBuilder {privélijstparameters; openbare MyUserPredicatesBuilder () {params = nieuwe ArrayList (); } openbare MyUserPredicatesBuilder met (String-sleutel, String-bewerking, Objectwaarde) {params.add (nieuwe zoekcriteria (sleutel, bewerking, waarde)); dit teruggeven; } openbare BooleanExpression build () {if (params.size () == 0) {return null; } Lijst predikaten = params.stream (). Map (param -> {MyUserPredicate predikaat = nieuw MyUserPredicate (param); return predicate.getPredicate ();}). Filter (Objects :: nonNull) .collect (Collectors.toList () ); BooleanExpression resultaat = Expressions.asBoolean (true) .isTrue (); for (BooleanExpression predikaat: predikaten) {resultaat = resultaat.en (predikaat); } resultaat teruggeven; }}

7. Test de zoekopdrachten

Vervolgens gaan we onze zoek-API testen.

We beginnen met het initialiseren van de database met een paar gebruikers - om deze klaar en beschikbaar te hebben om te testen:

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (classes = {PersistenceConfig.class}) @Transactional @Rollback openbare klasse JPAQuerydslIntegrationTest {@Autowired privé MyUserRepository-opslagplaats; privé MyUser userJohn; privé MyUser userTom; @Before public void init () {userJohn = nieuwe MyUser (); userJohn.setFirstName ("John"); userJohn.setLastName ("Doe"); userJohn.setEmail ("[e-mail beschermd]"); userJohn.setAge (22); repo.save (userJohn); userTom = nieuwe MyUser (); userTom.setFirstName ("Tom"); userTom.setLastName ("Doe"); userTom.setEmail ("[e-mail beschermd]"); userTom.setAge (26); repo.save (userTom); }}

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

@Test openbare ongeldige gegevenLast_whenGettingListOfUsers_thenCorrect () {MyUserPredicatesBuilder builder = nieuwe MyUserPredicatesBuilder (). Met ("lastName", ":", "Doe"); Herhaalbare resultaten = repo.findAll (builder.build ()); assertThat (resultaten, bevatInAnyOrder (userJohn, userTom)); }

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

@Test openbare leegte gegevenFirstAndLastName_whenGettingListOfUsers_thenCorrect () {MyUserPredicatesBuilder builder = nieuwe MyUserPredicatesBuilder () .with ("firstName", ":", "John"). With ("lastName", ":", "Doe"); Herhaalbare resultaten = repo.findAll (builder.build ()); assertThat (results, bevat (userJohn)); assertThat (resultaten, niet (bevat (userTom))); }

Laten we vervolgens kijken hoe we een gebruiker kunnen vinden met opgegeven zowel achternaam als minimumleeftijd

@Test openbare ongeldig gegevenLastAndAge_whenGettingListOfUsers_thenCorrect () {MyUserPredicatesBuilder builder = nieuwe MyUserPredicatesBuilder () .with ("lastName", ":", "Doe"). With ("age", ">", "25"); Herhaalbare resultaten = repo.findAll (builder.build ()); assertThat (results, bevat (userTom)); assertThat (resultaten, niet (bevat (userJohn))); }

Laten we nu eens kijken hoe we kunnen zoeken naar Mijngebruiker dat bestaat eigenlijk niet:

@Test openbare ongeldig gegevenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect () {MyUserPredicatesBuilder builder = nieuwe MyUserPredicatesBuilder () .with ("firstName", ":", "Adam"). With ("lastName", ":", "Fox"); Herhaalbare resultaten = repo.findAll (builder.build ()); assertThat (results, emptyIterable ()); }

Eindelijk - laten we eens kijken hoe we een Mijngebruiker slechts een deel van de voornaam gegeven - zoals in het volgende voorbeeld:

@Test openbare ongeldig gegevenPartialFirst_whenGettingListOfUsers_thenCorrect () {MyUserPredicatesBuilder builder = nieuwe MyUserPredicatesBuilder (). Met ("firstName", ":", "jo"); Herhaalbare resultaten = repo.findAll (builder.build ()); assertThat (results, bevat (userJohn)); assertThat (resultaten, niet (bevat (userTom))); }

8. UserController

Laten we tot slot alles samenvoegen en de REST API bouwen.

We definiëren een UserController dat definieert een eenvoudige methode vind alle() met een "zoeken“Parameter die moet worden doorgegeven in de querytekenreeks:

@Controller openbare klasse UserController {@Autowired privé MyUserRepository myUserRepository; @RequestMapping (methode = RequestMethod.GET, value = "/ myusers") @ResponseBody openbare herhaalbare zoekopdracht (@RequestParam (waarde = "zoeken") String zoeken) {MyUserPredicatesBuilder builder = nieuwe MyUserPredicatesBuilder (); if (zoek! = null) {Patroonpatroon = Patroon.compile ("(\ w +?) (: |) (\ w +?),"); Matcher matcher = pattern.matcher (zoek + ","); while (matcher.find ()) {builder.with (matcher.group (1), matcher.group (2), matcher.group (3)); }} BooleanExpression exp = builder.build (); retourneer myUserRepository.findAll (exp); }}

Hier is een voorbeeld van een snelle test-URL:

// localhost: 8080 / myusers? search = lastName: doe, leeftijd> 25

En het antwoord:

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

9. Conclusie

Dit derde artikel behandelde de eerste stappen van het bouwen van een querytaal voor een REST API, goed gebruik makend van de Querydsl-bibliotheek.

De implementatie is natuurlijk vroeg, maar kan gemakkelijk worden geëvolueerd om aanvullende operaties te ondersteunen.

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 - Geavanceerde zoekbewerkingen « Vorige REST Query Language met Spring Data JPA-specificaties