CodeGym /Kurse /Spring Data JPA /Basis-Repository-Schnittstelle

Basis-Repository-Schnittstelle

Spring Data JPA
Level 7, Lektion 1
Verfügbar

1. Auswahl der Basis-Repository-Schnittstelle

Wenn Sie gerade erst anfangen, wirkt die Wahl zwischen CrudRepository, PagingAndSortingRepository und JpaRepository schnell wie die Wahl zwischen drei identischen Joghurts, bei denen sich nur die Schrift auf der Verpackung unterscheidet. Der Code wird in vielen Fällen tatsächlich funktionieren. Aber es gibt einen Haken: Die Repository-Schnittstelle ist Ihr öffentlicher Vertrag für den Datenzugriff, und wenn Sie sie zufällig wählen, wird das Projekt schnell „uneben“ und schwer lesbar.

Stellen Sie sich vor, das Repository sei eine Fernbedienung für Ihren Speicher. Die eine Fernbedienung hat nur Tasten für „Ein/Aus“, eine andere hat „Lauter/Leiser“ dazu, und eine dritte hat zusätzlich noch eine richtige Quellenwahl, Modi und einen Timer. Man kann auch mit der einfachen Fernbedienung leben, aber wenn Sie in einem Raum drei verschiedene Fernbedienungen haben und jedes Mal vergessen, welche zu welchem Gerät gehört, dann wird der Abend nicht mehr gemütlich.

In Spring Data ist die Geschichte ähnlich: Unterschiedliche Basis-Schnittstellen bieten unterschiedliche APIs, und es ist wichtig, diejenige zu wählen, die zur einheitlichen Baseline in Ihrem Projekt wird.

2. Die Spring-Data-Landkarte

In diesem Thema ist es nützlich, einen kleinen Schritt zurückzutreten und zu erkennen, dass Spring Data nicht nur JPA ist. Spring Data ist eine Familie von Projekten, die Repositories für verschiedene Speicher erzeugen kann: SQL (über JPA), MongoDB, Redis und andere. Daher gibt es bei Spring Data einen „gemeinsamen Teil“, der ein abstraktes Repository beschreibt, und spezifische Erweiterungen für bestimmte Technologien, z. B. für JPA.

Daher kann man die Basis-Schnittstellen als Schichten betrachten. Ganz unten der minimale CRUD-Vertrag, darüber die Fähigkeit zu sortieren und Daten in Portionen auszugeben, noch darüber die JPA-orientierten Annehmlichkeiten. Auch wenn es in den Bibliotheken zusätzliche Zwischenschnittstellen und Versionsnuancen gibt, ist für den praktisch arbeitenden Ingenieur der Gedanke wichtiger: Je „reicher“ die Basis-Schnittstelle, desto mehr stabile Möglichkeiten hat das Repository.

Ein anschauliches Lehrschema (eher als Schichten-Schema, nicht als Versprechen einer exakten Vererbungshierarchie aller Versionen) sieht so aus:

flowchart TD
    A["CrudRepository
minimaler CRUD-Vertrag"] --> B["PagingAndSortingRepository
+ Sortierung und Portionsausgabe"] B --> C["JpaRepository
+ JPA-orientierte Bequemlichkeit"]

In unserem Projekt shop-data-jpa bauen wir bewusst ein JPA-Projekt auf PostgreSQL auf, daher ist es logisch, diese Schnittstellen durch die Brille zu betrachten: Welche macht das Repository am bequemsten und vorhersagbarsten speziell für JPA-Entitäten.

3. CrudRepository: Minimaler Vertrag „kann speichern und löschen“

CrudRepository ist der „basischste“ Repository-Vertrag in Spring Data. Man kann ihn sinnvoll als „notwendiges Minimum“ betrachten, das für die vier aus CRUD bekannten Aktionen zuständig ist: Create, Read, Update, Delete. Es ist wichtig, nicht sofort die gesamte Methodenliste auswendig lernen zu wollen, sondern die Idee zu erfassen: CrudRepository – das ist ein Repository, das grundlegende Operationen garantiert, aber kein JPA-Komfort verspricht.

Im Folgenden ein Beispiel für ein Repository für Category, aber auf der minimalsten Schnittstelle. Das ist nicht das, was wir als Baseline für den Kurs wählen werden, aber es ist eine gute Möglichkeit, die „untere Schicht“ der Landkarte zu sehen.

package com.example.shopdatajpa.catalog.repository;

import com.example.shopdatajpa.catalog.entity.Category;
import org.springframework.data.repository.CrudRepository;

// Repository-Vertrag für die Entität Category auf der minimalsten Spring-Data-Ebene.
public interface CategoryCrudRepository extends CrudRepository<Category, Long> {

    // Category — Entitätstyp, Long — Primärschlüsseltyp (ID).
    // Hier sind die grundlegenden CRUD-Operationen verfügbar (save/findById/delete/existsById usw.).
}

Wenn Sie die Dokumentation oder die IDE-Autovervollständigung öffnen, sehen Sie Methoden wie save(...), findById(...), existsById(...), findAll(), count(), delete(...). Für ein Lehrprojekt und für Anfänger sieht das schon wie „Wow, ich habe kein SQL geschrieben und es funktioniert“ aus. Und ja, das ist ein Wow – aber ohne magisches Denken: Dahinter stehen JPA und Hibernate, die das SQL generieren.

Es gibt eine praktische Feinheit, die vielen nicht sofort auffällt: Im Basisvertrag taucht oft Iterable<T> auf, nicht List<T>. Das ist kein Fehler, sondern einfach die „Minimalität“. Im Anwendungscode ist List jedoch meist bequemer, weil man die Größe wissen, Elemente indizieren, ordentlich streamen und einfach keine zusätzlichen Konvertierungen schreiben möchte.

4. PagingAndSortingRepository: CRUD + Listenlesen

PagingAndSortingRepository ist die nächste Schicht über dem grundlegenden CRUD. Es wird bemerkbar, wenn das Projekt kein Spielzeug mit drei Datensätzen mehr ist und Sie anfangen, so zu denken, wie ein Backend-Entwickler in der Realität denkt: Daten müssen nicht vollständig, sondern in Portionen und meist in einer bestimmten Reihenfolge gelesen werden. Auch wenn Sie heute keinen vollwertigen Katalog mit Filtern bauen, ist allein die Tatsache, dass „Sortierung und Portionsausgabe Teil des Repository-Vertrags sind“, sehr wichtig.

Hier kommen Methoden hinzu, die Sort und Pageable entgegennehmen. Wir werden diese Typen und ihr Verhalten jetzt nicht im Detail besprechen, es ist wichtig zu sehen: Diese Schnittstelle erweitert die Repository-Möglichkeiten in Richtung „Listenlesen“.

package com.example.shopdatajpa.catalog.repository;

import com.example.shopdatajpa.catalog.entity.Category;
import org.springframework.data.repository.PagingAndSortingRepository;

// Repository derselben Domäne, aber mit erweitertem Lese-Vertrag:
// Daten können mit Sortierung und Paginierung abgerufen werden.
public interface CategoryPagingRepository
        extends PagingAndSortingRepository<Category, Long> {

    // Wichtiger Unterschied zu CrudRepository:
    // Es gibt Methoden wie findAll(Sort) und findAll(Pageable),
    // um Daten geordnet und in Portionen auszugeben.
}

Warum ist das überhaupt wichtig zu wissen, wenn wir noch nicht in die Paginierung eintauchen? Weil viele Anfänger CrudRepository wählen und sich dann aufrichtig wundern, dass es „irgendwie keine Methoden für Sortierung und Seiten gibt“. Die andere Extrem ist, im Service „selbstgebaute“ Sortiermethoden hinzuzufügen, obwohl dies genau die Aufgabe ist, die Spring Data bereits auf Vertragsebene löst.

Im Sinne des gesunden Menschenverstandes: PagingAndSortingRepository ist immer noch eine allgemeine Spring-Data-Schnittstelle, nicht JPA-spezifisch, aber sie sagt bereits: „Das Repository kann nicht nur speichern, sondern auch Daten geordnet und in Portionen ausgeben“.

5. JpaRepository: Baseline für ein JPA-Projekt

JpaRepository ist eine Schnittstelle, die speziell auf die JPA-Welt zugeschnitten ist. Ihr Sinn liegt nicht darin, dass sie „die coolste“ ist, sondern darin, dass sie sich maximal natürlich in ein typisches Spring Boot + JPA-Projekt einfügt. Sie enthält grundlegendes CRUD, die Möglichkeiten zur Sortierung und zum Listenlesen, und fügt zusätzlich JPA-orientierte Annehmlichkeiten hinzu. Für ein Lehrprojekt ist das auch ein wichtiger methodischer Vorteil: weniger „Unebenheiten“ und weniger unerwartete Einschränkungen in der API.

Im Projekt shop-data-jpa möchten wir, dass die Repositories in allen Feature-Paketen gleich aussehen: in catalog, später in inventory, dann in ordering. Daher wird die Baseline so sein:

package com.example.shopdatajpa.catalog.repository;

import com.example.shopdatajpa.catalog.entity.Category;
import org.springframework.data.jpa.repository.JpaRepository;

// Gleichmäßige Baseline für ein JPA-Projekt:
// Enthält bereits CRUD + Paging/Sorting + JPA-orientierte Methoden.
public interface CategoryRepository extends JpaRepository<Category, Long> {

    // Hier gibt es standardmäßig mehr Annehmlichkeiten als bei CrudRepository:
    // Häufig werden Listen zurückgegeben, es gibt JPA-spezifische Methoden wie getReferenceById(...).
}

Und hier kommt ein sehr „praktischer“ Grund, warum JpaRepository im Anwendungscode bequemer ist: Viele Methoden geben List<T> zurück, nicht nur Iterable. Wenn Sie Servic-Code schreiben, reduziert das spürbar die Menge an kleinem „Rauschen“, das Sie nicht JPA lehrt, sondern Geduld.

Zum Beispiel verwenden Sie die Methode findAll() bei JpaRepository meist als Liste und nicht als abstrakten Iterator:

import com.example.shopdatajpa.catalog.entity.Category;
import com.example.shopdatajpa.catalog.repository.CategoryRepository;
import java.util.List;

public class CatalogAdminReadService {

    private final CategoryRepository categoryRepository;

    public CatalogAdminReadService(CategoryRepository categoryRepository) {
        // Abhängigkeitsinjektion: Der Service hängt vom Repository als Datenzugriffsvertrag ab
        this.categoryRepository = categoryRepository;
    }

    public List<Category> loadAllCategories() {
        // Wichtig: Bei JpaRepository gibt findAll() eine List zurück, kein Iterable.
        // Im Anwendungscode ist das normalerweise einfacher und besser lesbar.
        return categoryRepository.findAll();
    }
}

Gesondert erwähnt: JpaRepository bietet JPA-orientierte Methoden, die Sie im allgemeinen Vertrag nicht haben müssen. Zum Beispiel die Methode getReferenceById(...) (wir werden ihren Sinn separat besprechen, hier ist uns nur die Tatsache ihres Vorhandenseins wichtig) – das ist Teil der JPA-orientierten API, und genau deshalb sehen Sie in Vorlesungen und Beispielen zu JPA öfter JpaRepository als CrudRepository.

6. Auswahlprinzip: Baseline standardisieren

Wenn Sie die Basis-Schnittstelle wählen, wählen Sie nicht nur eine Menge von Methoden, sondern auch den Stil des Projekts. In kleinem Code scheint der Unterschied kosmetisch, aber in einem echten Projekt wird es viele Repositories geben, viele Services, und das Gehirn braucht einen klaren, verständlichen Standard. In unserem Lehrprojekt ist der Standard besonders wichtig: Wir bauen „beispielhaften“ Code, bei dem der Student weniger raten und mehr verstehen soll.

Es ist hilfreich, die Wahl anhand einer einfachen Vergleichstabelle zu betrachten. Das ist keine „Auswendiglern-Spickzettel“, sondern eine Karte, um nicht umherzuirren.

Schnittstelle Idee Bietet „obendrauf“ Wann sinnvoll Warum im Kurs gewählt/nicht gewählt
CrudRepository Minimales CRUD Basisoperationen und Iterable-orientierte API Wenn ein Ultra-Minimum nötig ist oder Sie Code für verschiedene Speicher ohne JPA-Spezifik schreiben Als Kurs-Baseline unbequem: viele kleine Unannehmlichkeiten und weniger JPA-orientierte API
PagingAndSorting Repository CRUD + Listenlesen Fügt Sortierung und Portionsausgabe hinzu Wenn Sie genau diese Schicht möchten und keine JPA-Spezifik wünschen In einem JPA-Projekt macht es meist keinen Sinn, die „Mitte“ zu nehmen, wenn man sowieso im JPA-Kontext lebt
JpaRepository Bequeme JPA-Baseline Bequemere Collections und JPA-API Typisches Spring Boot + Spring Data JPA-Projekt Unser praktischer Default: gleichmäßiger Vertrag, weniger Überraschungen, leichter lesbarer Code

Die wichtigste methodische Regel in dieser Phase: In einem JPA-Lehrprojekt ist es fast immer vorteilhafter, JpaRepository als einheitliche Baseline zu wählen. Das macht den Code vorhersagbar: Sie öffnen ein beliebiges Repository und sehen dieselbe „Fernbedienung“.

7. Schnittstellen nicht ohne Grund mischen

Wenn in einem Feature-Block ein Repository auf CrudRepository und ein anderes auf JpaRepository basiert, „bricht“ das Projekt nicht. Es wird nur unbequem. Das ist ein subtiler, aber wichtiger Unterschied: Der Code wird kompilieren, aber das Lesen wird unangenehm, und das Lernen daran noch unangenehmer. Besonders leidet die Service-Schicht, denn der Service ist der Ort, an dem mehrere Repositories zusammenkommen.

Angenommen, Sie machen Folgendes: CategoryRepository auf der minimalen Schnittstelle, ProductRepository auf JpaRepository. Im Service bekommen Sie plötzlich unterschiedliche API-Stile. Mal wird Iterable zurückgegeben, mal List, mal stehen Ihnen bequeme JPA-Methoden zur Verfügung, mal nicht. Und der Service beginnt, mit „Verkleidungen“ zugewuchert zu werden, die nichts mit der Geschäftslogik zu tun haben.

Unten ein kleines Beispiel für „Rauschen“, das entsteht, wenn Ihnen unerwartet ein Iterable gegeben wird, Sie aber eine normale Liste möchten. Das ist keine Katastrophe, aber es ist nicht das, worauf wir in einem Data-Kurs unsere Aufmerksamkeit richten wollen.

import com.example.shopdatajpa.catalog.entity.Category;
import com.example.shopdatajpa.catalog.repository.CategoryCrudRepository;
import java.util.ArrayList;
import java.util.List;

public class CategoryReadService {

    private final CategoryCrudRepository categoryRepository;

    public CategoryReadService(CategoryCrudRepository categoryRepository) {
        // Der Service hängt von einem engeren Vertrag (CrudRepository) ab,
        // daher wird es im Code mehr Handarbeit geben.
        this.categoryRepository = categoryRepository;
    }

    public List<Category> loadAsList() {
        // Wir wollen eine normale Liste, aber findAll() gibt Iterable zurück,
        // also müssen wir die Elemente manuell in eine List packen.
        List<Category> result = new ArrayList<>();
        categoryRepository.findAll().forEach(result::add); // Zusätzlicher Aufwand wegen zu enger API
        return result;
    }
}

Kann man so leben? Ja. Muss man so in einem Projekt leben, in dem unser Ziel ist, JPA zu verstehen, und nicht „wie man Iterable in List umwandelt“? Normalerweise nicht. Daher wählen wir im Rahmen von shop-data-jpa eine gleichmäßige Baseline und zwingen den Servic-Code nicht dazu, Dinge zu tun, die nicht zum Use Case gehören.

8. Lösung für shop-data-jpa

Wenn Sie Repositories im Projekt nach dem Prinzip „package-by-feature“ erstellen, ergeben sich natürliche Orte für Datenzugriffsverträge. Für den Katalog ist das com.example.shopdatajpa.catalog.repository, und dort liegen CategoryRepository und ProductRepository. Und genau hier ist es wichtig, den Code so „langweilig“ wie möglich zu machen – im positiven Sinne: gleiche Basis, gleicher Stil, minimale Überraschungen.

So sollten unsere Repositories in der aktuellen Phase aussehen: kurz, lesbar, und sie versuchen nicht, eine „architektonische Heldentat“ darzustellen.

package com.example.shopdatajpa.catalog.repository;

import com.example.shopdatajpa.catalog.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;

// Katalog-Repository auf dem Baseline-Vertrag JpaRepository:
// In dieser Phase reicht uns der Standardsatz an Methoden.
public interface ProductRepository extends JpaRepository<Product, Long> {

    // Product — Entität, Long — ID-Typ.
    // Benutzerdefinierte Abfragen und Methoden fügen wir nur hinzu, wenn ein echter Bedarf entsteht.
}

Beachten Sie ein wichtiges Detail: Wir fügen hier keine „Service“-Methoden wie createCategoryAndRenameProduct(...) hinzu. Ein Repository ist ein Vertrag für den Datenzugriff eines Modellteils. Je sauberer dieser Vertrag, desto einfacher wird es sein, einen ordentlichen CatalogService zu schreiben, in dem dann die Szenarioschritte und Prüfungen leben.

9. Typische Fehler bei der Auswahl der Repository-Schnittstelle

Fehler Nr. 1: Die Schnittstelle nach dem „bekannten Namen“ wählen, nicht nach der Rolle.
Eine sehr häufige Geschichte: Ein Anfänger hat irgendwo CrudRepository gesehen und setzt es daher überall als „Standard“ ein. Oder umgekehrt – er hat das Wort JpaRepository gehört und setzt es in einem Projekt ein, das überhaupt nichts mit JPA zu tun hat. Der richtige Gedanke ist: Zuerst verstehen Sie, in welcher Welt Sie sich befinden (bei uns JPA + PostgreSQL), und dann wählen Sie den Basisvertrag für diese Welt.

Fehler Nr. 2: CrudRepository und JpaRepository im selben Projekt ohne Grund mischen.
Das Mischen erzeugt eine „uneinheitliche“ API. An einer Stelle bekommen Sie List, an einer anderen Iterable, an einer Stelle stehen JPA-orientierte Methoden zur Verfügung, an einer anderen nicht. Die Service-Schicht beginnt dann, die Inkonsistenz der Repositories auszugleichen, und der Code verliert seine didaktische Transparenz. Viel gesünder ist es, eine Baseline zu wählen und diese stabil zu halten.

Fehler Nr. 3: Versuchen, „alle Methoden aller Schnittstellen“ auf einmal zu „lernen“.
Das ist eine Perfektionismusfalle. Ein Repository hat viele Methoden, und die meisten davon werden Sie nicht jeden Tag verwenden. In dieser Phase ist es wichtiger, die Karte lesen zu lernen: „minimales CRUD“, „Sortierung/Portionsausgabe hinzugefügt“, „JPA-orientierte Bequemlichkeit“. Die Details der konkreten Methoden besprechen wir nach Bedarf, wenn sie im Servic-Code verwendet werden.

Fehler Nr. 4: JPA-spezifische Möglichkeiten von einer zu basalen Schnittstelle erwarten.
Manchmal wählt ein Student CrudRepository und möchte dann eine JPA-Methode aufrufen, die dort nicht existiert, und fängt an zu denken, dass „Spring Data kaputt ist“. Spring Data ist nicht kaputt – Sie haben einfach einen anderen Vertrag gewählt. Wenn das Projekt ein JPA-Projekt ist, nimmt JpaRepository diesen Schmerz normalerweise, weil es einen „heimischeren“ Satz an Fähigkeiten für JPA enthält.

Fehler Nr. 5: Das Repository zum „Hauptservice der Anwendung“ machen.
Die Wahl der Basis-Schnittstelle hat damit nichts zu tun, aber der Fehler tritt in der Nähe auf: Da das Repository mächtig aussieht, möchte man die ganze Logik dort hineinpacken. Das führt zu einem „Giant Interface“, in dem Use-Case-Methoden auftauchen. Das Repository sollte ein Vertrag für den Datenzugriff bleiben, und die Szenarien und Geschäftsschritte sollten in der Service-Schicht leben. Und je gleichmäßiger und verständlicher die Repository-Baseline ist, desto einfacher ist es, diese Grenze einzuhalten.

1
Aufgabe
Spring Data JPA, Level 7, Lektion 1
Gesperrt
Übersicht der grundlegenden Repository-Interfaces für `Category`
Übersicht der grundlegenden Repository-Interfaces für `Category`
1
Aufgabe
Spring Data JPA, Level 7, Lektion 1
Gesperrt
`JpaRepository` als Baseline für `Product`
`JpaRepository` als Baseline für `Product`
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION