CodeGym /Kursy /Spring Data JPA /Bazowy interfejs repozytorium

Bazowy interfejs repozytorium

Spring Data JPA
Poziom 7, Lekcja 1
Dostępny

1. Wybór bazowego interfejsu repozytorium

Jeśli dopiero zaczynasz, wybór między CrudRepository, PagingAndSortingRepository a JpaRepository łatwo wygląda jak wybór między trzema identycznymi jogurtami, gdzie różni się tylko czcionka na opakowaniu. Kod będzie przecież działał w wielu przypadkach. Ale jest tu haczyk: interfejs repozytorium to twój publiczny kontrakt dostępu do danych, i jeśli wybierzesz go przypadkowo, projekt szybko stanie się „nierówny" i niewygodny do czytania.

Wyobraź sobie, że repozytorium to pilot do twojego magazynu danych. Jeden pilot ma tylko przyciski „wł./wył.", drugi dołożył „głośniej/ciszej", a trzeci — jeszcze normalny wybór źródła, tryby, timer. Można żyć z prostym pilotem, ale jeśli masz w jednym pokoju trzy różne piloty i ciągle zapominasz, który od czego — wieczór przestaje być przyjemny.

W Spring Data historia jest podobna: różne bazowe interfejsy dają różny zakres API, i ważne jest wybranie tego, który stanie się jednolitym baseline'em w twoim projekcie.

2. Mapa Spring Data

W tym temacie przydaje się zrobić mały krok wstecz i zobaczyć, że Spring Data to nie tylko JPA. Spring Data to rodzina projektów, które umieją budować repozytoria dla różnych magazynów: SQL (przez JPA), MongoDB, Redis i innych. Dlatego Spring Data ma „wspólną część", która opisuje abstrakcyjne repozytorium, oraz konkretne rozszerzenia pod konkretne technologie, np. pod JPA.

Bazowe interfejsy można więc postrzegać jak warstwy. Na dole — minimalny kontrakt CRUD, wyżej — umiejętność sortowania i oddawania danych porcjami, jeszcze wyżej — wygody zorientowane na JPA. Nawet jeśli w bibliotekach są dodatkowe pośrednie interfejsy i niuanse wersji, dla inżyniera-praktyka ważniejsza jest myśl: im „bogatszy" bazowy interfejs, tym więcej stabilnych możliwości ma repozytorium.

Wygodny schemat edukacyjny (właśnie jako schemat „warstw", a nie jako obietnica dokładnej hierarchii dziedziczenia wszystkich wersji) wygląda tak:

flowchart TD
    A["CrudRepository
minimalny kontrakt CRUD"] --> B["PagingAndSortingRepository
+ sortowanie i zwracanie porcjami"] B --> C["JpaRepository
+ wygody zorientowane na JPA"]

W naszym projekcie shop-data-jpa świadomie budujemy projekt JPA na PostgreSQL, więc logicznie jest patrzeć na te interfejsy przez pryzmat: który z nich sprawia, że repozytorium jest najbardziej wygodne i przewidywalne właśnie dla encji JPA.

3. CrudRepository: minimalny kontrakt „umiem przechowywać i usuwać"

CrudRepository to najbardziej „bazowy" kontrakt repozytoryjny w Spring Data. Wygodnie traktować go jako „niezbędne minimum", które odpowiada za cztery działania znane każdemu z CRUD: utwórz, przeczytaj, zaktualizuj, usuń. Tu ważne jest, żeby nie starać się zapamiętać całej listy metod od razu, lecz uchwycić ideę: CrudRepository to repozytorium, które gwarantuje, że masz operacje bazowe, ale nie obiecuje żadnego komfortu JPA.

Poniżej — przykład repozytorium dla Category, ale na minimalnym interfejsie. To nie to, co wybierzemy jako baseline kursu, ale to dobry sposób, żeby zobaczyć „dolną warstwę" mapy.

package com.example.shopdatajpa.catalog.repository;

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

// Kontrakt repozytorium dla encji Category na najbardziej minimalnym poziomie Spring Data.
public interface CategoryCrudRepository extends CrudRepository<Category, Long> {

    // Category — typ encji, Long — typ klucza głównego (ID).
    // Dostępne są tu bazowe operacje CRUD (save/findById/delete/existsById itp.).
}

Jeśli otworzysz dokumentację lub autouzupełnianie IDE, zobaczysz metody takie jak save(...), findById(...), existsById(...), findAll(), count(), delete(...). Dla projektu edukacyjnego i dla początkującego to już wygląda jak „wow, nie napisałem SQL, a to działa". I tak, to wow — ale bez magicznego myślenia: za tym stoi JPA i Hibernate, które generują SQL.

Jest jedna praktyczna subtelność, którą wiele osób zauważa dopiero po czasie: w bazowym kontrakcie często pojawia się Iterable<T>, a nie List<T>. To nie błąd, to po prostu „minimalność". Ale w kodzie aplikacyjnym List jest zazwyczaj wygodniejszy, bo chcesz znać rozmiar, indeksować elementy, sensownie streamować i po prostu nie pisać zbędnych konwersji.

4. PagingAndSortingRepository: CRUD + czytanie list

PagingAndSortingRepository to następna warstwa nad bazowym CRUD. Staje się zauważalny, gdy projekt przestaje być zabawką z trzema rekordami i zaczynasz myśleć tak, jak myśli backend developer w rzeczywistości: dane trzeba czytać nie w całości, a porcjami, i zazwyczaj w pewnym porządku. Nawet jeśli dziś nie budujesz pełnego katalogu z filtrami, sam fakt „sortowanie i zwracanie porcjami — część kontraktu repozytorium" jest bardzo ważny.

Pojawiają się tu metody przyjmujące Sort i Pageable. Nie będziemy teraz szczegółowo omawiać tych typów i ich zachowania, ważne jest żebyśmy zobaczyli: ten interfejs rozszerza możliwości repozytorium w kierunku „czytanie list".

package com.example.shopdatajpa.catalog.repository;

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

// Repozytorium tej samej domeny, ale z rozszerzonym kontraktem czytania list:
// można pobierać dane z sortowaniem i paginacją.
public interface CategoryPagingRepository
        extends PagingAndSortingRepository<Category, Long> {

    // Ważna różnica od CrudRepository:
    // pojawiają się metody takie jak findAll(Sort) i findAll(Pageable),
    // żeby oddawać dane schludnie (porządek + porcje).
}

Dlaczego w ogóle warto to wiedzieć, skoro nie zagłębiamy się jeszcze w paging? Bo wielu nowicjuszy wybiera CrudRepository, a potem szczerze się dziwi, że „jakoś nie ma metod do sortowania i stron". A druga skrajność — próba dodawania „domowych" metod sortowania w serwisie, choć to dokładnie zadanie, które Spring Data już rozwiązuje na poziomie kontraktu.

W kategoriach zdrowego rozsądku: PagingAndSortingRepository to wciąż ogólny interfejs Spring Data, nie specyficzny dla JPA, ale już mówi: „repozytorium umie nie tylko przechowywać, ale i oddawać dane schludnie".

5. JpaRepository: baseline dla projektu JPA

JpaRepository to interfejs stworzony specjalnie dla świata JPA. Jego sens nie polega na tym, że jest „najfajniejszy", a na tym, że jak najbardziej naturalnie pasuje do typowego projektu Spring Boot + JPA. Zawiera bazowy CRUD, możliwości sortowania i czytania list, plus dodaje wygody zorientowane na JPA. Dla projektu edukacyjnego to też ważna zaleta metodyczna: mniej „nierówności" i mniej nieoczekiwanych ograniczeń w API.

W projekcie shop-data-jpa chcemy, żeby repozytoria wyglądały identycznie we wszystkich paczkach feature-owych: w catalog, później w inventory, następnie w ordering. Dlatego baseline będzie wyglądał tak:

package com.example.shopdatajpa.catalog.repository;

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

// Równy baseline dla projektu JPA:
// wewnątrz jest już CRUD + paging/sorting + metody zorientowane na JPA.
public interface CategoryRepository extends JpaRepository<Category, Long> {

    // Tutaj domyślnie więcej wygód niż w CrudRepository:
    // często zwracane są List, są metody specyficzne dla JPA jak getReferenceById(...).
}

I tutaj pojawia się bardzo „przyziemiony" powód, dla którego JpaRepository jest wygodniejszy w kodzie aplikacyjnym: wiele metod zwraca List<T>, a nie tylko Iterable. Kiedy piszesz kod serwisu, to zauważalnie zmniejsza ilość drobnego „szumu", który nie uczy cię JPA, a uczy cię cierpliwości.

Na przykład metody findAll() z JpaRepository najczęściej używasz jako listy, a nie jako abstrakcyjnego iteratora:

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) {
        // Wstrzykiwanie zależności: serwis zależy od repozytorium jako kontraktu dostępu do danych
        this.categoryRepository = categoryRepository;
    }

    public List<Category> loadAllCategories() {
        // Ważne: w JpaRepository findAll() zwraca List, a nie Iterable.
        // W kodzie aplikacyjnym jest to zazwyczaj prostsze i bardziej czytelne.
        return categoryRepository.findAll();
    }
}

Warto odnotować oddzielnie: JpaRepository daje metody zorientowane na JPA, których w ogólnym kontrakcie nie musisz mieć. Na przykład metoda getReferenceById(...) (będziemy mówić o jej sensie osobno, tu ważny jest tylko fakt jej obecności) — to część API zorientowanego na JPA, i właśnie dlatego w lekturach i przykładach dotyczących JPA częściej widzisz JpaRepository niż CrudRepository.

6. Zasada wyboru: standaryzować baseline

Gdy wybierasz bazowy interfejs, wybierasz tak naprawdę nie tylko zestaw metod, ale styl projektu. W małym kodzie różnica wydaje się kosmetyczna, ale w prawdziwym projekcie repozytoriów będzie dużo, serwisów będzie dużo, i mózg potrzebuje jednego zrozumiałego standardu. W naszym projekcie edukacyjnym standard jest szczególnie ważny: budujemy kod „przykładowy", gdzie student powinien mniej zgadywać, a więcej rozumieć.

Przydatne jest spojrzenie na wybór przez prostą tabelę porównawczą. To nie „ściągawka do zapamiętania", a mapa, żeby się nie zgubić.

Interfejs Idea Co daje „ponad" Kiedy wygląda stosownie Dlaczego w kursie wybieramy/nie wybieramy
CrudRepository minimalny CRUD operacje bazowe i API zorientowane na Iterable gdy potrzebujesz ultra-minimum lub piszesz kod pod różne magazyny bez specyfiki JPA jako baseline kursu jest niewygodny: dużo drobnych niedogodności i mniej API zorientowanego na JPA
PagingAndSorting Repository CRUD + czytanie list dodaje sortowanie i zwracanie porcjami gdy chcesz dokładnie tej warstwy i nie chcesz specyfiki JPA w projekcie JPA zazwyczaj nie ma sensu brać „środka", jeśli i tak żyjemy w JPA
JpaRepository wygodny baseline JPA wygodniejsze kolekcje i API JPA typowy projekt Spring Boot + Spring Data JPA nasz praktyczny default: równy kontrakt, mniej niespodzianek, łatwiejszy do czytania kod

Najważniejsza zasada metodyczna na tym etapie: w edukacyjnym projekcie JPA prawie zawsze opłaca się wybrać JpaRepository jako jedyny baseline. To sprawia, że kod jest przewidywalny: otwierasz dowolne repozytorium i widzisz ten sam „pilot".

7. Nie mieszaj interfejsów bez powodu

Jeśli w jednym bloku feature-owym jedno repozytorium masz na CrudRepository, a drugie na JpaRepository, projekt „nie wybuchnie". Po prostu stanie się niewygodny. To subtelna, ale ważna różnica: kod będzie się kompilował, ale czytanie go stanie się nieprzyjemne, a uczenie się na nim — jeszcze nieprzyjemniejsze. Szczególnie cierpi warstwa serwisów, bo to miejsce, gdzie spotyka się kilka repozytoriów.

Na przykład zrobiłeś tak: CategoryRepository — na minimalnym interfejsie, a ProductRepository — na JpaRepository. W serwisie nagle dostajesz różne style API. Gdzieś zwracany jest Iterable, gdzieś List, gdzieś masz dostęp do wygodnych metod JPA, a gdzieś nie. I serwis zaczyna obrastać „owijkami", które nie mają nic wspólnego z logiką biznesową.

Poniżej mały przykład „szumu", który pojawia się, gdy niespodziewanie dostajesz Iterable, a chcesz zwykłej listy. To nie katastrofa, ale to też nie to, czemu chcemy poświęcać uwagę w kursie o danych.

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) {
        // Serwis zależy od węższego kontraktu (CrudRepository),
        // więc dalej w kodzie będzie więcej „ręcznej" roboty.
        this.categoryRepository = categoryRepository;
    }

    public List<Category> loadAsList() {
        // Chcemy zwykłej listy, ale findAll() zwraca Iterable,
        // więc trzeba ręcznie przenosić elementy do List.
        List<Category> result = new ArrayList<>();
        categoryRepository.findAll().forEach(result::add); // Zbędna robota przez zbyt wąskie API
        return result;
    }
}

Czy można tak żyć? Można. Czy trzeba tak żyć w projekcie, gdzie celem jest zrozumienie JPA, a nie „jak zamienić Iterable w List"? Zazwyczaj nie. Dlatego w ramach shop-data-jpa wybieramy równy baseline i nie zmuszamy kodu serwisu do zajmowania się tym, co nie należy do use case'u.

