CodeGym /Kurse /Spring Data JPA /JPQL und @Query: die Grenzen von derived queries

JPQL und @Query: die Grenzen von derived queries

Spring Data JPA
Level 11, Lektion 0
Verfügbar

1. Derived queries am Anfang

Derived queries – das ist wie die Autovervollständigung in der IDE: Solange die Aufgabe einfach ist, spart es Zeit und macht den Code angenehmer. Sie schreiben eine Methode wie findBySku(...) oder findByStatus(...), und Spring Data JPA erstellt selbst die Abfrage, sendet SQL an die Datenbank und gibt Ihnen das Ergebnis zurück. Für Anfänger ist das der ideale Einstieg: weniger Syntax, mehr Bedeutung.

Beginnen wir mit einem ehrlichen Beispiel: eine kurze Methode, eine Bedingung, leicht lesbar sowohl im Repository als auch im Service.

Im Folgenden werde ich bewusst lokale Ausschnitte aus ProductRepository verwenden: So kann man die Grenze der derived queries besser erkennen, ohne das gesamte Interface mitzuschleppen.

package com.example.shopdatajpa.catalog.repository;

import com.example.shopdatajpa.catalog.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

// Spring Data JPA Repository: grundlegende CRUD-Operationen sind bereits in JpaRepository vorhanden
public interface ProductRepository extends JpaRepository<Product, Long> {

    // Derived query: Spring Data baut die Abfrage automatisch aus dem Methodennamen (basierend auf dem Feld sku)
    Optional<Product> findBySku(String sku);
}

Hier ist der Methodenname kurz, fast umgangssprachlich. Im Service-Code sieht es noch besser aus: productRepository.findBySku(sku). Selbst wenn Sie kurz die Details vergessen haben, liest das Gehirn es ohne „Entschlüsselung“.

Und darin liegt der Hauptvorteil von derived queries: Sie funktionieren perfekt, solange die Abfrage kurz und offensichtlich ausgedrückt werden kann, und der Methodenname bleibt menschlich, anstatt zu einer „Methodenwurst“ zu werden.

2. Lange Methodennamen

Sobald die Abfrage komplexer wird, verlangt die derived query, dass Sie die Bedingungen direkt im Namen „aufschreiben“. Zunächst sieht das tolerierbar aus, dann wie ein Wi‑Fi-Passwort, das man Ihnen am Telefon diktiert: Es ist zwar logisch, aber man möchte nachfragen, wo die Bindestriche und Großbuchstaben sind.

Angenommen, wir möchten im Katalog Produkte nach Kategorie, Status und Höchstpreis auswählen. Im realen Leben ist das ein normaler Filter. In der derived query sieht das so aus:

import com.example.shopdatajpa.catalog.entity.Product;
import com.example.shopdatajpa.catalog.entity.ProductStatus;
import org.springframework.data.jpa.repository.JpaRepository;

import java.math.BigDecimal;
import java.util.List;

// Repository für den Katalog-Lesezugriff: noch derived query, aber der Name wächst bereits
public interface ProductRepository extends JpaRepository<Product, Long> {

    // Wichtig: Alle Bedingungen (category.code, status, price <=) sind direkt im Methodennamen kodiert
    List<Product> findByCategory_CodeAndStatusAndPriceLessThanEqual(
            String categoryCode, ProductStatus status, BigDecimal maxPrice);
}

Noch ist es erträglich, aber es ist bereits spürbar, dass wir eine Formel lesen, kein Szenario. Der Methodenname beginnt nicht zu beschreiben, „warum wir das tun“, sondern „mit welchen konkreten Bedingungen wir auswählen“. Und es wird noch „lustiger“, weil Filter die Angewohnheit haben, sich zu vermehren.

In diesem Moment ist es wichtig, den Gedanken zu erfassen: derived queries sind nicht „kaputt“ oder schlecht geworden. Sie bewegen sich einfach von Natur aus von der Zone „bequem“ in die Zone „zu viele Details im Namen“. Und das ist eine normale technische Entwicklungsphase eines Projekts.

3. Navigation über Beziehungen in derived queries

