Zum Hauptinhalt springen

Abgeleitete Abfragen

Die Ausgangslage

Dein Repository ist nur ein Interface:

public interface GuestbookEntryRepository extends JpaRepository<GuestbookEntry, Long> {
}

Damit hast du bereits save(), findById(), findAll(), deleteById() und einige weitere. Was du nicht hast, sind fachliche Abfragen: „alle Einträge eines bestimmten Verfassers", „alle Einträge aus einem Zeitraum", „alle Einträge, deren Titel ein Wort enthält".

Ohne Hilfsmittel müsstest du dafür SQL schreiben. Spring Data bietet einen anderen Weg: Du benennst die Methode, und die Implementierung entsteht daraus.

Das Prinzip

Der Methodenname ist die Abfrage. Spring Data liest ihn beim Start, zerlegt ihn und baut daraus eine JPA-Abfrage. Das eigentliche SQL erzeugt daraus wie immer Hibernate. Es gibt keinen Rumpf zu schreiben.

Wie der Name gelesen wird

List<GuestbookEntry> findByAuthor(String author);

Spring Data zerlegt das so:

findBy Author
────── ──────
Einleitung Attribut der Entität
→ where author = ?

Wichtig ist der zweite Teil: Author muss ein Attribut der Entität sein. Es wird kleingeschrieben und in die Bedingung übernommen.

Ein Tippfehler fällt beim Start auf — nicht beim Kompilieren

Schreibst du findByAutor statt findByAuthor, kompiliert das Projekt einwandfrei. Beim Start bricht es mit einer Meldung wie:

No property 'autor' found for type 'GuestbookEntry'

Das ist unangenehm, aber immer noch besser als bei handgeschriebenem SQL in einer Zeichenkette: Dort fiele der Fehler erst auf, wenn ein Nutzer die betroffene Funktion aufruft.

Die Bausteine

Einleitungen

PräfixErgebnis
findBy…die Treffer
countBy…die Anzahl
existsBy…true / false
deleteBy…löscht die Treffer

Vergleiche

BausteinSQLBeispiel
(nichts)= ?findByAuthor
Not<> ?findByAuthorNot
Containinglike %?%findByTitleContaining
StartingWithlike ?%findByAuthorStartingWith
GreaterThan> ?findByDateGreaterThan
GreaterThanEqual>= ?findByDateGreaterThanEqual
LessThan< ?findByDateLessThan
Betweenbetween ? and ?beide Grenzen eingeschlossenfindByDateBetween
After / Before> ? / < ?findByDateAfter
IsNullis nullfindByCommentIsNull
Inin (?, ?, …)findByAuthorIn
IgnoreCaseGroß-/Kleinschreibung egalfindByAuthorIgnoreCase

Verknüpfungen und Sortierung

BausteinWirkung
Andbeide Bedingungen
Oreine von beiden
OrderBy…Asc / …DescSortierung
First / Top10begrenzt die Trefferzahl
Distinctkeine Doppelungen

Beispiele

// alle Einträge eines Verfassers, neueste zuerst
List<GuestbookEntry> findByAuthorOrderByDateDesc(String author);

// Titel enthält ein Wort, Groß-/Kleinschreibung egal
List<GuestbookEntry> findByTitleContainingIgnoreCase(String wort);

// alle aus einem Zeitraum, seitenweise - untere Grenze eingeschlossen, obere nicht
Page<GuestbookEntry> findByDateGreaterThanEqualAndDateLessThan(
LocalDateTime from, LocalDateTime to, Pageable pageable);

// die drei neuesten
List<GuestbookEntry> findTop3ByOrderByDateDesc();

// wie viele hat jemand geschrieben?
long countByAuthor(String author);

// gibt es überhaupt einen?
boolean existsByAuthor(String author);
Between schließt beide Grenzen ein

Das ist die häufigste Falle bei Zeiträumen. Ein Jahr reicht vom 1. Januar 00:00 Uhr bis vor den 1. Januar des Folgejahres. Mit Between gehört der erste Augenblick des Folgejahres noch dazu — ein Eintrag genau in dieser Sekunde fällt in beide Jahre.

Für Zeiträume nimmt man deshalb GreaterThanEqual für den Anfang und LessThan für das Ende. Für abzählbare Werte („Note zwischen 1 und 4") ist Between dagegen genau richtig.

Lass dir die Möglichkeiten vorschlagen

Tippe im Repository findBy und warte. Deine Entwicklungsumgebung kennt die Attribute deiner Entität und schlägt gültige Fortsetzungen vor.

Das ist der schnellste Weg, ein Gefühl für die Möglichkeiten zu bekommen — schneller als jede Tabelle.

Sortierung: im Namen oder als Parameter?

Für „neueste zuerst" gibt es zwei Wege:

// (a) fest im Namen
List<GuestbookEntry> findByAuthorOrderByDateDesc(String author);

// (b) beweglich als Parameter
List<GuestbookEntry> findByAuthor(String author, Sort sort);
im Namenals Parameter
Sortierungfestvom Aufrufer bestimmbar
LesbarkeitselbsterklärendName bleibt kurz
Geeignet, wenndie Reihenfolge zur Fachlichkeit gehörtder Client sortieren darf

Bei einer REST-Schnittstelle, deren Client ?sort=author,asc schicken darf, ist (b) richtig. Deshalb arbeitet dein Gästebuch mit Pageable — darin steckt die Sortierung schon.

Wenn der Name zu lang wird

Abgeleitete Abfragen haben eine Grenze. Diese Methode ist theoretisch möglich:

List<GuestbookEntry> findByAuthorAndTitleContainingAndDateBetweenOrderByDateDesc(
String author, String title, LocalDateTime from, LocalDateTime to);

Lesbar ist sie nicht mehr. Ab etwa drei Bedingungen greift man besser zu @Query:

@Query("""
select e from GuestbookEntry e
where e.author = :author
and lower(e.title) like lower(concat('%', :title, '%'))
order by e.date desc
""")
List<GuestbookEntry> searchEntries(String author, String title);

Das ist JPQL — eine Abfragesprache, die wie SQL aussieht, aber über Klassen und Attribute formuliert wird, nicht über Tabellen und Spalten. Beachte GuestbookEntry und e.author statt guestbook_entry und author.

Wann was?
SituationWahl
eine bis zwei Bedingungenabgeleitete Abfrage
drei und mehr@Query mit JPQL
Berechnungen, Gruppierungen@Query
datenbankspezifische Funktionen@Query mit nativeQuery = true

Nachsehen, was wirklich passiert

Bei aller Bequemlichkeit gilt: Du siehst das erzeugte SQL nicht — es sei denn, du schaust hin.

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

Damit steht jede Abfrage im Log. Das ist der einzige verlässliche Weg zu prüfen, ob wirklich die Datenbank filtert.

Der häufigste Anfängerfehler

Alles holen und in Java filtern:

repository.findAll().stream()
.filter(e -> e.getAuthor().equals(author)) // ❌
.toList();

Im Log erscheint dann ein select ohne where — die ganze Tabelle wird übertragen. Mit findByAuthor(author) erledigt die Datenbank die Auswahl.

Das hast du gelernt
  • Bei einer abgeleiteten Abfrage ist der Methodenname die Abfrage; Spring Data erzeugt die Implementierung beim Start.
  • Der Name besteht aus Einleitung (findBy, countBy, existsBy), Attributnamen und Vergleichsbausteinen.
  • Ein Tippfehler im Attributnamen fällt erst beim Start auf, nicht beim Kompilieren.
  • Sortierung gehört in den Namen, wenn sie fachlich festliegt — sonst als Sort- bzw. Pageable-Parameter.
  • Ab etwa drei Bedingungen ist @Query mit JPQL lesbarer.
  • Nur das Log zeigt, ob wirklich die Datenbank filtert.