REST-Abfragesprache mit Spring Data JPA und Querydsl

Dieser Artikel ist Teil einer Reihe: • REST-Abfragesprache mit Spring- und JPA-Kriterien

• REST-Abfragesprache mit Spring Data JPA-Spezifikationen

• REST-Abfragesprache mit Spring Data JPA und Querydsl (aktueller Artikel). • REST-Abfragesprache - Erweiterte Suchvorgänge

• REST-Abfragesprache - Implementierung einer ODER-Operation

• REST-Abfragesprache mit RSQL

• REST-Abfragesprache mit Querydsl-Webunterstützung

1. Übersicht

In diesem Tutorial möchten wir eine Abfragesprache für eine REST-API mit Spring Data JPA und Querydsl erstellen .

In den ersten beiden Artikeln dieser Reihe haben wir dieselbe Such- / Filterfunktion unter Verwendung von JPA-Kriterien und Spring Data JPA-Spezifikationen erstellt.

Also - warum eine Abfragesprache? Weil - für jede ausreichend komplexe API - das Suchen / Filtern Ihrer Ressourcen nach sehr einfachen Feldern einfach nicht ausreicht. Eine Abfragesprache ist flexibler und ermöglicht es Ihnen, genau auf die benötigten Ressourcen zu filtern.

2. Querydsl-Konfiguration

Lassen Sie uns zunächst sehen, wie Sie unser Projekt für die Verwendung von Querydsl konfigurieren.

Wir müssen die folgenden Abhängigkeiten zu pom.xml hinzufügen :

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

Wir müssen auch das APT - Annotation Processing Tool - Plugin wie folgt konfigurieren:

 com.mysema.maven apt-maven-plugin 1.1.3    process   target/generated-sources/java com.mysema.query.apt.jpa.JPAAnnotationProcessor    

Dadurch werden die Q-Typen für unsere Entitäten generiert.

3. Die MyUser- Entität

Als nächstes werfen wir einen Blick auf die Entität " MyUser ", die wir in unserer Such-API verwenden werden:

@Entity public class MyUser { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String firstName; private String lastName; private String email; private int age; }

4. Benutzerdefiniertes Prädikat mit PathBuilder

Jetzt erstellen wir ein benutzerdefiniertes Prädikat, das auf beliebigen Einschränkungen basiert.

Wir verwenden hier PathBuilder anstelle der automatisch generierten Q-Typen, da wir Pfade dynamisch erstellen müssen, um eine abstraktere Verwendung zu ermöglichen:

public class MyUserPredicate { private SearchCriteria criteria; public BooleanExpression getPredicate() { PathBuilder entityPath = new PathBuilder(MyUser.class, "user"); if (isNumeric(criteria.getValue().toString())) { NumberPath path = entityPath.getNumber(criteria.getKey(), Integer.class); int value = Integer.parseInt(criteria.getValue().toString()); switch (criteria.getOperation()) { case ":": return path.eq(value); case ">": return path.goe(value); case "<": return path.loe(value); } } else { StringPath path = entityPath.getString(criteria.getKey()); if (criteria.getOperation().equalsIgnoreCase(":")) { return path.containsIgnoreCase(criteria.getValue().toString()); } } return null; } }

Beachten Sie, wie die Implementierung des Prädikats generisch mit mehreren Arten von Operationen umgeht . Dies liegt daran, dass die Abfragesprache per Definition eine offene Sprache ist, in der Sie mithilfe einer unterstützten Operation möglicherweise nach jedem Feld filtern können.

Um diese Art von offenen Filterkriterien darzustellen, verwenden wir eine einfache, aber recht flexible Implementierung - SearchCriteria :

public class SearchCriteria { private String key; private String operation; private Object value; }

Die SearchCriteria enthält die Details, die wir benötigen, um eine Einschränkung darzustellen:

  • Schlüssel : der Feldname - zum Beispiel: Vorname , Alter , ... usw.
  • Operation : die Operation - zum Beispiel: Gleichheit, kleiner als, ... usw.
  • value : der Feldwert - zum Beispiel: john, 25,… etc.

5. MyUserRepository

Schauen wir uns jetzt unser MyUserRepository an .

Wir müssen unsere MyUserRepository zu erweitern QuerydslPredicateExecutor so dass wir verwenden können Prädikate später zu filtern Ergebnisse:

public interface MyUserRepository extends JpaRepository, QuerydslPredicateExecutor, QuerydslBinderCustomizer { @Override default public void customize( QuerydslBindings bindings, QMyUser root) { bindings.bind(String.class) .first((SingleValueBinding) StringExpression::containsIgnoreCase); bindings.excluding(root.email); } }

Beachten Sie, dass wir hier den generierten Q-Typ für die MyUser- Entität verwenden, der QMyUser heißt.

6. Prädikate kombinieren