In unserem mini-shop sind die Entitäten verknüpft: Product hat Category, OrderItem hat Product, die Bestellung hat Positionen. Das ist gut – so wird das Modell realitätsnäher. Aber es gibt einen Nebeneffekt: In den Abfragen beginnen wir nicht nur nach Feldern von Product zu filtern, sondern auch nach Feldern der verknüpften Entitäten, und die derived query zwingt uns, dies im Namen „auszuschreiben“.

Zum Beispiel möchten Sie: „Gib Produkte aus aktiven Kategorien.“ Im Objektmodell sieht das natürlich aus, aber in der derived query beginnt eine „Pfad“ aus Namen zu wachsen:

import com.example.shopdatajpa.catalog.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

// Beispiel für Navigation über Beziehung: category -> active
public interface ProductRepository extends JpaRepository<Product, Long> {

    // Category_ActiveTrue bedeutet Filterung nach dem Feld active der verknüpften Entität category
    List<Product> findByCategory_ActiveTrue();
}

Und jetzt fügen wir einen weiteren Schritt hinzu: Kategorie aktiv, Kategorie-Code so-und-so, Produkt aktiv, Preis nicht größer, Name enthält Teilzeichenfolge. In Geschäftsworten ist das ein Szenario. In der derived query – bereits ein literarisches Werk:

import com.example.shopdatajpa.catalog.entity.Product;
import com.example.shopdatajpa.catalog.entity.ProductStatus;
import org.springframework.data.jpa.repository.JpaRepository;

import java.math.BigDecimal;
import java.util.List;

// Beispiel einer „tiefen“ derived query: viele Bedingungen + mehrere Navigationsschritte über Beziehungen
public interface ProductRepository extends JpaRepository<Product, Long> {

    // Je mehr Bedingungen, desto höher das Risiko, den Methodennamen in eine „Abfragezeichenkette ohne Leerzeichen“ zu verwandeln
    List<Product> findByCategory_CodeAndCategory_ActiveTrueAndStatusAndPriceLessThanEqualAndNameContainingIgnoreCase(
            String categoryCode, ProductStatus status, BigDecimal maxPrice, String namePart);
}

Jetzt ist es an der Zeit, sich ehrlich zu fragen: Entwerfen Sie das Repository, oder entwirft das Repository Sie?

Der Methodenname ist so lang geworden, dass er bereits die Rolle des Abfragetexts übernimmt, nur geschrieben in einer seltsamen Sprache. Und das Ärgerlichste: Diese Sprache ist schlechter lesbar als eine normale Abfrage, weil es keine Leerzeichen gibt, keine visuelle Struktur, und sie sich schlecht „überfliegen“ lässt.

4. Kombinatorische Explosion der Filter

Es gibt eine spezielle Art von Schmerz, die fast unvermeidlich in jedes lebendige Projekt kommt: Filter können nicht nur „viele Bedingungen“ sein, sondern auch „ein Teil der Bedingungen ist optional“. Das heißt, heute hat der Benutzer nur status angegeben, morgen status + category, übermorgen status + price range, und so weiter. Wenn man versucht, das ausschließlich mit derived queries zu machen, beginnt das Repository anzuschwellen.

Zuerst schreiben Sie ein paar Methoden und freuen sich. Dann fügen Sie eine dritte hinzu. Dann stellen Sie fest, dass Sie eine Sammlung von Methoden haben, die sich in einem Detail unterscheiden, und Sie sind sich nicht mehr sicher, welche der Service aufruft.

Hier ist eine typische „Treppe“ von Varianten, die harmlos aussieht, bis Sie verstehen, dass das erst der Anfang ist:

import com.example.shopdatajpa.catalog.entity.Product;
import com.example.shopdatajpa.catalog.entity.ProductStatus;
import org.springframework.data.jpa.repository.JpaRepository;

import java.math.BigDecimal;
import java.util.List;

// Das Repository beginnt mit Filterkombinationen zu „überwuchern“
public interface ProductRepository extends JpaRepository<Product, Long> {

