Spring Boot JPA Query Methods II

Weiter geht‘s! Im vorherigen Post haben wir uns die Query Methoden von Spring Data JPA angesehen. Aber wie sehen nun die Zusammenhänge aus? Welche Strukturen kommen aus JPA und welche aus Spring Boot? Und was sollte ich bei der Verwendung beachten? Ihr merkt, es gibt noch einiges zu besprechen. Schauen wir mal rein!

Im Beispielprojekt sind wir ja über sehr einfache Beispiele nicht herausgekommen. Das holen wir heute nach, versprochen! Wir werden uns ansehen, welche Hilfskonstrukte uns dabei helfen, Zeichenketten zu durchsuchen, wie wir mit Zahlen und Datumswerten umgehen können und auch kurz in Listen als Suchparameter eintauchen. Gespannt? Dann legen wir los!

Wer sich noch einmal kurz einen Überblick über die Datenstruktur des Beispielprojekts verschaffen möchte, findet im vorherigen Post ein paar einleitende Sätze dazu: Spring Boot JPA Query Methods

Stringvergleiche mit LIKE

Zur Erinnerung: Um die Eigenschaft einer Entity gegen einen String zu vergleichen haben wir bereits das Schlüsselwort Equals genutzt – bzw. einfach weggelassen, da es implizit verwendet wird ;-)

List<Employee> findByFirstnameAndLastname(String firstname, String lastname);

Diese Query Method fragt alle Angestellten (Entity Employee) mit einem exakten Vergleich auf Vor- und Nachname ab. Spontan fällt mir ehrlich gesagt kein sinnvoller Anwendungsfall dafür ein. Häufiger ist doch der Fall, dass nach Teilstrings gefiltert werden soll. In nativem SQL setzen wir solch eine Query mit dem Schlüsselwort LIKE um. Wenig überraschend bieten uns die Spring Data JPA Query Methods diese Möglichkeit auch. So filtert zum Beispiel die folgende Definition

List<Employee> findByFirstnameLike(String firstname);

die Employees anhand eines Suchbegriffs im Vornamen. Beachtet dabei aber unbedingt, dass Ihr mit dem Parameter firstname auch schon die Wildcards für die Suche übergeben müsst:

List<Employee> employees = employeeRepository.findByFirstnameLike("%ictor%");

Ihr könnt die Abfrage durch Hinzufügen des Schlüsselwortes IgnoreCase auch case-insensitive machen. Mehr in die Tiefe geht der Post https://www.spring-boot-blog.de/blog/like-queries-mit-spring-boot/.

Das Schlüsselwort Like lässt sich in der Query Method auch negieren, dazu stellt Ihr einfach Not voran:

List<Employee> findByFirstnameNotLike(String firstname);

LIKE, jetzt aber komfortabel

Spring Boot und das Spring Framework sind damit groß geworden, bequeme Lösungen für wiederkehrende Aufgaben anzubieten. Auf Dauer ist das Jonglieren mit den Wildcards bei LIKE abfragen auch mit der eleganten Query Method natürlich etwas sperrig. Dafür schenkt uns das Framework weitere Schlüsselwörter, die gewisse Wildcards bereits implizieren. Äquivalent zur oben gezeigten Abfrage mit den explizit gesetzten Wildcards funktioniert der Aufruf dieser Query Method:

employeesByName = employeeRepository.findByFirstnameContaining("ictor");

Aber nicht nur innerhalb der Eigenschaft der Entity (entspricht ja der Datenbankspalte) lässt sich suchen, natürlich gibt es auch die entsprechenden Varianten für die Filterung am Beginn und Ende. Die Schlüsselwörter heißen hier StartingWith (oder kürzer StartsWith) und EndingWith (EndsWith).

Natürlich lassen sich auch diese Methoden wieder case-insensitive verwenden. Hier bietet es sich wieder an, die Kurzschreibweise zu benutzen, sonst werden selbst einfachste Anfragen bei der Umsetzung als Query Method zu ziemlichen Monstern.

List<Employee> findByFirstnameStartsWithIgnoreCase(String firstname);

Wenn es komplizierter wird

Als weiteres Goodie bekommen wir die Möglichkeit, direkt per Query Method auch Regex auswerten zu können. Die entsprechenden Schlüsselwörter dafür sind Regex, Matches oder beide zusammen: MatchesRegex. Die Funktion ist bei allen dreien gleich ;-) Zur Vermeidung der drohenden Methodenmonster empfehle ich natürlich das kürzestes Schlüsselwort, also Regex.

Zahlen

Natürlich können wir mit Hilfe der Query Methods nicht nur Texteigenschaften abfragen, sondern auch Zahlen. Keine Angst, hier wird es nicht so umfangreich, wie in den vorherigen Beispielen, im wesentlichen können wir echt größer und größer gleich prüfen, ebenso echt kleiner und kleiner gleich.

Die Schlüsselwörter für echt größer sind GreaterThan, bzw. IsGreaterThan. Wie immer, wenn die zwei zusätzlichen Buchstaben zum allgemeinen Verständnis dienen, gerne mitnehmen, ansonsten würde ich immer die knappere Schreibweise präferieren. Für größer gleich hängt Ihr einfach noch Equal an. Die Schlüsselwörter lauten also GreaterThanEqual und IsGreaterThanEqual.

Ganz analog funktioniert das für kleiner und kleiner gleich, nur eben mit dem Schlüsselwort LessThan und den entsprechenden Varianten. Um zum Beispiel alle nicht volljährigen Angestellten einer Firma zu bestimmen, würden wir folgende Query Method mit dem Wert 18 für den Parameter age aufrufen:

List<Employee> findByCompanyAndAgeLessThanEqual(Company company, int age);

Definitiv unverzichtbar, aber auch recht profan ;-) Wenden wir uns also wieder spannenderen Themen zu: dem Vergleich von Datumswerte.

Datumswerte

Für das Filtern von Datumswerten bieten uns die Query Methods zwei grundsätzliche Wege: Entweder setze ich einen größer/kleiner Vergleich an, um zu prüfen, ob ein Datum vor oder nach einem anderen liegt. Zudem gibt es das Hilfskonstrukt, mit dem ich feststellen kann, ob ein Datum in einem gegebenen Bereich liegt.

Eigentlich analog zum Vergleich numerischer Werte funktionieren die Schlüsselwörter After und Before (bzw. ihre Synonyme IsAfter und IsBefore). Die Verwendung zeige ich nicht noch einmal, ich glaube, mittlerweile ist Euch die grundsätzliche Verwendung der Query Methods klar ;-)

Nur noch ein warnendes Wort an dieser Stelle: Achtet auf eure Datentypen in den Entities und in der Datenbank. Was meine ich damit? Ich erinnere mich noch an die zähe Suche zurück, als ich in einem Projekt eine mySQL Datenbank angebunden habe. In den Testfällen sollte sichergestellt werden, dass Daten in der Reihenfolge, in der sie angelegt wurden, auch wieder ausgelesen werden. Alles schön im Setup formuliert – aber es funktionierte einfach nicht… An sich war das Problem ziemlich simpel: Der Datentyp der Spalte hat einfach keine Millisekunden mitgespeichert. Da die Testdaten natürlich innerhalb von Sekundenbruchteilen gespeichert wurden, hatten sie für die Datenbank das gleiche Datum. In solch einem Fall wirkt das Abfrageergebnis auf den ersten Blick zum Teil falsch, obwohl es das eigentlich nicht ist…

Etwas spannender und in der Praxis definitiv häufiger ist die Abfrage, ob ein Datum in einem vorgegeben Intervall liegt. Wenig überraschend heißen die äquivalenten Schlüsselwörter hier Between bzw. IsBetween. Die Abfrage sieht dann zum Beispiel so aus:

List<Company> findByFoundationBetween(LocalDateTime from, LocalDateTime to);

Alles soweit im Bereich des Bekannten. Bitte nur aufpassen, dass dieses Schlüsselwort zwei Parameter „verbraucht“. In dem Moment, in dem die Query Method länger wird, darf man sich da beim nachzählen nicht herausbringen lassen. Aber Spring Data lässt uns natürlich im Zweifelsfall nicht im Regen stehen und liefert uns eine vernünftige Fehlermeldung.

Ohne Parameter

Wo wir gerade dabei sind: Es gibt auch Schlüsselwörter, die komplett ohne Parameter funktionieren: Um zum Beispiel alle Offices zu lesen, die keiner Firma zugeordnet sind, dient folgende Query Method:

List<Office> findByCompanyIsNull();

Oder noch einfacher mit dem Synonym Null. Das funktioniert bei allen Feldern einer Entity. Für Boolean-Felder gibt es noch eine Vereinfachung: Mit den Schlüsselwörtern True und False (Überraschung: IsTrue und IsFalse funktionieren äquivalent) kann ich die Eigenschaften direkt abfragen:

List<Employee> findByActiveTrue();

Sehr schlank, sehr praktisch, wie schon gesagt muss man nur beim Parameterzählen achtsam sein.

Listen als Parameter

Ein letztes großes Thema sind Listen als Parameter für Query Methods. Damit stellen wir IN bzw. NOT IN Queries in SQL her. Die Funktionsweise überrascht uns als gestandene Query Method Junkies natürlich nicht mehr. Die Definition ist nicht so spannend, schauen wir uns daher gleich den tatsächlichen Aufruf an:

List<Office> offices = officeRepository.findByCityIn(List.of(„Augsburg“, „Berlin“));

Der Aufruf liefert dann alle Office Entities zurück, bei denen die Stadt in der Parameterliste auftaucht. Umgekehrt funktioniert es mit dem Schlüsselwort NotIn. Zu beachten ist eigentlich nur, dass die Datentypen passen, wir also eine zum Feld passend typisierte Liste übergeben.

Hier haben wir den Fall gesehen, dass wir einen Parameter innerhalb der Entity gegen eine Liste vergleichen. Aber auch andersherum können wir eine Prüfung vornehmen: Wir können abfragen, ob eine Listeneigenschaft einer Entity leer ist oder eben nicht:

List<Company> companiesWithoutOffices = companyRepository.findByOfficesIsEmpty();

Natürlich können wir wieder Is weglassen und die Negierung funktioniert mit einem vorangestellten Not.

Last Goodie

Das waren jetzt viele Möglichkeiten, damit können wir schon eine ganze Reihe Anforderungen abdecken. Spätenstens, wenn wir Queries mit EXISTS oder auch einen komplexeren Join benötigen, müssen wir uns etwas schlagkräftigere Werkzeuge zulegen.

Als letzter Input aber noch ein Beispiel zum Traversieren: Erinnern wir uns, in unserer Beispiel-Datenstruktur hat jeder Employee eine Company, diese hat wiederum eine Eigenschaft name. Wenn wir alle Employees, die in einer Firma mit einem gegebenen Namen arbeiten suchen wollen, können wir das ohne expliziten Join tun:

List<Employee> findByCompanyName(String companyName)

Spring Data JPA versteht bei dieser Query, dass wir zunächst die company Eigenschaft von Employee auswählen wollen und dann von der Company die Eigenschaft name. Gegen diese soll dann der Parameter verglichen werden.

Fazit

Ganz ehrlich gesagt war das immer noch nicht alles ;-) Die Query Methods können noch mehr! Eine vollständige Übersicht findet sich immer in der aktuellen Dokumentation zu Spring Data JPA.

Viele Anwendungsfälle kann ich mit den Query Methods mit sehr wenig Code erledigen. Noch einmal zur Erinnerung, wir definieren die Methoden nur im Interface, um die gesamte Implementierung kümmert sich Spring! Dadurch halten wir unseren Projektcode sehr schlank.

Natürlich gibt es noch viele Anwendungsfälle, in denen die Query Methods nicht mehr ausreichen. Aber auch dafür bietet uns Spring Data JPA die passenden Konstrukte an. Wir können die JPA Query Language JPQL verwenden, Spring Specifications oder die JPA CriteriaApi. Wenn alle Stricke reißen, funktionieren sogar native Queries. Aber mehr zu all diesen Themen in einem späteren Artikel...

Spring-Boot News

Bleibe auf dem aktuellsten Stand mit unseren kostenlosen Spring-Boot updates. So wirst Du direkt informiert, wenn wir einen neuen Artikel veröffentlichen.
Kein Spam, kein Bullshit, nur Spring-Boot Insider-Wissen, versprochen.

Noch keine Kommentare vorhanden.

Was denkst du?

© 2020 Mischok