Woche 5, Tag 2 — Repository Query Methods
Ziel
Heute verstehst du, wie du Daten mit Spring Data JPA Repositories abfragst.
Die Kernfragen:
- Was sind Repository Query Methods?
- Was sind abgeleitete Queries?
- Wie erstellt Spring Data Queries aus Methodennamen?
- Was ist
@Query? - Was ist JPQL?
- Was ist natives SQL?
- Was ist der Unterschied zwischen JPQL und SQL?
- Wie verwendest du Query-Parameter?
- Wie verwendest du
Optional,List,PageundSlice? - Wie verwendest du Pagination?
- Wie verwendest du Sortierung?
- Was sind typische Prüfungsfallen?
1. Kurz-Wiederholung aus Woche 5, Tag 1
In Tag 1 hast du gelernt:
- JPA ist die Java-Persistence-Spezifikation.
- Hibernate ist eine JPA-Implementierung.
- Spring Data JPA ist eine Repository-Abstraktion auf JPA.
- Eine Entity ist eine Java-Klasse, die auf eine Datenbanktabelle gemappt ist.
- Ein Repository verbirgt den Datenbankzugriff hinter einem Interface.
JpaRepository<Entity, Id>liefert gängige CRUD-Methoden.- Spring Data erstellt Repository-Proxy-Implementierungen automatisch.
EntityManagerist die JPA-API auf niedrigerer Ebene unter den Repositories.
Merksatz:
JPA definiert.
Hibernate implementiert.
Spring Data JPA vereinfacht.
Heute lernst du, wie du eigene Repository-Queries schreibst.
2. Warum Repository Query Methods wichtig sind
Das Basis-JpaRepository liefert Methoden wie:
save()
findById()
findAll()
deleteById()
existsById()
count()
Echte Anwendungen brauchen aber spezifischere Queries:
Benutzer per E-Mail finden
Tasks nach Status finden
Clients nach Firmen-ID finden
Rechnungen nach Datum finden
Tasks nach zugewiesenem Benutzer finden
nach Stichwort suchen
Ergebnisse paginieren
nach Erstellungsdatum sortieren
prüfen, ob E-Mail bereits existiert
Spring Data JPA unterstützt das mit:
abgeleitete Query-Methoden
@Query mit JPQL
@Query mit nativem SQL
Pagination
Sortierung
Projections
Merksatz:
Repository Query Methods ermöglichen es dir, Datenbankabfragen über Repository-Interfaces auszudrücken.
3. Beispiel-Entity
Für die Beispiele nutzen wir diese Entity:
@Entity
@Table(name = "tasks")
public class TaskEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long clientId;
@Column(nullable = false)
private String title;
@Column(nullable = false)
private String status;
@Column(nullable = false)
private String priority;
@Column(nullable = false)
private LocalDate dueDate;
@Column(nullable = false)
private Instant createdAt;
protected TaskEntity() {
}
public Long getId() {
return id;
}
public Long getClientId() {
return clientId;
}
public String getTitle() {
return title;
}
public String getStatus() {
return status;
}
public String getPriority() {
return priority;
}
public LocalDate getDueDate() {
return dueDate;
}
public Instant getCreatedAt() {
return createdAt;
}
}
Repository:
public interface TaskRepository extends JpaRepository<TaskEntity, Long> {
}
4. Was ist eine abgeleitete Query?
Eine abgeleitete Query ist eine Query, die Spring Data aus einem Repository-Methodennamen erzeugt.
Beispiel:
List<TaskEntity> findByStatus(String status);
Spring Data liest den Methodennamen:
findByStatus
und erzeugt eine Query wie:
select * from tasks where status = ?
Kurzdefinition:
Eine abgeleitete Query ist eine Query, die Spring Data aus dem Repository-Methodennamen ableitet.
Merksatz:
Bei abgeleiteten Queries beschreibt der Methodenname die Query.
5. Grundlegende Beispiele für abgeleitete Queries
Repository:
public interface TaskRepository extends JpaRepository<TaskEntity, Long> {
List<TaskEntity> findByStatus(String status);
List<TaskEntity> findByPriority(String priority);
Optional<TaskEntity> findByTitle(String title);
boolean existsByTitle(String title);
long countByStatus(String status);
}
Bedeutung:
findByStatus -> Tasks finden, deren Status dem übergebenen Wert entspricht
findByPriority -> Tasks finden, deren Priorität dem übergebenen Wert entspricht
findByTitle -> einen Task nach Titel finden
existsByTitle -> prüfen, ob ein Task mit diesem Titel existiert
countByStatus -> Tasks mit diesem Status zählen
6. Methoden-Präfixe
Gängige Präfixe:
findBy
readBy
getBy
queryBy
searchBy
streamBy
existsBy
countBy
deleteBy
removeBy
Am häufigsten:
findByStatus(String status)
existsByEmail(String email)
countByStatus(String status)
deleteByStatus(String status)
Für Lernen und Interviews konzentriere dich auf:
findBy
existsBy
countBy
deleteBy
7. Bedingungen mit And kombinieren
Beispiel:
List<TaskEntity> findByClientIdAndStatus(Long clientId, String status);
Bedeutung:
where client_id = ? and status = ?
Verwendung:
taskRepository.findByClientIdAndStatus(10L, "OPEN");
Spring mappt:
clientId -> erster Parameter
status -> zweiter Parameter
Merksatz:
Andkombiniert Bedingungen.
8. Bedingungen mit Or kombinieren
Beispiel:
List<TaskEntity> findByStatusOrPriority(String status, String priority);
Bedeutung:
where status = ? or priority = ?
Vorsicht.
Or-Queries werden schnell schwer lesbar, besonders bei vielen Bedingungen.
Wenn der Methodenname zu komplex wird, nutze @Query.
9. Gängige Schlüsselwörter
Spring-Data-Methodennamen können Schlüsselwörter enthalten.
Beispiele:
List<TaskEntity> findByStatusNot(String status);
List<TaskEntity> findByDueDateBefore(LocalDate date);
List<TaskEntity> findByDueDateAfter(LocalDate date);
List<TaskEntity> findByDueDateBetween(LocalDate start, LocalDate end);
List<TaskEntity> findByTitleContaining(String keyword);
List<TaskEntity> findByTitleContainingIgnoreCase(String keyword);
List<TaskEntity> findByStatusIn(List<String> statuses);
List<TaskEntity> findByClientIdIsNull();
List<TaskEntity> findByClientIdIsNotNull();
Bedeutung:
Not -> ungleich
Before -> kleiner als Datum/Zeit
After -> größer als Datum/Zeit
Between -> zwischen zwei Werten
Containing -> like %value%
IgnoreCase -> ohne Berücksichtigung der Groß-/Kleinschreibung
In -> Wert in Collection
IsNull -> null-Wert
IsNotNull -> nicht-null-Wert
10. Containing, StartingWith, EndingWith
Beispiele:
List<TaskEntity> findByTitleContaining(String keyword);
List<TaskEntity> findByTitleStartingWith(String prefix);
List<TaskEntity> findByTitleEndingWith(String suffix);
Bedeutung:
Containing -> Titel enthält Keyword
StartingWith -> Titel beginnt mit Präfix
EndingWith -> Titel endet mit Suffix
Für case-insensitive Suche:
List<TaskEntity> findByTitleContainingIgnoreCase(String keyword);
Merksatz:
ContainingIgnoreCaseist gängig für einfache Suchfelder.
11. Sortierung in Methodennamen
Beispiel:
List<TaskEntity> findByStatusOrderByCreatedAtDesc(String status);
Bedeutung:
where status = ?
order by created_at desc
Weiteres Beispiel:
List<TaskEntity> findByClientIdOrderByDueDateAsc(Long clientId);
Bedeutung:
Tasks für einen Client finden, sortiert nach Fälligkeitsdatum aufsteigend.
Merksatz:
OrderBy...Asc/Descfügt einer abgeleiteten Query statische Sortierung hinzu.
12. Ergebnisse begrenzen
Beispiele:
Optional<TaskEntity> findFirstByStatusOrderByCreatedAtDesc(String status);
List<TaskEntity> findTop5ByStatusOrderByCreatedAtDesc(String status);
Bedeutung:
findFirstBy -> erstes passendes Ergebnis
findTop5By -> erste 5 passende Ergebnisse
Nützlich für:
neuester Eintrag
Top-Ergebnisse
aktuelle Datensätze
13. Rückgabetypen
Gängige Rückgabetypen:
Optional<TaskEntity>
TaskEntity
List<TaskEntity>
Page<TaskEntity>
Slice<TaskEntity>
boolean
long
void
Beispiele:
Optional<TaskEntity> findByTitle(String title);
List<TaskEntity> findByStatus(String status);
Page<TaskEntity> findByClientId(Long clientId, Pageable pageable);
boolean existsByTitle(String title);
long countByStatus(String status);
Merksatz:
Wähle den Rückgabetyp danach, wie viele Ergebnisse du erwartest.
14. Optional vs. Entity als Rückgabetyp
Besser:
Optional<TaskEntity> findByTitle(String title);
Riskant:
TaskEntity findByTitle(String title);
Warum?
Das Ergebnis existiert möglicherweise nicht.
Optional macht das Fehlen explizit.
Nutze Optional, wenn null oder ein Ergebnis erwartet wird.
Nutze List, wenn viele Ergebnisse erwartet werden.
15. Abgeleiteter Query-Name kann zu lang werden
Schlecht:
List<TaskEntity> findByClientIdAndStatusAndPriorityAndDueDateBeforeAndTitleContainingIgnoreCaseOrderByCreatedAtDesc(
Long clientId,
String status,
String priority,
LocalDate dueDate,
String keyword
);
Problem:
schwer lesbar
schwer wartbar
leicht Fehler zu machen
Methodenname ist zu lang
Besser:
@Query nutzen
Specification nutzen
Querydsl nutzen
Criteria API nutzen
Custom Repository nutzen
Für die Zertifizierung merken:
Abgeleitete Queries sind gut für einfache Abfragen. Für komplexe Queries nutze
@Queryoder andere Techniken.
16. @Query
@Query ermöglicht es dir, die Query manuell zu definieren.
JPQL-Beispiel:
@Query("select t from TaskEntity t where t.status = :status")
List<TaskEntity> findTasksByStatus(@Param("status") String status);
Bedeutung:
Diese JPQL-Query nutzen, statt die Query aus dem Methodennamen abzuleiten.
Merksatz:
Nutze
@Query, wenn der Methodenname zu komplex oder unklar würde.
17. Was ist JPQL?
JPQL steht für:
Java Persistence Query Language
Kurzdefinition:
JPQL ist eine Query-Sprache, die mit JPA-Entities und ihren Feldern arbeitet — nicht direkt mit Datenbanktabellen und Spalten.
JPQL-Beispiel:
@Query("select t from TaskEntity t where t.status = :status")
List<TaskEntity> findByStatusWithQuery(@Param("status") String status);
Wichtig:
TaskEntity ist der Entity-Klassenname.
status ist der Entity-Feldname.
Nicht die Tabellen- und Spaltennamen.
Merksatz:
JPQL spricht über Entities. SQL spricht über Tabellen.
18. JPQL vs. SQL
Entity:
@Entity
@Table(name = "tasks")
public class TaskEntity {
private String status;
}
JPQL:
select t from TaskEntity t where t.status = :status
SQL:
select * from tasks t where t.status = ?
Unterschied:
| JPQL | SQL |
|---|---|
| nutzt Entity-Namen | nutzt Tabellennamen |
| nutzt Feldnamen | nutzt Spaltennamen |
| datenbankunabhängig | datenbankspezifisch möglich |
| vom JPA-Provider | direkt von der Datenbank |
19. JPQL-Query mit mehreren Bedingungen
@Query("""
select t
from TaskEntity t
where t.clientId = :clientId
and t.status = :status
order by t.createdAt desc
""")
List<TaskEntity> findClientTasksByStatus(
@Param("clientId") Long clientId,
@Param("status") String status
);
Das ist leichter lesbar als ein sehr langer Methodenname.
20. Benannte Parameter
Beispiel:
@Query("select t from TaskEntity t where t.status = :status")
List<TaskEntity> findTasks(@Param("status") String status);
:status ist ein benannter Parameter.
@Param("status") bindet den Java-Methodenparameter an den Query-Parameter.
Merksatz:
@Paramverbindet Methodenparameter mit benannten Query-Parametern.
21. Positionsparameter
Beispiel:
@Query("select t from TaskEntity t where t.status = ?1 and t.priority = ?2")
List<TaskEntity> findTasks(String status, String priority);
Bedeutung:
?1 = erster Methodenparameter
?2 = zweiter Methodenparameter
Das funktioniert, aber benannte Parameter sind meist klarer.
Besser:
@Query("""
select t
from TaskEntity t
where t.status = :status
and t.priority = :priority
""")
List<TaskEntity> findTasks(
@Param("status") String status,
@Param("priority") String priority
);
Merksatz:
Bevorzuge benannte Parameter für bessere Lesbarkeit.
22. Query mit DTO-Rückgabe
JPQL kann DTOs direkt mit Konstruktorausdrücken erzeugen.
DTO:
public record TaskSummaryDto(
Long id,
String title
) {
}
Query:
@Query("""
select new de.klarsync.tasks.TaskSummaryDto(t.id, t.title)
from TaskEntity t
where t.status = :status
""")
List<TaskSummaryDto> findTaskSummariesByStatus(@Param("status") String status);
Wichtig:
JPQL-Konstruktorausdruck braucht den vollqualifizierten Klassennamen.
Das vermeidet das Laden vollständiger Entity-Daten, wenn nur DTO-Felder gebraucht werden.
23. Native SQL-Query
Eine native Query nutzt echtes SQL.
Beispiel:
@Query(
value = "select * from tasks where status = :status",
nativeQuery = true
)
List<TaskEntity> findByStatusNative(@Param("status") String status);
Das nutzt:
Tabellenname: tasks
Spaltenname: status
nicht den Entity-Namen.
Merksatz:
Native Query bedeutet echtes SQL.
24. Wann natives SQL nutzen
Nutze natives SQL, wenn:
datenbankspezifische Features werden gebraucht
Query ist zu komplex für JPQL
Performance erfordert spezifisches SQL
CTEs oder Window Functions werden genutzt
datenbankspezifische Funktionen werden aufgerufen
Aber Vorsicht.
Natives SQL ist weniger portabel zwischen Datenbanken.
Beispiel:
PostgreSQL-spezifisches SQL funktioniert möglicherweise nicht auf MySQL.
Merksatz:
Natives SQL ist mächtig, aber weniger portabel.
25. JPQL vs. native Query
| Thema | JPQL | Natives SQL |
|---|---|---|
| Arbeitet mit | Entities und Feldern | Tabellen und Spalten |
| Portabel | portabler | datenbankspezifisch |
| Syntax | JPQL | echtes SQL |
| Nutzt JPA-Mapping | ja | teilweise, je nach Ergebnis |
| Gut für | normale Entity-Queries | fortgeschrittene datenbankspezifische Queries |
26. Modifying Queries
Für Update- oder Delete-Queries nutze:
@Modifying
Beispiel:
@Modifying
@Query("update TaskEntity t set t.status = :status where t.id = :id")
int updateStatus(
@Param("id") Long id,
@Param("status") String status
);
Wichtig:
Modifying Queries brauchen eine Transaktion.
Rufe sie meist innerhalb einer @Transactional-Service-Methode auf.
Transaktionen schauen wir uns bald an.
Merksatz:
@Modifyingist für Update-/Delete-Queries nötig.
27. Abgeleitete Delete-Query
Beispiel:
void deleteByStatus(String status);
oder:
long deleteByClientId(Long clientId);
Vorsicht.
Delete-Queries können viele Zeilen entfernen.
Schütze sie meist mit Business-Logik in der Service-Schicht.
28. Sortierung
Spring Data unterstützt dynamische Sortierung mit Sort.
Repository:
List<TaskEntity> findByStatus(String status, Sort sort);
Service:
List<TaskEntity> tasks = taskRepository.findByStatus(
"OPEN",
Sort.by(Sort.Direction.DESC, "createdAt")
);
Bedeutung:
Offene Tasks finden und nach createdAt absteigend sortieren.
Merksatz:
Sortfügt Repository-Queries dynamische Sortierung hinzu.
29. Mehrere Sortierfelder
Beispiel:
Sort sort = Sort.by(
Sort.Order.asc("priority"),
Sort.Order.desc("createdAt")
);
List<TaskEntity> tasks = taskRepository.findByStatus("OPEN", sort);
Bedeutung:
order by priority asc, createdAt desc
Wichtig:
Sort-Property-Namen nutzen Entity-Feldnamen, nicht Datenbank-Spaltennamen.
Merksatz:
Sortnutzt Entity-Property-Namen.
30. Pagination
Pagination bedeutet, Ergebnisse seitenweise zu laden statt alle Zeilen auf einmal.
Warum?
zu viele Daten vermeiden
Performance verbessern
UI-Seiten unterstützen
API-Paging unterstützen
Speicherverbrauch reduzieren
Repository:
Page<TaskEntity> findByClientId(Long clientId, Pageable pageable);
Service:
Pageable pageable = PageRequest.of(0, 20);
Page<TaskEntity> page = taskRepository.findByClientId(10L, pageable);
Bedeutung:
Seite 0
Größe 20
Wichtig:
Seitenindizes sind nullbasiert.
Merksatz:
PageRequest.of(0, 20)bedeutet die erste Seite mit 20 Einträgen.
31. Pagination mit Sortierung
Pageable pageable = PageRequest.of(
0,
20,
Sort.by(Sort.Direction.DESC, "createdAt")
);
Page<TaskEntity> page = taskRepository.findByStatus("OPEN", pageable);
Repository:
Page<TaskEntity> findByStatus(String status, Pageable pageable);
Bedeutung:
Erste Seite offener Tasks finden, 20 pro Seite, neueste zuerst.
32. Page
Page<T> enthält:
Inhalt
Seitennummer
Seitengröße
Gesamtanzahl
Gesamtseitenzahl
ob nächste Seite existiert
ob vorherige Seite existiert
Beispiel:
Page<TaskEntity> page = taskRepository.findByStatus("OPEN", pageable);
List<TaskEntity> content = page.getContent();
int pageNumber = page.getNumber();
int pageSize = page.getSize();
long totalElements = page.getTotalElements();
int totalPages = page.getTotalPages();
boolean hasNext = page.hasNext();
Wichtig:
Pagelöst meist eine zusätzliche Count-Query aus, um die Gesamtanzahl zu berechnen.
Merksatz:
Pageliefert Inhalt plus Informationen zur Gesamtanzahl.
33. Slice
Slice<T> ist leichter als Page<T>.
Repository:
Slice<TaskEntity> findByStatus(String status, Pageable pageable);
Slice enthält:
Inhalt
Seiteninfo
ob nächster Slice existiert
Braucht aber keine Gesamtanzahl.
Nutze Slice, wenn:
Du brauchst nur zu wissen, ob eine nächste Seite existiert.
Du brauchst keine Gesamtanzahl oder Gesamtseitenzahl.
Merksatz:
Pagehat Totals.Sliceist leichter und braucht keine vollständige Gesamtanzahl.
34. Mapping von Entity-Page zu DTO-Page
Entity-Page:
Page<TaskEntity> entityPage = taskRepository.findByStatus("OPEN", pageable);
Auf DTO-Page mappen:
Page<TaskDto> dtoPage = entityPage.map(this::toDto);
Beispiel:
private TaskDto toDto(TaskEntity entity) {
return new TaskDto(
entity.getId(),
entity.getTitle(),
entity.getStatus()
);
}
Merksatz:
Nutze
page.map(...), um eine Entity-Page in eine DTO-Page zu konvertieren.
35. Controller-Pagination-Beispiel
Controller:
@GetMapping("/api/tasks")
public Page<TaskDto> list(
@RequestParam(defaultValue = "OPEN") String status,
@RequestParam(defaultValue = "0") @Min(0) int page,
@RequestParam(defaultValue = "20") @Min(1) @Max(100) int size
) {
return taskService.findByStatus(status, page, size);
}
Service:
public Page<TaskDto> findByStatus(String status, int page, int size) {
Pageable pageable = PageRequest.of(
page,
size,
Sort.by(Sort.Direction.DESC, "createdAt")
);
return taskRepository.findByStatus(status, pageable)
.map(this::toDto);
}
Repository:
Page<TaskEntity> findByStatus(String status, Pageable pageable);
36. Vorsicht beim direkten Zurückgeben von Page
Page<TaskDto> zurückzugeben ist einfach und gängig.
Manche Teams bevorzugen aber ein eigenes Response-DTO.
Beispiel:
public record PageResponse<T>(
List<T> content,
int page,
int size,
long totalElements,
int totalPages,
boolean hasNext
) {
}
Warum?
stabile API-Response-Struktur
weniger Kopplung an Spring-Data-Page-JSON-Struktur
mehr Kontrolle
Zum Lernen ist Page<T> in Ordnung.
Für öffentliche APIs kann ein eigenes Page-Response besser sein.
37. findAll(Pageable pageable)
JpaRepository liefert bereits:
Page<TaskEntity> findAll(Pageable pageable);
Verwendung:
Pageable pageable = PageRequest.of(0, 20);
Page<TaskEntity> tasks = taskRepository.findAll(pageable);
Auch Sortierung:
List<TaskEntity> tasks = taskRepository.findAll(
Sort.by(Sort.Direction.DESC, "createdAt")
);
38. Query-Methoden mit Pageable
Beispiel:
Page<TaskEntity> findByClientIdAndStatus(
Long clientId,
String status,
Pageable pageable
);
Verwendung:
Pageable pageable = PageRequest.of(0, 20);
Page<TaskEntity> tasks = taskRepository.findByClientIdAndStatus(
10L,
"OPEN",
pageable
);
Wichtig:
Pageable ist meist der letzte Parameter.
39. @Query mit Pageable
Beispiel:
@Query("""
select t
from TaskEntity t
where t.clientId = :clientId
and t.status = :status
""")
Page<TaskEntity> findClientTasks(
@Param("clientId") Long clientId,
@Param("status") String status,
Pageable pageable
);
Spring kann Pagination auf diese Query anwenden.
Bei komplexen Queries muss die Count-Query eventuell angepasst werden.
Beispiel:
@Query(
value = """
select t
from TaskEntity t
where t.clientId = :clientId
and t.status = :status
""",
countQuery = """
select count(t)
from TaskEntity t
where t.clientId = :clientId
and t.status = :status
"""
)
Page<TaskEntity> findClientTasks(
@Param("clientId") Long clientId,
@Param("status") String status,
Pageable pageable
);
Merksatz:
Pageablefunktioniert mit abgeleiteten Queries und@Query.
40. Validierung von Query-Methoden beim Start
Spring Data prüft Repository-Query-Methoden beim Start.
Beispiel-Fehler:
List<TaskEntity> findByStatuz(String status);
Das Entity-Feld heißt aber:
status
nicht:
statuz
Ergebnis:
Anwendung kann beim Start fehlschlagen.
Warum?
Spring Data kann keine Query für eine nicht existierende Property erzeugen.
Merksatz:
Falsche Property-Namen in abgeleiteten Queries können beim Start zum Fehler führen.
41. Häufiger Fehler: Datenbank-Spaltenname in abgeleiteter Query
Entity-Feld:
private Instant createdAt;
Spalte:
@Column(name = "created_at")
private Instant createdAt;
Falsche Repository-Methode:
List<TaskEntity> findByCreated_at(Instant createdAt);
Richtig:
List<TaskEntity> findByCreatedAt(Instant createdAt);
Warum?
Abgeleitete Queries nutzen Entity-Property-Namen, nicht Datenbank-Spaltennamen.
Merksatz:
Repository-Methodennamen nutzen Java-Feldnamen.
42. Häufiger Fehler: Zu viele Ergebnisse für Einzelrückgabe
Repository:
Optional<TaskEntity> findByStatus(String status);
Aber viele Tasks können denselben Status haben.
Problem:
Query kann mehrere Ergebnisse zurückgeben, obwohl nur eines erwartet wird.
Besser:
List<TaskEntity> findByStatus(String status);
Nutze Optional nur, wenn null oder ein Ergebnis erwartet wird.
43. Häufiger Fehler: N+1-Problem — Vorschau
Beispiel:
List<TaskEntity> findByStatus(String status);
Wenn TaskEntity lazy Relationships hat und der Code sie nacheinander aufruft, kann das viele zusätzliche Queries auslösen.
Das nennt man:
N+1 query problem
Das schauen wir uns später an.
Merke vorerst:
Repository-Queries können die Performance beeinflussen — besonders bei Relationships.
44. Häufiger Fehler: Zu viele Daten abfragen
Schlecht:
List<TaskEntity> findAll();
für eine große Tabelle.
Besser:
Page<TaskEntity> findAll(Pageable pageable);
oder eine gefilterte Query:
Page<TaskEntity> findByClientId(Long clientId, Pageable pageable);
Merksatz:
Lade große Tabellen nicht ohne Pagination oder Filterung.
45. Welchen Query-Stil solltest du nutzen?
Einfache Bedingung:
List<TaskEntity> findByStatus(String status);
Mehrere einfache Bedingungen:
List<TaskEntity> findByClientIdAndStatus(Long clientId, String status);
Lange oder komplexe Query:
@Query(...)
Datenbankspezifische Query:
@Query(nativeQuery = true, ...)
Dynamische komplexe Filter:
Specification
Criteria API
Querydsl
Custom Repository
Merksatz:
Nutze den einfachsten Query-Stil, der lesbar bleibt.
46. Typische Prüfungsfallen
Prüfungsfalle 1
Spring Data leitet Queries aus Methodennamen ab.
Prüfungsfalle 2
Abgeleitete Queries nutzen Entity-Property-Namen, nicht Datenbank-Spaltennamen.
Prüfungsfalle 3
findByStatus erwartet ein Entity-Feld namens status.
Prüfungsfalle 4
JpaRepository liefert bereits grundlegende CRUD-Methoden.
Prüfungsfalle 5
Nutze Optional, wenn null oder ein Ergebnis erwartet wird.
Prüfungsfalle 6
Nutze List, wenn viele Ergebnisse erwartet werden.
Prüfungsfalle 7
JPQL nutzt Entity-Namen und Felder.
Prüfungsfalle 8
Natives SQL nutzt Tabellennamen und Spalten.
Prüfungsfalle 9
@Param bindet Methodenparameter an benannte Query-Parameter.
Prüfungsfalle 10
@Modifying ist für Update-/Delete-Queries nötig.
Prüfungsfalle 11
Page enthält Informationen zur Gesamtanzahl.
Prüfungsfalle 12
Slice ist leichter und enthält keine Gesamtanzahl.
Prüfungsfalle 13
Seitenindizes sind nullbasiert.
Prüfungsfalle 14
Sort nutzt Entity-Property-Namen.
Prüfungsfalle 15
Falsche Repository-Methodennamen können beim Start zum Fehler führen.
47. Prüfungsfrage: Abgeleitete Query
Frage:
Was ist eine abgeleitete Query in Spring Data JPA?
Antwort:
Eine abgeleitete Query ist eine Query, die Spring Data aus dem Repository-Methodennamen erzeugt — zum Beispiel findByStatus oder findByEmail.
48. Prüfungsfrage: Property im Methodennamen
Frage:
Bezieht sich CreatedAt in findByCreatedAt auf die Java-Entity-Property oder die Datenbank-Spalte?
Antwort:
Es bezieht sich auf den Java-Entity-Property-Namen, nicht auf den Datenbank-Spaltennamen.
49. Prüfungsfrage: @Query
Frage:
Wann solltest du @Query nutzen?
Antwort:
Nutze @Query, wenn ein abgeleiteter Query-Methodenname zu lang oder unklar wäre — oder wenn du mehr Kontrolle über die Query brauchst.
50. Prüfungsfrage: JPQL
Frage:
Was ist JPQL?
Antwort:
JPQL ist die Java Persistence Query Language. Sie fragt JPA-Entities und ihre Felder ab — nicht Datenbanktabellen und Spalten.
51. Prüfungsfrage: Native Query
Frage:
Was ist eine native Query?
Antwort:
Eine native Query ist eine Query in echtem SQL mit Datenbank-Tabellen- und Spaltennamen. In Spring Data JPA kann sie mit @Query(nativeQuery = true) deklariert werden.
52. Prüfungsfrage: @Param
Frage:
Was macht @Param?
Antwort:
@Param bindet einen Repository-Methodenparameter an einen benannten Parameter in einer JPQL- oder nativen Query.
53. Prüfungsfrage: @Modifying
Frage:
Wann ist @Modifying nötig?
Antwort:
@Modifying ist nötig für Repository-Queries, die Daten ändern — zum Beispiel Update- oder Delete-Queries, die mit @Query deklariert sind.
54. Prüfungsfrage: Page
Frage:
Was enthält Page<T>?
Antwort:
Page<T> enthält den Seiteninhalt plus Pagination-Metadaten wie Seitennummer, Größe, Gesamtanzahl, Gesamtseitenzahl und ob eine nächste oder vorherige Seite existiert.
55. Prüfungsfrage: Slice
Frage:
Was ist der Unterschied zwischen Page und Slice?
Antwort:
Page enthält Informationen zur Gesamtanzahl und Gesamtseitenzahl. Slice ist leichter und zeigt nur an, ob ein nächster Slice existiert — ohne Gesamtanzahl.
56. Prüfungsfrage: Seitenindex
Frage:
Ist PageRequest.of(0, 20) die erste oder die zweite Seite?
Antwort:
Es ist die erste Seite. Spring-Data-Seitenindizes sind nullbasiert.
57. Prüfungsfrage: Sort
Frage:
Nutzt Sort.by("createdAt") den Entity-Property-Namen oder den Datenbank-Spaltennamen?
Antwort:
Es nutzt den Entity-Property-Namen.
58. Interview-Antwort
Frage:
Wie funktionieren abgeleitete Query-Methoden in Spring Data JPA?
Gute Antwort:
Spring Data JPA kann Queries aus Repository-Methodennamen erzeugen. Zum Beispiel erzeugt findByStatus eine Query mit dem Entity-Feld status, und findByClientIdAndStatus eine Query mit beiden Bedingungen. Die Methodennamen nutzen Java-Entity-Property-Namen, nicht Datenbank-Spaltennamen. Abgeleitete Queries sind gut für einfache Abfragen — wird der Methodenname zu lang oder unklar, nutze ich @Query.
59. Interview-Antwort
Frage:
Was ist der Unterschied zwischen JPQL und nativem SQL?
Gute Antwort:
JPQL fragt JPA-Entities und ihre Felder ab — es nutzt also Entity-Klassennamen und Java-Property-Namen. Natives SQL fragt Datenbanktabellen und Spalten direkt ab. JPQL ist portabler zwischen Datenbanken, während natives SQL für datenbankspezifische Features oder komplexe optimierte Queries nützlich ist.
60. Interview-Antwort
Frage:
Wie implementierst du Pagination in Spring Data JPA?
Gute Antwort:
Ich füge einen Pageable-Parameter zur Repository-Methode hinzu und gebe Page<T> oder Slice<T> zurück. Im Service erstelle ich ein PageRequest, zum Beispiel PageRequest.of(page, size, Sort.by("createdAt").descending()). Page liefert Inhalt plus Gesamtanzahl, während Slice leichter ist und nur anzeigt, ob ein weiterer Slice existiert.
61. Interview-Antwort
Frage:
Wann würdest du
@Querystatt einer abgeleiteten Query nutzen?
Gute Antwort:
Ich nutze @Query, wenn der abgeleitete Methodenname zu lang oder schwer lesbar wird, wenn ich eine komplexere Query brauche, wenn ich eine DTO-Projection schreiben will oder wenn ich eine native SQL-Query für datenbankspezifische Features brauche. Für einfache Bedingungen wie findByStatus reichen abgeleitete Queries meist aus.
62. Interview-Antwort
Frage:
Welche Fehler können bei Repository Query Methods passieren?
Gute Antwort:
Häufige Fehler sind: Datenbank-Spaltennamen statt Entity-Property-Namen nutzen, zu lange Methodennamen erstellen, Optional zurückgeben wenn mehrere Zeilen matchen können, @Param-Namen in @Query vergessen, @Modifying bei Update-/Delete-Queries vergessen und zu viele Daten ohne Pagination laden.
63. Kleine Code-Übung
Repository:
public interface TaskRepository extends JpaRepository<TaskEntity, Long> {
List<TaskEntity> findByStatus(String status);
List<TaskEntity> findByClientIdAndStatus(Long clientId, String status);
List<TaskEntity> findByTitleContainingIgnoreCase(String keyword);
Page<TaskEntity> findByClientId(Long clientId, Pageable pageable);
boolean existsByTitle(String title);
long countByStatus(String status);
}
Fragen:
- Welche Query erzeugt
findByStatus? - Welche Query erzeugt
findByClientIdAndStatus? - Was bedeutet
ContainingIgnoreCase? - Was macht
Pageable? - Was gibt
existsByTitlezurück?
Antworten:
- Findet Tasks, deren Status dem übergebenen Wert entspricht.
- Findet Tasks, bei denen
clientIdundstatusübereinstimmen. - Sucht im Titel nach dem Keyword ohne Berücksichtigung der Groß-/Kleinschreibung.
- Wendet Pagination und ggf. Sortierung an.
- Ein
boolean, das angibt, ob ein Task mit diesem Titel existiert.
64. Kleine Bug-Übung 1
Entity-Feld:
@Column(name = "created_at")
private Instant createdAt;
Repository:
List<TaskEntity> findByCreated_at(Instant createdAt);
Frage:
Was ist falsch?
Antwort:
Abgeleitete Query-Methoden nutzen Java-Entity-Property-Namen, nicht Datenbank-Spaltennamen. Die richtige Methode ist:
List<TaskEntity> findByCreatedAt(Instant createdAt);
65. Kleine Bug-Übung 2
Repository:
Optional<TaskEntity> findByStatus(String status);
Frage:
Was ist riskant?
Antwort:
Viele Tasks können denselben Status haben — die Query kann also mehrere Zeilen zurückgeben. Optional ist nur gut, wenn null oder ein Ergebnis erwartet wird. Nutze:
List<TaskEntity> findByStatus(String status);
66. Kleine Bug-Übung 3
Repository:
@Query("update TaskEntity t set t.status = :status where t.id = :id")
int updateStatus(Long id, String status);
Frage:
Was fehlt?
Antwort:
Für Update-/Delete-Queries ist @Modifying nötig. Nutze außerdem @Param für benannte Parameter.
Richtig:
@Modifying
@Query("update TaskEntity t set t.status = :status where t.id = :id")
int updateStatus(
@Param("id") Long id,
@Param("status") String status
);
Das sollte innerhalb einer Transaktion aufgerufen werden.
Übungsfragen
Frage 1
Was ist eine abgeleitete Query?
Antwort:
Eine abgeleitete Query ist eine Query, die Spring Data aus dem Repository-Methodennamen erzeugt.
Frage 2
Was bedeutet findByStatus(String status)?
Antwort:
Es findet Entities, deren status-Property dem übergebenen Parameter entspricht.
Frage 3
Was bedeutet findByClientIdAndStatus(Long clientId, String status)?
Antwort:
Es findet Entities, bei denen sowohl clientId als auch status mit den übergebenen Parametern übereinstimmen.
Frage 4
Was bedeutet findByTitleContainingIgnoreCase(String keyword)?
Antwort:
Es findet Entities, deren Titel das Keyword enthält — ohne Berücksichtigung der Groß-/Kleinschreibung.
Frage 5
Nutzen abgeleitete Query-Methoden Entity-Property-Namen oder Datenbank-Spaltennamen?
Antwort:
Sie nutzen Entity-Property-Namen, nicht Datenbank-Spaltennamen.
Frage 6
Wann solltest du @Query nutzen?
Antwort:
Nutze @Query, wenn ein abgeleiteter Methodenname zu lang oder unklar wäre — oder wenn du mehr Kontrolle über die Query brauchst.
Frage 7
Was ist JPQL?
Antwort:
JPQL ist die Java Persistence Query Language. Sie fragt JPA-Entities und ihre Felder ab.
Frage 8
Was ist der Unterschied zwischen JPQL und nativem SQL?
Antwort:
JPQL nutzt Entity-Namen und Felder. Natives SQL nutzt Datenbank-Tabellen und Spalten.
Frage 9
Was macht @Param?
Antwort:
@Param bindet einen Repository-Methodenparameter an einen benannten Query-Parameter.
Frage 10
Wann brauchst du @Modifying?
Antwort:
Nutze @Modifying für Update- oder Delete-Queries, die mit @Query deklariert sind.
Frage 11
Wann solltest du Optional als Repository-Rückgabetyp nutzen?
Antwort:
Nutze Optional, wenn null oder ein Ergebnis erwartet wird.
Frage 12
Wann solltest du List als Repository-Rückgabetyp nutzen?
Antwort:
Nutze List, wenn viele Ergebnisse matchen können.
Frage 13
Was ist Pageable?
Antwort:
Pageable repräsentiert Pagination-Informationen wie Seitennummer, Seitengröße und Sortierung.
Frage 14
Was enthält Page<T>?
Antwort:
Page<T> enthält Inhalt plus Pagination-Metadaten wie Gesamtanzahl, Gesamtseitenzahl, Seitennummer, Seitengröße und Informationen zu nächster/vorheriger Seite.
Frage 15
Was ist der Unterschied zwischen Page und Slice?
Antwort:
Page enthält Informationen zur Gesamtanzahl. Slice ist leichter und zeigt nur an, ob ein nächster Slice existiert.
Frage 16
Ist der erste Seitenindex 0 oder 1?
Antwort:
Der erste Seitenindex ist 0.
Frage 17
Was nutzt Sort.by("createdAt") — Entity-Property oder Datenbank-Spalte?
Antwort:
Es nutzt den Entity-Property-Namen.
Frage 18
Warum solltest du findAll() auf großen Tabellen vermeiden?
Antwort:
Weil es zu viele Daten in den Speicher laden und die Performance beeinträchtigen kann. Nutze Filterung und Pagination.
Frage 19
Wie kannst du Page<TaskEntity> auf Page<TaskDto> mappen?
Antwort:
Nutze:
Page<TaskDto> dtoPage = entityPage.map(this::toDto);
Frage 20
Was kann passieren, wenn eine abgeleitete Query-Methode auf eine nicht existierende Entity-Property verweist?
Antwort:
Die Anwendung kann beim Start fehlschlagen, weil Spring Data die Query nicht erzeugen kann.
Merksätze zum Mitnehmen
- Abgeleitete Queries werden aus Repository-Methodennamen erzeugt.
- Repository-Methodennamen nutzen Entity-Property-Namen, nicht Datenbank-Spaltennamen.
findByStatusbedeutet: nach dem Feldstatusabfragen.AndundOrkombinieren Bedingungen.ContainingIgnoreCaseist nützlich für einfache Textsuche.- Nutze
@Query, wenn abgeleitete Methodennamen zu lang oder unklar werden. - JPQL fragt Entities und Felder ab.
- Natives SQL fragt Tabellen und Spalten ab.
@Parambindet benannte Query-Parameter.@Modifyingist für Update-/Delete-Queries nötig.- Nutze
Optionalfür null oder ein Ergebnis. - Nutze
Listfür viele Ergebnisse. - Nutze Pagination für große Ergebnismengen.
Pageableträgt Seite, Größe und Sortierung.Pageenthält Informationen zur Gesamtanzahl.Sliceist leichter alsPage.- Seitenindizes sind nullbasiert.
Sortnutzt Entity-Property-Namen.- Falsche Repository-Property-Namen können beim Start zum Fehler führen.