Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Eine API-Anforderung klingt oft einfach: «Zeige offene Tickets», «Liefere eine kompakte Liste» oder «Wie viele Tickets hat jede Priorität?». Trotzdem ist der Datenzugriff eine Designentscheidung. Eine sehr kurze Repository Method kann eine klare Anforderung gut ausdrücken. Bei mehreren Bedingungen wird derselbe Methodenname schnell schwer lesbar. Und ein Report mit Zählungen ist in SQL häufig klarer als eine verschachtelte Objektabfrage.

In diesem Block lernst du nicht möglichst viele Abfragesprachen. Du lernst, für eine konkrete Anforderung die kleinste verständliche Query-Form zu wählen. Das Ticket-System bleibt dabei der gemeinsame Fall: aktive Tickets listen, nur die nötigen Felder laden und eine Auswertung für den Support erstellen.

Falls du nach Block 4 in den Kurs einsteigst, erzeugst du zuerst den Checkpoint block-5-start. Für die Kontrolle steht block-5-complete bereit. Die Befehle und die Idee dahinter findest du im Abschnitt Checkpoints.

Die API-Anforderung bestimmt die Query

Ein Repository ist keine Sammlung möglichst cleverer Queries. Es ist die Übersetzung einer fachlichen Leseanforderung in einen gezielten Datenzugriff. Vor jeder technischen Entscheidung helfen vier Fragen:

  1. Was braucht der aufrufende Endpunkt wirklich? Eine Liste braucht häufig weniger Felder als eine Detailansicht.

  2. Wie komplex ist die fachliche Bedingung? Ein einzelner Statusfilter ist etwas anderes als eine gruppierte Auswertung mit Kommentaren.

  3. Muss die Query über Java-Entities oder direkt über das Schema sprechen? Beide Perspektiven sind sinnvoll, aber nicht für denselben Zweck.

  4. Welche Daten dürfen nicht erscheinen? Im Ticket-System gehören soft-gelöschte Tickets nicht in normale Listen und Reports.

Die Entscheidung ist kein Wettbewerb. Gute Query Design bedeutet nicht, immer SQL oder immer JPQL zu verwenden. Es bedeutet, dass ein Team die gewählte Form später noch lesen, testen und bei einer Anforderungsänderung anpassen kann.

KriteriumFrage im Review
LesbarkeitVersteht eine neue Entwicklerin die fachliche Absicht ohne die ganze Query zu entschlüsseln?
WartbarkeitIst die Query an einer passenden Stelle und mit einem stabilen Vertrag gekapselt?
Schema-NäheBraucht die Anforderung Tabellen, PostgreSQL-Funktionen oder ein exakt sichtbares SQL-Statement?
Geladene DatenWerden nur Felder und Zeilen geladen, die der Endpunkt tatsächlich verwendet?
DatenwachstumGibt es eine Annahme, die bei vielen Tickets kritisch werden könnte?

Der letzte Punkt ist eine Erwartung, noch keine Optimierung. Pagination, Indexe, Query-Pläne und ORM-Probleme vertiefst du in Block 6.

Repository Methods für einfache Filter

Spring Data kann aus einem konventionellen Methodennamen eine parametrisierte Query ableiten. Das ist für einfache, sprechende Filter nützlich VMware, Inc. (2026). Im Block-4-Lösungsstand verwendet das Repository bereits diese Methode:

List<TicketEntity> findByStatusAndDeletedAtIsNullOrderByCreatedAtDesc(String status);

Sie beantwortet eine konkrete Anforderung: «Zeige aktive Tickets eines Status, neueste zuerst.» Der Name enthält Filter, Soft-Delete-Regel und Sortierung. Das ist noch gut lesbar, weil jede Komponente eine offensichtliche Bedeutung hat.

@GetMapping
List<TicketResponse> findTickets(@RequestParam(required = false) String status) {
    return ticketService.findTickets(status);
}

Der Endpunkt GET /api/tickets?status=open verwendet diese Repository Method. Ohne Parameter ruft der Service die noch einfachere Methode findByDeletedAtIsNullOrderByCreatedAtDesc() auf.

Wann der Name zu viel versteckt

Eine hypothetische Methodensignatur wie diese ist ein Warnsignal:

findByStatusAndPriorityAndCreatedAtAfterAndDeletedAtIsNullOrderByPriorityDescCreatedAtDesc(...)

Sie ist nicht falsch. Aber der Methodenname wird zur eigenen kleinen Abfragesprache. Bei Änderungen muss ein Team die Grammatik des Namens statt eine klar formatierte Query lesen. Spätestens bei Projektionen, optionalen Kriterien, Joins oder Aggregationen ist JPQL beziehungsweise native SQL häufig die bessere Erklärung.

JPQL für fachliche Listen

JPQL spricht über Entities und deren Java-Felder, nicht direkt über Tabellen und Spalten. Im Ticket-System heisst die Entity TicketEntity, ihr Feld für den Soft Delete deletedAt und ihr Feld für den Erstellzeitpunkt createdAt. JPQL nutzt genau diese Namen VMware, Inc. (2026).

Die Ticketübersicht benötigt keine externe Referenz, keine Version und keinen Änderungszeitpunkt. Deshalb ist eine ganze TicketEntity als Rückgabewert unnötig breit. Der Block-5-Lösungsstand führt dafür ein eigenes Antwortmodell ein:

@Value
public class TicketListItemResponse {
    Long id;
    String title;
    String status;
    String priority;
    OffsetDateTime createdAt;
}

Die Repository Query erzeugt dieses DTO direkt mit einer Constructor-Projektion:

@Query("""
        SELECT new ch.hftm.db2.ticketsystem.ticket.TicketListItemResponse(
            t.id, t.title, t.status, t.priority, t.createdAt
        )
        FROM TicketEntity t
        WHERE t.deletedAt IS NULL
          AND (:status IS NULL OR t.status = :status)
        ORDER BY t.createdAt DESC
        """)
List<TicketListItemResponse> findActiveTicketOverview(@Param("status") String status);

Wichtig sind drei Aspekte:

Der Service normalisiert einen vorhandenen Status und übergibt sonst null. Damit kann derselbe Endpunkt alle oder nur offene, wartende oder geschlossene Tickets anzeigen:

@GetMapping("/overview")
List<TicketListItemResponse> findTicketOverview(
        @RequestParam(required = false) String status
) {
    return ticketService.findTicketOverview(status);
}

Die resultierende API ist bewusst schlank:

GET /api/tickets/overview?status=open

Eine Projektion ist keine pauschale Performance-Garantie. Sie ist zuerst ein klarer Vertrag: Diese Liste braucht genau diese Felder. Ob und wann das zudem relevant Datenmenge spart, prüfst du im nächsten Block mit konkreten Zugriffsmustern.

Native SQL für Reporting

Ein Support-Team möchte wissen, wie viele aktive Tickets jede Kombination aus Status und Priorität hat und wie viele Kommentare zu diesen Tickets gehören. Diese Anforderung verbindet zwei Tabellen und zwei unterschiedliche Zählungen. Genau hier kann native SQL die klarste Form sein.

SELECT
    t.status AS status,
    t.priority AS priority,
    COUNT(DISTINCT t.id) AS "ticketCount",
    COUNT(c.id) AS "commentCount"
FROM app_starter.tickets t
LEFT JOIN app_starter.ticket_comments c ON c.ticket_id = t.id
WHERE t.deleted_at IS NULL
GROUP BY t.status, t.priority
ORDER BY t.status, t.priority;

Diese Query spricht absichtlich die PostgreSQL-Tabelle und ihre Spaltennamen an. Sie liefert keine Entity, sondern eine Zeile pro Status-Prioritäts-Gruppe.

TeilBedeutung
LEFT JOINTickets ohne Kommentar bleiben im Report sichtbar.
COUNT(DISTINCT t.id)Ein Ticket mit zwei Kommentaren wird trotzdem nur einmal als Ticket gezählt.
COUNT(c.id)Es zählt nur vorhandene Kommentarzeilen; ein Ticket ohne Kommentar erhöht diese Zahl nicht.
GROUP BY t.status, t.priorityJede Kombination aus Status und Priorität wird zu einer Report-Zeile.
WHERE t.deleted_at IS NULLAusgeblendete Tickets verfälschen den aktiven Support-Report nicht.

Die Aliase bilden einen Vertrag zwischen SQL und einer internen Interface-Projektion:

interface TicketStatusPriorityReportProjection {
    String getStatus();
    String getPriority();
    long getTicketCount();
    long getCommentCount();
}

Das Repository führt die Query als nativeQuery = true aus. Der Service übersetzt die interne Projektion anschliessend explizit in TicketStatusPriorityReportResponse. So bleibt die API nicht an die technische Projektion des Repositories gekoppelt.

@GetMapping("/reports/status-priority")
List<TicketStatusPriorityReportResponse> findStatusPriorityReport() {
    return ticketService.findStatusPriorityReport();
}

Native SQL ist besonders passend, wenn die SQL-Struktur selbst wichtig ist: Reporting, datenbankspezifische Funktionen oder eine Abfrage, die über Tabellen und Spalten klarer verständlich wird. Sie koppelt den Code aber stärker an das konkrete Schema. Eine Umbenennung von deleted_at ist hier zum Beispiel nicht durch das Entity-Mapping verborgen.

Soft Deletes gehören in die Anforderung

Block 4 hat festgelegt, dass DELETE /api/tickets/{id} keine Zeile entfernt. Der Service setzt deleted_at. Damit hat jede normale Lesefunktion eine zusätzliche Pflichtfrage:

Soll dieser Use Case nur aktive Tickets sehen oder ausdrücklich auch ausgeblendete?

Für die normale Ticketliste, die Übersicht und den Status-/Prioritätsreport lautet die Antwort: nur aktive Tickets. Deshalb erscheint der Filter in jeder der drei Query-Formen:

Query-FormSchutz vor gelöschten Tickets
Repository MethodAndDeletedAtIsNull im Namen
JPQLWHERE t.deletedAt IS NULL
native SQLWHERE t.deleted_at IS NULL

Ein häufiger Fehler ist, nur den Service zu betrachten. Wenn ein neues Repository eine direkte Query ergänzt, kann es den bisher sicheren Listenpfad umgehen. Soft Delete ist daher ein gutes Beispiel, warum Query Reviews nicht nur nach Syntax fragen, sondern nach fachlichen Filtern.

Query Review: klein, klar und überprüfbar

Nutze diese Checkliste, bevor du eine neue Query akzeptierst:

Typische Fehlentscheide

BeobachtungBessere Frage
«SQL ist immer schneller.»Welche Daten braucht dieser Use Case, und wie messen wir bei Bedarf die tatsächliche Ausführung?
«Eine Entity kann ich immer zurückgeben.»Welche Felder soll diese API wirklich versprechen?
«Ein langer Methodenname spart eine Query.»Macht der Name die Bedingung für das Team wirklich verständlicher?
«COUNT(t.id) reicht.»Vervielfacht der Join Tickets durch Kommentare oder Labels?
«Der Soft-Delete-Filter ist bereits im Service.»Kann diese neue Repository Query den Service-Pfad umgehen?

Transfer: vom Query Design zur Performance

Nach diesem Block kannst du die Query-Form begründen. Das ist die Voraussetzung für die nächste Frage: Wie verhält sie sich mit vielen Daten und Beziehungen? In Block 6 untersuchst du N+1-Probleme, zu breite Entity-Ladungen, Pagination, EXPLAIN und Indexe.

Der wichtigste Merksatz bleibt:

Wähle die kleinste Query-Form, die die fachliche Anforderung klar, vollständig und testbar ausdrückt.

References
  1. VMware, Inc. (2026). Spring Data JPA Reference Documentation. https://docs.spring.io/spring-data/jpa/reference/