Weiter - Schauen wir uns an, wie Prädikate kombiniert werden, um mehrere Einschränkungen bei der Ergebnisfilterung zu verwenden.

Im folgenden Beispiel arbeiten wir mit einem Builder - MyUserPredicatesBuilder - zusammen, um Prädikate zu kombinieren :

public class MyUserPredicatesBuilder { private List params; public MyUserPredicatesBuilder() { params = new ArrayList(); } public MyUserPredicatesBuilder with( String key, String operation, Object value) { params.add(new SearchCriteria(key, operation, value)); return this; } public BooleanExpression build() { if (params.size() == 0) { return null; } List predicates = params.stream().map(param -> { MyUserPredicate predicate = new MyUserPredicate(param); return predicate.getPredicate(); }).filter(Objects::nonNull).collect(Collectors.toList()); BooleanExpression result = Expressions.asBoolean(true).isTrue(); for (BooleanExpression predicate : predicates) { result = result.and(predicate); } return result; } }

7. Testen Sie die Suchanfragen

Als nächstes testen wir unsere Such-API.

Wir beginnen mit der Initialisierung der Datenbank mit einigen Benutzern, damit diese zum Testen bereitstehen:

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { PersistenceConfig.class }) @Transactional @Rollback public class JPAQuerydslIntegrationTest { @Autowired private MyUserRepository repo; private MyUser userJohn; private MyUser userTom; @Before public void init() { userJohn = new MyUser(); userJohn.setFirstName("John"); userJohn.setLastName("Doe"); userJohn.setEmail("[email protected]"); userJohn.setAge(22); repo.save(userJohn); userTom = new MyUser(); userTom.setFirstName("Tom"); userTom.setLastName("Doe"); userTom.setEmail("[email protected]"); userTom.setAge(26); repo.save(userTom); } }

Als nächstes sehen wir uns an, wie Sie Benutzer mit dem angegebenen Nachnamen finden :

@Test public void givenLast_whenGettingListOfUsers_thenCorrect() { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("lastName", ":", "Doe"); Iterable results = repo.findAll(builder.build()); assertThat(results, containsInAnyOrder(userJohn, userTom)); }

Lassen Sie uns nun sehen, wie Sie einen Benutzer mit Vor- und Nachnamen finden :

@Test public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder() .with("firstName", ":", "John").with("lastName", ":", "Doe"); Iterable results = repo.findAll(builder.build()); assertThat(results, contains(userJohn)); assertThat(results, not(contains(userTom))); }

Next, let’s see how to find user with given both last name and minimum age

@Test public void givenLastAndAge_whenGettingListOfUsers_thenCorrect() { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder() .with("lastName", ":", "Doe").with("age", ">", "25"); Iterable results = repo.findAll(builder.build()); assertThat(results, contains(userTom)); assertThat(results, not(contains(userJohn))); }

Now, let’s see how to search for MyUser that doesn’t actually exist:

@Test public void givenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect() { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder() .with("firstName", ":", "Adam").with("lastName", ":", "Fox"); Iterable results = repo.findAll(builder.build()); assertThat(results, emptyIterable()); }

Finally – let’s see how to find a MyUser given only part of the first name – as in the following example:

@Test public void givenPartialFirst_whenGettingListOfUsers_thenCorrect() { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("firstName", ":", "jo"); Iterable results = repo.findAll(builder.build()); assertThat(results, contains(userJohn)); assertThat(results, not(contains(userTom))); }

8. UserController

Finally, let's put everything together and build the REST API.

We're defining a UserController that defines a simple method findAll() with a “search“ parameter to pass in the query string:

@Controller public class UserController { @Autowired private MyUserRepository myUserRepository; @RequestMapping(method = RequestMethod.GET, value = "/myusers") @ResponseBody public Iterable search(@RequestParam(value = "search") String search) { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder(); if (search != null) { Pattern pattern = Pattern.compile("(\w+?)(:|)(\w+?),"); Matcher matcher = pattern.matcher(search + ","); while (matcher.find()) { builder.with(matcher.group(1), matcher.group(2), matcher.group(3)); } } BooleanExpression exp = builder.build(); return myUserRepository.findAll(exp); } }

Here is a quick test URL example:

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

And the response:

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

9. Conclusion

Dieser dritte Artikel befasste sich mit den ersten Schritten zum Erstellen einer Abfragesprache für eine REST-API unter guter Verwendung der Querydsl-Bibliothek.

Die Implementierung ist natürlich früh, kann aber leicht weiterentwickelt werden, um zusätzliche Operationen zu unterstützen.

Die vollständige Implementierung dieses Artikels finden Sie im GitHub-Projekt - dies ist ein Maven-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.

Weiter » REST-Abfragesprache - Erweiterte Suchvorgänge « Vorherige REST-Abfragesprache mit Spring Data JPA-Spezifikationen