8. Rozwiązanie dla shop-data-jpa

Gdy tworzysz repozytoria w projekcie według zasady package-by-feature, pojawiają się naturalne miejsca na kontrakty dostępu do danych. Dla katalogu to com.example.shopdatajpa.catalog.repository, i tam mieszkają CategoryRepository i ProductRepository. I właśnie tu ważne jest, żeby kod był jak najbardziej „nudny" — w dobrym sensie: jednakowa baza, jednakowy styl, minimum niespodzianek.

Tak powinny wyglądać nasze repozytoria na tym etapie: są krótkie, czytelne i nie próbują odgrywać „architektonicznego wyczynu".

package com.example.shopdatajpa.catalog.repository;

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

// Repozytorium katalogu na kontrakcie baseline JpaRepository:
// na tym etapie wystarczy nam standardowy zestaw metod.
public interface ProductRepository extends JpaRepository<Product, Long> {

    // Product — encja, Long — typ ID.
    // Własne zapytania i metody dodajemy dopiero gdy pojawia się realna potrzeba.
}

Zwróć uwagę na ważny detal: nie dodajemy tu „serwisowych" metod w stylu createCategoryAndRenameProduct(...). Repozytorium to kontrakt dostępu do danych jednej części modelu. Im czystszy ten kontrakt, tym łatwiej będzie napisać normalny CatalogService, gdzie będą mieszkały kroki scenariusza i walidacje.

9. Typowe błędy przy wyborze interfejsu repozytorium

Błąd nr 1: wybierać interfejs „po znajomej nazwie", a nie po roli.
Bardzo częsta historia: nowicjusz gdzieś widział CrudRepository, więc wszędzie stawia go „jako standard". Albo odwrotnie — słyszał słowo JpaRepository i stawia go w projekcie, który w ogóle nie jest o JPA. Prawidłowe podejście jest takie: najpierw rozumiesz, w jakim jesteś świecie (u nas JPA + PostgreSQL), a dopiero potem wybierasz bazowy kontrakt pod ten świat.

Błąd nr 2: mieszanie CrudRepository i JpaRepository w jednym projekcie bez powodu.
Mieszanie tworzy „nierówne" API. W jednym miejscu zwracany jest List, w innym Iterable, w jednym miejscu dostępne są metody zorientowane na JPA, w innym nie. W rezultacie warstwa serwisów zaczyna kompensować niespójność repozytoriów, i kod traci edukacyjną przejrzystość. Znacznie zdrowiej jest wybrać jeden baseline i utrzymywać go stabilnie.

Błąd nr 3: próba „nauczenia się wszystkich metod wszystkich interfejsów" od razu.
To pułapka perfekcjonizmu. Repozytorium ma wiele metod, a większość z nich nie będziesz używać codziennie. Na tym etapie ważniejsze jest nauczenie się czytania mapy: „minimalny CRUD", „dodane sortowanie/porcjowanie", „wygoda zorientowana na JPA". Szczegóły konkretnych metod omawiamy z sensem, gdy zaczynają być używane w kodzie serwisu.

Błąd nr 4: oczekiwanie możliwości specyficznych dla JPA od zbyt bazowego interfejsu.
Czasem student wybiera CrudRepository, a potem chce wywołać metodę JPA, której tam nie ma, i zaczyna myśleć, że „Spring Data się posypał". Spring Data się nie posypał — po prostu wybrałeś inny kontrakt. Jeśli projekt jest projektem JPA, JpaRepository zazwyczaj usuwa ten ból, bo zawiera bardziej „natywny" dla JPA zestaw możliwości.

Błąd nr 5: zamienianie repozytorium w „główny serwis aplikacji".
Wybór bazowego interfejsu nie ma tu znaczenia, ale błąd pojawia się w okolicach: skoro repozytorium wygląda potężnie, chce się tam wpchać całą logikę. Prowadzi to do giant interface, gdzie pojawiają się metody na poziomie use case'u. Repozytorium powinno pozostać kontraktem dostępu do danych, a scenariusze i kroki biznesowe powinny mieszkać w warstwie service. Im równiejszy i bardziej zrozumiały baseline repozytoryjny, tym łatwiej utrzymać tę granicę.

1
Zadanie
Spring Data JPA, poziom 7, lekcja 1
Niedostępne
Mapa podstawowych interfejsow repozytorium dla `Category`
Mapa podstawowych interfejsow repozytorium dla `Category`
1
Zadanie
Spring Data JPA, poziom 7, lekcja 1
Niedostępne
`JpaRepository` jako baseline dla `Product`
`JpaRepository` jako baseline dla `Product`
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION