Zum Hauptinhalt springen

Woche 5, Tag 2 — Repository Query Methods

Ziel

Heute verstehst du, wie du Daten mit Spring Data JPA Repositories abfragst.

Die Kernfragen:

  1. Was sind Repository Query Methods?
  2. Was sind abgeleitete Queries?
  3. Wie erstellt Spring Data Queries aus Methodennamen?
  4. Was ist @Query?
  5. Was ist JPQL?
  6. Was ist natives SQL?
  7. Was ist der Unterschied zwischen JPQL und SQL?
  8. Wie verwendest du Query-Parameter?
  9. Wie verwendest du Optional, List, Page und Slice?
  10. Wie verwendest du Pagination?
  11. Wie verwendest du Sortierung?
  12. 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.
  • EntityManager ist 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:

And kombiniert 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:

ContainingIgnoreCase ist 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/Desc fü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 @Query oder 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:

JPQLSQL
nutzt Entity-Namennutzt Tabellennamen
nutzt Feldnamennutzt Spaltennamen
datenbankunabhängigdatenbankspezifisch möglich
vom JPA-Providerdirekt 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:

@Param verbindet 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

ThemaJPQLNatives SQL
Arbeitet mitEntities und FeldernTabellen und Spalten
Portabelportablerdatenbankspezifisch
SyntaxJPQLechtes SQL
Nutzt JPA-Mappingjateilweise, je nach Ergebnis
Gut fürnormale Entity-Queriesfortgeschrittene 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:

@Modifying ist 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:

Sort fü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:

Sort nutzt 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:

Page löst meist eine zusätzliche Count-Query aus, um die Gesamtanzahl zu berechnen.

Merksatz:

Page liefert 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:

Page hat Totals. Slice ist 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:

Pageable funktioniert 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 @Query statt 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:

  1. Welche Query erzeugt findByStatus?
  2. Welche Query erzeugt findByClientIdAndStatus?
  3. Was bedeutet ContainingIgnoreCase?
  4. Was macht Pageable?
  5. Was gibt existsByTitle zurück?

Antworten:

  1. Findet Tasks, deren Status dem übergebenen Wert entspricht.
  2. Findet Tasks, bei denen clientId und status übereinstimmen.
  3. Sucht im Titel nach dem Keyword ohne Berücksichtigung der Groß-/Kleinschreibung.
  4. Wendet Pagination und ggf. Sortierung an.
  5. 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.
  • findByStatus bedeutet: nach dem Feld status abfragen.
  • And und Or kombinieren Bedingungen.
  • ContainingIgnoreCase ist 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.
  • @Param bindet benannte Query-Parameter.
  • @Modifying ist für Update-/Delete-Queries nötig.
  • Nutze Optional für null oder ein Ergebnis.
  • Nutze List für viele Ergebnisse.
  • Nutze Pagination für große Ergebnismengen.
  • Pageable trägt Seite, Größe und Sortierung.
  • Page enthält Informationen zur Gesamtanzahl.
  • Slice ist leichter als Page.
  • Seitenindizes sind nullbasiert.
  • Sort nutzt Entity-Property-Namen.
  • Falsche Repository-Property-Namen können beim Start zum Fehler führen.