AB 04 – Suchen, filtern und blättern
Die Situation
Das Gästebuch läuft seit einigen Wochen. Zwei Rückmeldungen kommen herein:
„Die ältesten Einträge stehen oben. Wir wollen die neuesten zuerst sehen."
„Für den Jahresbericht brauchen wir alle Einträge aus einem bestimmten Jahr."
Und die Kollegin aus dem Betrieb ergänzt: „Habt ihr euch überlegt, was passiert, wenn da irgendwann 10.000 Einträge drin stehen?"
Die Schnittstelle, die du gerade dokumentiert hast, muss also wachsen. Was das mit der Dokumentation macht, siehst du am Ende dieses Arbeitsblatts.
Lernziele
Nach Bearbeitung dieses Arbeitsblatts kannst du:
- abgeleitete Abfragen aus Methodennamen erzeugen und ihre Funktionsweise erklären
- begründen, warum Filtern und Sortieren in die Datenbank gehört und nicht in den Controller
- Request-Parameter mit
@RequestParamentgegennehmen und optional gestalten - eine Schnittstelle mit
Pageableseitenweise ausliefern - den Aufbau einer
Page-Antwort lesen - erklären, warum
@ParameterObjectnötig ist, damit die Dokumentation die Parameter einzeln kennt
Aufgaben
Aufgabe 1 – Die falsche Reihenfolge
Vermutlich erscheinen die Einträge in der Reihenfolge ihrer Ids — der älteste zuerst. Für ein Gästebuch ist das genau verkehrt herum.
Ohne ORDER BY darf die Datenbank die Zeilen in beliebiger Reihenfolge liefern. Dass hier die Ids aufsteigend erscheinen, ist ein Zufall der aktuellen H2-Version — kein Versprechen, auf das man sich verlassen darf.
Das macht die Aufgabe nur dringlicher: Wer eine bestimmte Reihenfolge braucht, muss sie anfordern.
Eine naheliegende Lösung wäre, im Controller zu sortieren:
@GetMapping
public ResponseEntity<List<GuestbookEntry>> findAllEntries() {
List<GuestbookEntry> all = repository.findAll();
all.sort(Comparator.comparing(GuestbookEntry::getDate).reversed()); // ❌
return ResponseEntity.ok(all);
}
Das funktioniert. Trotzdem ist es der falsche Weg.
Die Datenbank kann sortieren — schnell, und mit Unterstützung durch Indizes. Hier holt sich die Anwendung alle Datensätze, überträgt sie über die Datenbankverbindung in den Hauptspeicher und ordnet sie dann selbst neu.
Bei zwanzig Einträgen fällt das nicht auf. Bei zwanzigtausend schon.
Faustregel: Arbeit, die die Datenbank erledigen kann, gehört in die Datenbank.
Aufgabe 2 – Sortieren lassen
Der Auftrag „sortiere nach Datum, absteigend" muss zur Datenbank. Und dorthin kommt er auf eine Weise, die auf den ersten Blick nach einem Trick aussieht: Du schreibst ihn in den Methodennamen.
List<GuestbookEntry> findAllByOrderByDateDesc();
Das ist alles. Kein Rumpf, keine Implementierung, kein SQL. Spring Data liest den Namen beim Start, zerlegt ihn in seine Wortbestandteile und baut daraus die Abfrage:
Daraus wird ... order by date_of_entry desc — ein where kommt nicht vor, es sollen ja alle Einträge geliefert werden.
By, wenn nichts folgt?By leitet in Spring Data immer den Bedingungsteil ein. Hier gibt es keine Bedingung, deshalb folgt sofort OrderBy. Die Stelle sieht leer aus — und ist es auch.
In Aufgabe 3 füllst du sie: Dort steht hinter findBy eine Bedingung — und dann ergibt das By auch sichtbar Sinn.
date sortieren, nicht nach idBeides liefert hier zufällig dasselbe Ergebnis, weil neuere Einträge auch höhere Ids haben.
Fachlich richtig ist date: Gefragt war „die neuesten zuerst" — und das ist eine Aussage über das Datum. Würden Einträge später einmal nachträglich importiert, liefe die Sortierung nach id ins Leere.
Ein Methodenname legt die Sortierung fest. Soll der Aufrufer sie bestimmen, bräuchtest du für jede Spalte und jede Richtung eine eigene Methode — findAllByOrderByAuthorAsc, findAllByOrderByTitleDesc und so fort. Dafür gibt es einen zweiten Weg, das Interface Sort:
repository.findAll(Sort.by(Sort.Direction.DESC, "date"));
Hier steht die Sortierung fest, deshalb ist der Methodenname der direktere Weg. In Aufgabe 5 begegnet dir Sort trotzdem wieder — dann als Teil des Pageable.
Aufgabe 3 – Einträge eines Jahres
Jetzt der zweite Wunsch: alle Einträge eines bestimmten Jahres. Das Jahr soll der Client in der URL mitgeben:
GET /api/v1/guestbook-entries?year=2026
Alles nach dem ? ist ein Request-Parameter. Im Unterschied zur Pfadvariablen bezeichnet er keine eigene Ressource, sondern schränkt eine vorhandene ein.
| Pfadvariable | Request-Parameter | |
|---|---|---|
| Beispiel | /guestbook-entries/7 — die 7 ist die Pfadvariable | /guestbook-entries?year=2026 — year=2026 ist der Parameter |
| Annotation | @PathVariable | @RequestParam |
| Bedeutung | welche Ressource | wie eingeschränkt |
| Pflicht? | ja, Teil des Pfades | meist optional |
Wohin der Filter gehört, weißt du seit Aufgabe 1: in die Datenbank. Stünde er in der Controller-Methode, holte findAll() bei 100.000 Einträgen erst alle 100.000 über die Datenbankverbindung in den Hauptspeicher — um dann vielleicht dreißig zu behalten und den Rest wegzuwerfen. Die Bedingung muss ins SQL, und dorthin kommt sie auf demselben Weg wie die Sortierung: über den Methodennamen.
Als Zwischenschritt bekommt der Filter noch eine eigene Adresse — /guestbook-entries/by-year. Sie verschwindet in Aufgabe 4 wieder; hier geht es erst einmal darum, dass überhaupt gefiltert wird.
Beachte die Schreibweise: englisch und mit Bindestrich. Adressen sind Teil der Schnittstelle — an ihnen liest ein fremder Client ab, was er bekommt. Deutsche Wörter und camelCase haben dort so wenig zu suchen wie im Quelltext.
Dasselbe Verfahren wie in Aufgabe 2 — mit drei Unterschieden: Die leere Stelle
hinter By ist diesmal gefüllt, die Methode nimmt Werte entgegen, und die
Sortierung aus Aufgabe 2 hängt hinten wieder mit dran. Sie soll ja nicht
verlorengehen, nur weil gefiltert wird.
List<GuestbookEntry> findByDateGreaterThanEqualAndDateLessThanOrderByDateDesc(
LocalDateTime from, LocalDateTime to);
Mehr nicht. Kein Rumpf, kein SQL — und ein langer Name, der genau deshalb lang ist: Er enthält die vollständige Abfrage.
So liest Spring Data den Namen:
Neun Bausteine, eine Abfrage. Die oberen sechs bauen die Bedingung, die unteren
drei die Sortierung — dieselben drei, die in Aufgabe 2 schon
order by date_of_entry desc erzeugt haben.
Im Methodennamen steht Date — so heißt das Attribut in der Java-Klasse. Im SQL steht date_of_entry — so heißt die Spalte, festgelegt in Arbeitsblatt 01 mit @Column(name = "date_of_entry").
Du schreibst den Namen, den du in Java kennst; Hibernate setzt den Namen ein, den die Datenbank kennt. Genau dafür ist ein objektrelationaler Mapper da.
findByDateBetween?Der Name wäre kürzer, und die Vorschlagsliste bietet ihn zuerst an. Er ist hier aber falsch.
Between schließt beide Grenzen ein — date >= ? and date <= ?. Die obere Grenze ist der 1. Januar des Folgejahres um 00:00 Uhr. Ein Eintrag, der genau in dieser Sekunde verfasst wurde, gehört dann zu beiden Jahren: Er erscheint bei ?year=2026 und bei ?year=2027.
Ein einziges Zeichen Unterschied — < statt <= — und der Fehler zeigt sich nur an einem Wert von Milliarden. Genau darum geht es bei der Grenzwertanalyse im Infoblatt Testfälle formulieren.
Tippe im Repository findBy und warte auf die Vorschlagsliste deiner Entwicklungsumgebung. Sie kennt die Attribute deiner Entität und schlägt gültige Fortsetzungen vor — findByAuthor, findByTitleContaining, findByDateAfter und viele mehr.
Eine Übersicht steht im Infoblatt Abgeleitete Abfragen.
@GetMapping("/by-year")
public ResponseEntity<List<GuestbookEntry>> findByYear(@RequestParam Integer year) {
LocalDateTime from = LocalDate.of(year, 1, 1).atStartOfDay();
LocalDateTime to = from.plusYears(1);
return ResponseEntity.ok(
repository.findByDateGreaterThanEqualAndDateLessThanOrderByDateDesc(
from, to));
}
Die Umrechnung steht im Controller und nicht im Repository: Das Repository kennt Zeitpunkte, nicht Jahreszahlen. Aus „das Jahr 2026" einen Zeitraum zu machen, ist die Aufgabe der Schicht darüber.
Aufgabe 4 – Ein Endpunkt statt zwei
Du hast jetzt zwei Endpunkte: /guestbook-entries und /guestbook-entries/by-year. Das ist unnötig.
Überleg einmal fachlich: Beide beantworten dieselbe Frage — „gib mir Gästebucheinträge". Der zweite schränkt sie nur ein. Das ist kein eigener Anwendungsfall und verdient deshalb keine eigene Adresse.
Ein Endpunkt steht für einen fachlichen Vorgang. Nicht für jede Art, ihn einzuschränken.
Die Prüffrage vor jeder neuen Adresse lautet:
Ist das eine andere Frage — oder dieselbe Frage mit einer Einschränkung?
| Dieselbe Frage, nur eingeschränkt | Eine andere Frage |
|---|---|
| „Gästebucheinträge, aber nur aus 2026" | „Die Kommentare zu Eintrag 7" |
| „Gästebucheinträge, aber nur von Meyer" | „Ein neuer Eintrag" |
| „Gästebucheinträge, aber nur die ersten zehn" | „Die Einträge als CSV-Datei" |
| → derselbe Endpunkt, ein Parameter mehr | → eigene Adresse |
Warum das mehr ist als Ordnungsliebe. Adressen für Einschränkungen vermehren sich nicht einzeln, sondern multipliziert. Nach /by-year will jemand /by-author, dann beides zusammen. Drei Filter ergeben sieben Kombinationen — mit der ungefilterten Liste acht Adressen. Acht Endpunkte heißen: acht Einträge in der Dokumentation, acht Stellen im Test, acht Gelegenheiten, dass etwas auseinanderläuft. Die eine sortiert absteigend, die andere hat es beim letzten Umbau verloren, und auffallen wird es niemandem.
Mit Parametern wächst dieselbe Schnittstelle anders: Aus drei Filtern werden drei Parameter an einem Endpunkt, und der Client kombiniert sie, wie er sie braucht. Aus acht Adressen wird eine.
Die Gegenprobe. Wer wissen will, ob eine Adresse berechtigt ist, sagt sie laut als Satz: „Gib mir die Kommentare zu Eintrag 7" ist ein eigener Vorgang. „Gib mir die Gästebucheinträge nach Jahr" ist keiner — das nach Jahr ist eine Bedingung, kein Gegenstand. Was sich nur als Nebensatz sagen lässt, gehört hinter das Fragezeichen.
Dass eine Adresse die Sache benennt und nicht die Abfrage danach, ist der Kern des REST-Paradigmas — siehe Das REST-Paradigma. Wonach man einen Endpunkt zuschneidet, steht in DTOs und Schichten.
@GetMapping
public ResponseEntity<List<GuestbookEntry>> findAllEntries(
@RequestParam(required = false) Integer year) {
if (year == null) {
return ResponseEntity.ok(repository.findAllByOrderByDateDesc());
}
LocalDateTime from = LocalDate.of(year, 1, 1).atStartOfDay();
LocalDateTime to = from.plusYears(1);
return ResponseEntity.ok(
repository.findByDateGreaterThanEqualAndDateLessThanOrderByDateDesc(
from, to));
}
Dass beide Fälle gleich sortiert sind, ist keine Kleinigkeit. Ein Client sieht eine Adresse und verlässt sich auf eine Zusage. Hinge die Reihenfolge davon ab, ob er einen Filter gesetzt hat, könnte er das nicht ahnen — und würde es erst merken, wenn sich jemand über die Anzeige beschwert.
Beide Methodennamen enden deshalb auf OrderByDateDesc. Die Sortierung ist Teil
der Zusage, nicht Beiwerk eines einzelnen Aufrufs.
Aufgabe 5 – Seitenweise ausliefern
Bleibt die Frage der Kollegin: Was bei 10.000 Einträgen?
Auch dann liefert dein Endpunkt alle auf einmal. Die Antwort wäre mehrere Megabyte groß, der Browser des Kunden würde spürbar hängen. Echte APIs liefern deshalb Seiten.
Spring Data bringt das mit: Statt einer List gibt man eine Page zurück und nimmt ein Pageable entgegen.
Page<GuestbookEntry> findByDateGreaterThanEqualAndDateLessThan(
LocalDateTime from, LocalDateTime to, Pageable pageable);
Der Teil OrderByDateDesc aus Aufgabe 3 fällt dabei weg: Die Sortierung steht
jetzt im Pageable. Stünde sie an beiden Stellen, gäbe es zwei Quellen für
dieselbe Entscheidung — und der Client könnte die im Methodennamen nicht mehr
übersteuern.
@GetMapping
public ResponseEntity<Page<GuestbookEntry>> findAllEntries(
@RequestParam(required = false) Integer year,
@PageableDefault(size = 10, sort = "date", direction = Sort.Direction.DESC)
@ParameterObject Pageable pageable) {
if (year == null) {
return ResponseEntity.ok(repository.findAll(pageable));
}
LocalDateTime from = LocalDate.of(year, 1, 1).atStartOfDay();
LocalDateTime to = from.plusYears(1);
return ResponseEntity.ok(
repository.findByDateGreaterThanEqualAndDateLessThan(from, to, pageable));
}
Das Pageable trägt drei Angaben: welche Seite, wie groß, wie sortiert. @PageableDefault legt die Werte fest, falls der Client nichts angibt.
@ParameterObject gut istEin Pageable ist für Spring ein Parameter, für den Client aber drei — page, size und sort stehen einzeln in der URL.
Ohne @ParameterObject weiß springdoc davon nichts und schreibt in die Dokumentation genau einen Parameter namens pageable vom Typ object. In der Swagger-Oberfläche wird daraus ein Textfeld, in das man ein ganzes JSON-Objekt tippen müsste — und das funktioniert nicht, weil Spring die drei Werte einzeln erwartet.
@ParameterObject sagt springdoc: Zerlege das. Danach stehen dort drei Felder, jedes mit seinem Vorgabewert.
Der Import lautet org.springdoc.core.annotations.ParameterObject — die Annotation gehört zu springdoc, nicht zu Spring.
Damit erledigt sich die Sortierung aus Aufgabe 2 gleich mit: Du rufst wieder das schlichte findAll(pageable) auf, und die Reihenfolge steckt im Pageable. Deine Methode findAllByOrderByDateDesc() wird nicht mehr gebraucht und kann aus dem Repository verschwinden — genau wie der Endpunkt /by-year, den du in Aufgabe 4 schon abgeschafft hast.
Das ist kein vergebliches Arbeiten: Jeder dieser Schritte hat etwas gezeigt, was der nächste voraussetzt. So entsteht Software auch im Betrieb.
Der Client steuert es über Parameter:
GET /api/v1/guestbook-entries?page=0&size=5
GET /api/v1/guestbook-entries?page=1&size=5&sort=author,asc
GET /api/v1/guestbook-entries?year=2026&page=0&size=20
Bevor du das ausprobierst, spiel die Einstellungen einmal durch. Die Regler verändern dieselben zwei Werte, die auch in der URL stehen. Damit sich der Effekt überhaupt zeigt, liegen im Beispiel zwölf Einträge in der Datenbank — dein Gästebuch hat bisher weniger:
Die Antwort sieht jetzt anders aus:
{
"content": [ { "author": "...", "id": 4, "title": "..." } ],
"totalElements": 4,
"totalPages": 4,
"number": 0,
"size": 1,
"first": true,
"last": false
}
| Feld | Bedeutung |
|---|---|
content | die Einträge dieser Seite |
totalElements | wie viele es insgesamt gibt |
totalPages | wie viele Seiten das ergibt |
number | die aktuelle Seite, beginnend bei 0 |
size | wie viele pro Seite |
first / last | ob dies die erste bzw. letzte Seite ist |
Beim ersten Aufruf schreibt Spring Data eine lange Zeile ins Log:
Serializing PageImpl instances as-is is not supported, meaning that there is
no guarantee about the stability of the resulting JSON structure!
Sie ist berechtigt: Page ist eine interne Klasse von Spring Data, und ihre JSON-Form darf sich in einer künftigen Version ändern. Wer sie direkt nach außen gibt, macht eine Framework-Klasse zum Vertrag mit dem Client.
Für dieses Tutorial lassen wir es dabei — der Aufbau ist so am besten zu erkennen. In einem echten Projekt nimmt man PagedModel oder ein eigenes Antwortobjekt.
Warnungen im Log liest man. Sie stehen dort, weil jemand einen Grund hatte, sie zu schreiben.
Vorher kam ein Array, jetzt ein Objekt mit den Einträgen unter content. Ein Client, der bisher direkt über die Antwort gelaufen ist, muss angepasst werden.
Genau deshalb überlegt man sich Paginierung früh — ein späterer Umbau trifft alle Clients.
Aufgabe 6 – Zurück in die Dokumentation
Im vorigen Arbeitsblatt hast du die Schnittstelle dokumentiert. Seitdem hat sie drei Parameter dazubekommen — und du hast dafür keine Dokumentation geschrieben.
Die letzte Aufgabe ist die wichtigste. Swagger baut dir die Adresse zusammen —
aber es ist immer noch dieselbe Adresse, die du auch in requests.http
schreiben würdest:
http://localhost:8080/api/v1/guestbook-entries?page=0&size=10&sort=date%2CDESC
%2C bedeutetDas ist ein Komma. In einer URL haben manche Zeichen eine eigene Bedeutung,
deshalb werden sie als % und zwei Ziffern geschrieben — hier , als %2C.
Der Server macht daraus wieder ein Komma, bevor er date,DESC liest.
Für dich heißt das: Eine bequeme Oberfläche nimmt dir das Tippen ab, nicht das Verstehen. Darunter liegt immer eine gewöhnliche HTTP-Anfrage.
Aufgabe 7 – Die Anfragen zum Ausprobieren
### Einträge eines Jahres
GET http://localhost:8080/api/v1/guestbook-entries?year=2026
### Jahr ohne Einträge - erwartet 200 mit leerem content, nicht 404
GET http://localhost:8080/api/v1/guestbook-entries?year=2019
### Ungültiges Jahr - erwartet 400
GET http://localhost:8080/api/v1/guestbook-entries?year=abc
### Zwei Einträge je Seite
GET http://localhost:8080/api/v1/guestbook-entries?size=2
### Die zweite Seite
GET http://localhost:8080/api/v1/guestbook-entries?size=2&page=1
Den Aufruf mit /by-year aus Aufgabe 3 kannst du löschen — den Endpunkt gibt es
nicht mehr.
Aufgabe 8 – Testfälle
Anders als im ersten Tutorial überlebt der Datenbestand hier jeden Neustart — deine Einträge aus den vorigen Arbeitsblättern stehen alle noch da. Die Testfälle prüfen deshalb Verhältnisse statt fester Zahlen: „so viele wie vorher", „zwei pro Seite", „nur die aus diesem Jahr".
Notiere dir vor der Abnahme einmal, wie viele Einträge es gerade gibt (totalElements bei einem Aufruf ohne Parameter). Diese Zahl brauchst du in mehreren Testfällen.
| ID | Beschreibung | Vorbedingung | Testschritte | Erwartetes Ergebnis | Ergebnis |
|---|---|---|---|---|---|
| TF-01 | Neueste zuerst | Mindestens drei Einträge, nacheinander angelegt |
| Der zuletzt angelegte Eintrag steht in content an erster Stelle. | |
| TF-02 | Sortierung geschieht in der Datenbank | spring.jpa.show-sql=true ist gesetzt |
| Das erzeugte SQL enthält order by. | |
| TF-03 | Filter greift in der Datenbank | Einträge aus dem laufenden Jahr existieren |
| Das SQL enthält where ge1_0.date_of_entry>=? and ge1_0.date_of_entry<? — nicht ein einfaches select über die ganze Tabelle, und nicht between. | |
| TF-04 | Jahr ohne Einträge | Es gibt keine Einträge aus 2019 |
| Status 200, content ist [] und totalElements ist 0 — nicht 404. | |
| TF-05 | Parameter ist optional | Anwendung läuft |
| Status 200 mit allen Einträgen — nicht 400. | |
| TF-06 | Seitengröße wirkt | Mindestens vier Einträge existieren; ihre Anzahl wurde notiert |
| content enthält genau 2 Einträge. totalElements entspricht der notierten Gesamtzahl, totalPages ist diese Zahl aufgerundet durch 2. | |
| TF-07 | Blättern | TF-06 wurde ausgeführt |
| Andere Einträge als auf Seite 0, number ist 1. last ist true, sobald du auf der letzten Seite bist — bei mehr als vier Einträgen brauchst du dafür eine höhere page. | |
| TF-08 | Ungültiges Jahr | Anwendung läuft |
| Status 400 — der Wert lässt sich nicht in eine Zahl umwandeln. |
Ausblick
Dein Gästebuch ist funktionsfähig und dokumentiert. Diese Fragen bleiben offen:
- Was passiert, wenn jemand einen Eintrag ohne Titel oder mit einem 5000 Zeichen langen Kommentar schickt? (→ Validierung)
- Der Controller redet direkt mit dem Repository. Wo gehören eigentlich fachliche Regeln hin — etwa eine Sperre gegen Spam? (→ Service-Schicht)
- Du hast alle Testfälle von Hand ausgeführt. Beim nächsten Mal wieder? (→ automatisierte Tests)
- Die API gibt die Entität unverändert nach außen. Was, wenn sich die Datenbankstruktur ändert? (→ DTOs)
Diese vier Fragen sind kein Restposten, sondern der Stoff, mit dem es weitergeht — im Unterricht und in deinem Ausbildungsbetrieb. Wenn dir dort eine Service-Klasse, ein DTO oder ein @SpringBootTest begegnet: Genau davon ist hier die Rede.
Zusammenfassung
- Sortieren und Filtern gehören in die Datenbank, nicht in den Controller.
- Abgeleitete Abfragen entstehen aus dem Methodennamen — ganz ohne Implementierung.
findAllByOrderByDateDescwird zuorder by date_of_entry desc,findByDateGreaterThanEqualAndDateLessThanOrderByDateDesczuwhere date_of_entry >= ? and date_of_entry < ? order by date_of_entry desc. Der Methodenname nennt das Attribut, das SQL die Spalte. Betweenschließt beide Grenzen ein und ist für einen Zeitraum, der vor der oberen Grenze endet, das falsche Werkzeug.@RequestParamnimmt Werte aus der URL entgegen;required = falsemacht sie optional.- Ein Filter rechtfertigt keinen eigenen Endpunkt — er schränkt eine vorhandene Ressource ein.
PageableundPageliefern Ergebnisse seitenweise;@PageableDefaultsetzt Standardwerte.- Eine
Page-Antwort ist ein Objekt mitcontent,totalElements,totalPagesundnumber— kein Array mehr. - Im Log lässt sich am erzeugten SQL überprüfen, ob die Arbeit wirklich in der Datenbank passiert.
@ParameterObjectzerlegt dasPageablein der Dokumentation inpage,sizeundsort.- Die neuen Parameter erscheinen in der Swagger-Oberfläche, ohne dass jemand sie dort einträgt — das ist der Beleg für die Behauptung aus Arbeitsblatt 03.
Selbstkontrolle
- Warum ist Sortieren im Controller schlechter Stil?
- Wie wird aus
findByDateGreaterThanEqualAndDateLessThanOrderByDateDesceine SQL-Abfrage — und warum steht dort nichtBetween? - Wann nimmt man eine Pfadvariable, wann einen Request-Parameter?
- Warum bekommt der Jahresfilter keinen eigenen Endpunkt?
- Welchen Wert hat
numberauf der ersten Seite? - Woran erkennst du im Log, dass die Datenbank filtert und nicht dein Programm?
- In
findAllByOrderByDateDesc()steckt einBy, auf das nichts folgt. Warum steht es trotzdem da?
Antworten
- Weil die Anwendung dafür alle Datensätze aus der Datenbank holen und in den Hauptspeicher übertragen muss. Die Datenbank kann das schneller und mit weniger Datenverkehr.
- Spring Data zerlegt den Namen beim Start:
findByleitet die Abfrage ein,Datebenennt jeweils das Attribut,GreaterThanEqualundLessThandie Vergleiche.OrderByDateDeschängt die Sortierung an. Daraus entstehtwhere date_of_entry >= ? and date_of_entry < ? order by date_of_entry desc— der Methodenname nennt das Attributdate, Hibernate setzt den Spaltennamen ein.Betweenwäre falsch, weil es beide Grenzen einschließt — ein Eintrag exakt am 1. Januar des Folgejahres um 00:00 Uhr fiele dann in zwei Jahre. - Eine Pfadvariable bezeichnet welche Ressource gemeint ist (
/guestbook-entries/7). Ein Request-Parameter schränkt eine Sammlung ein (?year=2026). - Weil er keinen neuen Anwendungsfall darstellt. Gefragt sind weiterhin Gästebucheinträge — nur eine Teilmenge davon. Deshalb dieselbe Adresse mit optionalem Parameter.
0. Die Zählung beginnt bei null.- Das erzeugte SQL enthält eine
where-Bedingung. Fehlt sie und es steht nurselect ... from guestbook_entry, wurde die ganze Tabelle geholt und erst danach gefiltert. - Weil
Byin Spring Data immer den Bedingungsteil einleitet. Hier gibt es keine Bedingung — es sollen ja alle Einträge kommen —, deshalb bleibt die Stelle leer und es folgt sofortOrderBy. InfindByDateGreaterThanEqualAndDateLessThanOrderByDateDescist dieselbe Stelle gefüllt.
Musterlösung
Hier steht der vollständige Quelltext des Gästebuchs, so wie er nach allen vier Arbeitsblättern aussieht: die Entität, das Repository, der Controller, die OpenAPI-Konfiguration, die application.properties und die Datei requests.http.
Eine Lösung zu lesen fühlt sich an wie Verstehen, ist aber keines. Wer sie aufschlägt, bevor er selbst gescheitert ist, nimmt sich genau den Teil weg, an dem man etwas lernt.
Sinnvoll ist sie in zwei Fällen: nach der eigenen Lösung zum Vergleichen — oder wenn du an einer Stelle wirklich nicht weiterkommst. Dann sieh dir nur diese eine Datei an und arbeite danach selbst weiter.