    // Nur nach Status
    List<Product> findByStatus(ProductStatus status);

    // Status + Kategorie
    List<Product> findByStatusAndCategory_Code(ProductStatus status, String categoryCode);

    // Status + Preisspanne
    List<Product> findByStatusAndPriceBetween(ProductStatus status, BigDecimal min, BigDecimal max);

    // Status + Kategorie + Preisspanne (und diese Liste wächst normalerweise weiter)
    List<Product> findByStatusAndCategory_CodeAndPriceBetween(
            ProductStatus status, String categoryCode, BigDecimal min, BigDecimal max);
}

Das ist noch keine Katastrophe. Aber die Logik ist sehr unangenehm: Das Repository beginnt, Kombinationen von Parametern abzubilden, anstatt durchdachte Lese-Szenarien.

Irgendwann erkennen Sie, dass Sie kein API entwerfen, sondern einfach versuchen, mit der chaotischen „Explosion der Varianten“ Schritt zu halten. Und das ist der Moment, in dem derived queries nicht mehr helfen, sondern nur noch Rauschen erzeugen.

5. Wenn der Service unter den Namen leidet

Das Repository ist nicht nur ein Ort für den Datenzugriff. Es ist auch Teil der Projektsprache: Services werden durch Repository-Aufrufe lesbar. Wenn das Repository Szenarien sauber ausdrückt, liest sich der Service wie ein Text: „das geholt, das geprüft, das gemacht“. Wenn das Repository Auswahlformeln ausdrückt, verwandelt sich der Service in eine Ansammlung von „magischen Zaubersprüchen“.

Betrachten Sie den Unterschied auf der Ebene des normalen Code-Lesens. Die erste Variante: Der Service ruft eine Methode mit einem langen Namen auf, und Sie müssen die Formel direkt an der Aufrufstelle lesen.

// Der Aufruf sieht aus wie eine „Formel“ und muss direkt im Service gelesen werden
var products = productRepository
        .findByCategory_CodeAndCategory_ActiveTrueAndStatusAndPriceLessThanEqualAndNameContainingIgnoreCase(
                categoryCode, status, maxPrice, namePart);

Die zweite Variante: Der Service ruft eine Methode auf, deren Name das Szenario widerspiegelt. Dazu müssen die Auswahldetails aus dem Methodennamen in die Abfrage selbst verlagert werden. Aber schon jetzt, auf der Ideenebene, ist der Unterschied offensichtlich:

// Der Aufruf sieht aus wie ein Szenario: „Hole Katalogprodukte“
var products = productRepository.findCatalogProducts(categoryCode, status, maxPrice, namePart);

Im zweiten Fall liest sich der Service menschlich: „Hole Katalogprodukte“. Im ersten Fall: „Hole ... äh ... warte, ich lese diese Methode zu Ende“. Und das ist der reale technische Preis. Nicht die Performance, nicht SQL, sondern schlicht die Lesbarkeit des Codes.

6. Anzeichen: Zeit für eine explizite Abfrage

Man wünscht sich eine magische Zahl wie „Nach drei Bedingungen wechseln Sie immer zu JPQL“. In der Realität funktioniert das nicht: Manchmal sind fünf Bedingungen noch gut lesbar, manchmal sehen zwei Bedingungen schon seltsam aus, wenn sie über mehrere Beziehungen gehen und eine knifflige Logik erfordern. Aber praktische Anzeichen gibt es dennoch – man kann sie als „technischen Test auf gesunden Menschenverstand“ betrachten.

Unten finden Sie eine kleine Tabelle, die bei der Entscheidung hilft – ohne Philosophie und Religion (derived queries vs. manuelle Abfragen), sondern einfach anhand von Wartbarkeitsmerkmalen:

Symptom im Code Was es bedeutet Was normalerweise getan wird
Der Methodenname ist schwer vorlesbar ohne Pause und Atemübungen Die Methode speichert eine Abfrageformel anstelle eines Szenarios Die Formel wird in einen expliziten Abfragetext ausgelagert, der Name bleibt für das Szenario
Im Namen tauchen mehrere Navigationsschritte über Beziehungen auf (Category, dann noch etwas) Die Abfrage ist „graphartig“ und lässt sich schlecht durch einen Namen ausdrücken Man wählt eine Abfrage, die sich schön formatieren und warten lässt
Das Repository wächst aufgrund von Kombinationen von Varianten (mal so, mal so) Eine kombinatorische Explosion von Methoden beginnt Man versucht, Szenarien anders zu beschreiben, um Methoden nicht zu vermehren
Im Code-Review muss die Methode teilweise auseinandergenommen werden Lesbarkeit verloren, Wartung wird teurer Im Team wird ein Stil vereinbart: Einfaches – derived, Komplexes – explizite Abfrage
Im Service sieht der Repository-Aufruf wie eine „Abfragezeichenkette ohne Leerzeichen“ aus Das technische Detail ist in den Business-Code eingedrungen Man verschiebt die Filterdetails näher ans Repository und macht sie lesbar

Ein wichtiger Gedanke: „Zeit dafür“ bedeutet nicht, dass derived queries falsch sind. Es bedeutet, dass Ihr Repository auch ein Produkt zum Lesen durch Menschen ist. Und wenn Menschen den Code nicht mehr schnell verstehen, dann ist das bereits technische Schuld, selbst wenn alles perfekt auf der lokalen Datenbank funktioniert.

7. Hybrider Ansatz: derived + @Query

Ein sehr typischer Anfängerfehler ist es, zu sehen, dass derived queries unbequem werden können, und dann alles abrupt auf etwas anderes „umzuschreiben“. Das ist fast immer schädlich. Die richtige Reifungsstrategie ist hybrid: Sie lassen derived queries dort, wo sie hervorragend sind, und hören auf, sie dort zu quälen, wo sie anfangen zu stören.

Zum Beispiel bleibt findBySku völlig normal, weil es kurz, klar und ein einfaches Szenario widerspiegelt:

import com.example.shopdatajpa.catalog.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

// Einfache derived queries sind weiterhin angemessen und lesbar
public interface ProductRepository extends JpaRepository<Product, Long> {

    // Szenario „Produkt nach SKU finden“ – kurz und klar
    Optional<Product> findBySku(String sku);
}

Aber der „Katalogfilter“, der bereits mehrere Bedingungen und Navigationsschritte über Beziehungen enthält, kann so ausgedrückt werden, dass der Name das Szenario beschreibt und die Auswahldetails separat leben. Heute ist es wichtig, den Ansatz auf Ideenebene zu sehen: Im Repository taucht eine Methode mit einem kurzen Namen auf, und die Komplexität lebt nicht mehr im Namen.

import com.example.shopdatajpa.catalog.entity.Product;
import com.example.shopdatajpa.catalog.entity.ProductStatus;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.math.BigDecimal;
import java.util.List;

// Hybrider Stil: Methodenname für das Szenario, Bedingungen in JPQL
public interface ProductRepository extends JpaRepository<Product, Long> {

    // JPQL (nicht SQL): Wir arbeiten mit der Entität Product und ihren Feldern/Beziehungen, nicht mit Tabellen
    // Die Parameterbindung sollte explizit erfolgen, damit die Abfrage nicht von der Übereinstimmung der Argumentnamen abhängt
    @Query("select p from Product p where p.category.code = :code and p.status = :status and p.price <= :maxPrice")
    List<Product> findCatalogProducts(@Param("code") String code,
                                      @Param("status") ProductStatus status,
                                      @Param("maxPrice") BigDecimal maxPrice);
}

Hier ist etwas anderes wichtiger: Die Methode sieht wieder wie ein API aus, nicht wie eine serialisierte Abfrage. Die Parameter werden explizit über @Param gebunden, sodass die Abfrage nicht von der Übereinstimmung der Argumentnamen und Compiler-Einstellungen abhängt. Das Repository wird lesbarer, der Service wird lesbarer, und das gesamte Projekt beginnt, wie ein System auszusehen, nicht wie eine Sammlung von „Zaubersprüchen aus Methodennamen“.

8. Typische Fehler beim Arbeiten mit derived queries

In diesem Moment treten Fehler oft nicht aufgrund mangelnden Wissens auf, sondern aufgrund einer zu geradlinigen Reaktion auf das Problem: Entweder ertragen wir die Unannehmlichkeiten zu lange, oder wir fangen an, alles auf einmal zu reparieren und verwandeln ein kleines Unbehagen in eine große Refaktorisierung. Lassen Sie uns die häufigsten Fallstricke besprechen, damit Sie wenigstens in bequemen Schuhen darauf treten.

Fehler Nr. 1: Alles unterschiedslos in „explizite Abfragen“ umschreiben, einschließlich einfacher Fälle.
Wenn Sie einmal gesehen haben, dass lange Namen schlecht sind, entsteht der Wunsch, das Repository zu „säubern“ und derived queries vollständig zu entfernen. Normalerweise führt das zum gegenteiligen Effekt: Einfache Methoden werden länger, der Komfort geht verloren, und der Code sieht schwerfälliger aus. Besser ist es, derived queries als Werkzeug für kurze und offensichtliche Szenarien zu behalten und sich nicht zu schämen, sie dort zu verwenden, wo sie wirklich punkten.

Fehler Nr. 2: „Wurst-Methoden“ dulden, nur weil Spring Data das kann.
Spring Data kann zwar viel, aber „können“ und „es wert sein“ sind zwei verschiedene Dinge. Wenn eine Methode schlechter lesbar ist als eine normale Abfrage, zahlen Sie Lesbarkeit für Automatisierung. In echten Teams ist Lesbarkeit kein Luxus, sondern Entwicklungsgeschwindigkeit: Je schneller jemand den Code versteht, desto weniger zufällige Bugs.

Fehler Nr. 3: Die Methode als Aufzählung von Bedingungen benennen, anstatt als Szenario.
Ein Name wie findByCategory_CodeAndStatusAndPriceLessThanEqual beschreibt zwar ehrlich die Bedingungen, aber er beschreibt nicht den Sinn. Das führt oft dazu, dass der Service-Code beginnt, „in der Sprache der Filter“ zu sprechen, anstatt in der Sprache des Geschäfts. Wenn Sie sehen, dass der Methodenname zu einer Formel geworden ist, ist es hilfreich, innezuhalten und zu fragen: Wie heißt dieses Szenario auf menschlich?

Fehler Nr. 4: Ähnliche Methoden für Parameterkombinationen vermehren, ohne die Variantenexplosion zu bemerken.
Die ersten vier Methoden sehen normal aus, aber dann beginnt der Schneeball: Morgen kommt createdAt dazu, dann manufacturer, dann „nur Produkte auf Lager“… und das Repository wird zu einer Liste von Variationen. Frühzeitig ist es nützlich, zumindest diesen Trend zu bemerken: Wenn Sie anfangen, „noch eine Methode fast gleich, aber mit einer zusätzlichen Bedingung“ zu schreiben, ist das bereits ein Signal, dass derived queries die Komfortzone verlassen.

Fehler Nr. 5: Denken, das Problem sei nur im Repository, und der Service werde „irgendwie damit klarkommen“.
In Wirklichkeit ist der Schmerz meistens im Service sichtbar: Dort lesen Sie Code als Szenario, und dort wirken lange Namen maximal unangenehm. Wenn ein Repository-Aufruf den halben Bildschirm einnimmt, ist das kein „schön gestalteter Datenzugriff“, sondern technisches Rauschen innerhalb der Geschäftslogik. Ein gutes Repository sollte dem Service helfen, lesbar zu sein, sonst erhalten Sie ein Projekt, das selbst bei korrekten SQL-Abfragen schwer zu warten ist.

1
Aufgabe
Spring Data JPA, Level 11, Lektion 0
Gesperrt
Kurzer und langer derived query in einem Repository
Kurzer und langer derived query in einem Repository
1
Aufgabe
Spring Data JPA, Level 11, Lektion 0
Gesperrt
Servicesszenario auf Basis langer derived query
Servicesszenario auf Basis langer derived query
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION