Spring Data JPA

Spring Data JPA oferuje zaawansowane narzędzia do zarządzania repozytoriami danych w ramach Java Persistence API (JPA). Kluczową korzyścią tego rozwiązania jest uproszczenie procesu dostępu do danych. Przykładowo, mając tabelę w bazie danych z odpowiadającą jej encją Hibernate, możemy natychmiastowo ją wykorzystywać. Spring Data JPA eliminuje potrzebę tworzenia od podstaw standardowych operacji bazodanowych, takich jak CRUD. System dostarcza predefiniowane interfejsy z zestawem podstawowych metod dostępowych, co umożliwia efektywne tworzenie, usuwanie oraz modyfikowanie rekordów bez nadmiernego obciążania kodu aplikacji.

Jako entuzjaści Spring Boot, wprowadzamy konfigurację szablonu startowego, która zapewnia nam niezbędny zestaw zależności. W tej części kursu opieramy się na Spring Boot w wersji 1.x, który jest kompatybilny ze Spring Data JPA.

W rozdziale Spring Data JPA 2 / 3 - Podstawy przedstawiamy wersję dla Spring Boot 2 / 3. Różnice nie są duże. Polegają głównie na wprowadzeniu udogodnień znanych z Javy 8 oraz na zmianie nazewnictwa niektórych metod find (ale o tym szerzej we wspomnianym rozdziale).

Wracając do meritum, tak wygląda nasza zależność szablonu startowego spring-boot-starter-data-jpa:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Po zbudowaniu projektu możemy przystąpić do przyjrzenia się kilku istotnym interfejsom, które dostajemy razem ze starterem. Będziemy z nich korzystać (w szczególności z jednego z nich) podczas wykonywania operacji na danych w systemie.

Podstawowy interfejs - CrudRepository

Dostarcza podstawowe wersje metod dla operacji typu CRUD (Create, Read, Update, Delete):
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {

	<S extends T> S save(S entity);

	<S extends T> Iterable<S> save(Iterable<S> entities);

	T findOne(ID id);

	boolean exists(ID id);

	Iterable<T> findAll();

	Iterable<T> findAll(Iterable<ID> ids);

	long count();

	void delete(ID id);

	void delete(T entity);

	void delete(Iterable<? extends T> entities);
    
	void deleteAll();
}
Wytłumaczenie kolejnych kategorii metod wydaje się być zwykłą formalnością, ale podsumujmy:

save - zapisuje encje w bazie (pojedynczo lub cały zbiór)
findOne - wyszukuje encje po id
exists - sprawdza po id czy encja istnieje
findAll - pobiera wszystkie encje zwracając interfejs Iterable
count - zlicza wszystkie encje
delete - usuwa encje po podanym obiekcie lub zbiorze obiektów
deleteAll - usuwa wszystkie encje
   

PagingAndSortingRepository rozszerzający CrudRepository

Dorzuca - w stosunku do tego co ma CrudRepository - metody wyciągające encje w sposób posortowany i stronicowany:
public interface PagingAndSortingRepository<T, ID extends Serializable>
                                                              extends CrudRepository<T, ID> {

	Iterable<T> findAll(Sort sort);

	Page<T> findAll(Pageable pageable);
}
Metody te rozumiemy w ten sposób:

findAll(Sort sort) - pobiera wszystkie encje (z uwzględnieniem sortowania) i zwraca interfejs Iterable
findAll(Pageable pageable) - pobiera wszystkie encje (z uwzględnieniem stronicowania) i zwraca interfejs strony: Page

JPARepository rozszerzający PagingAndSortingRepository

Dostarcza bardziej precyzyjne wersje metod, np. poprzez zwracanie interfejsu List zamiast Iterable oraz dodaje nowe metody:
public interface JpaRepository<T, ID extends Serializable>
		extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {

	List<T> findAll();

	List<T> findAll(Sort sort);

	List<T> findAll(Iterable<ID> ids);

	<S extends T> List<S> save(Iterable<S> entities);

	void flush();

	<S extends T> S saveAndFlush(S entity);

	void deleteInBatch(Iterable<T> entities);

	void deleteAllInBatch();

	T getOne(ID id);

	@Override
	<S extends T> List<S> findAll(Example<S> example);

	@Override
	<S extends T> List<S> findAll(Example<S> example, Sort sort);
}
W tym przypadku wytłumaczenie kolejnych kategorii metod wygląda tak:

findAll - pobiera wszystkie encje (ew. sortowane), albo encje po zbiorze idików i zwraca interfejs List
save - zapisuje encje w bazie, podane w postaci zbioru bazującego na interfejsie Iterable
flush - wymusza odzwierciedlenie wykonanych operacji (na encjach) na bazie
saveAndFlush - zapisuje encje i wymusza odzwierciedlenie wykonanych operacji (na encjach) na bazie
deleteInBatch - usuwa całą partię encji, podaną w postaci zbioru bazującego na interfejsie Iterable
deleteAllInBatch - usuwa wszystkie encje w jednej partii
getOne - pobiera encję po id
findAll(Example...) - pobiera encje po podanej klasie przykładu (możliwe w wersji z sortowaniem)
Zatrzymajmy się w tym miejscu na chwilę, aby zauważyć jedną rzecz. Dwie ostatnie metody, pobierające obiekty po klasie przykładu (Example), znajdują się w tym interfejsie nieprzypadkowo, mimo iż mają niewiele wspólnego z dziedziczonymi klasami. Okazuje się bowiem, że interfejs JPARepository dziedziczy nie tylko z PagingAndSortingRepository, ale również z interfejsu QueryByExampleExecutor. Ten ostatni jest odpowiedzialny za dostarczenie deklaracji metod operujących właśnie na klasie przykładu.

JPARepository w akcji

No dobrze, zobaczmy zatem teraz przykład, który zobrazuje nam dokładnie jak działa JPARepository. Mamy klasę ItemServiceImpl implementującą doskonale znany nam już interfejs ItemService (znany dla czytających rozdziały po koleji ;)).

Mamy również interfejs, za pomocą którego będziemy wykonywać operacje na itemach. Interfejs ten dziedziczy z JPARepository i..nie posiada własnych metod (jedynie dziedziczy metody):
public interface ItemRepository extends JpaRepository<Item, Long> {
    
}
Wstrzykujemy obiekt interfejsu ItemRepository do naszego serwisu ItemServiceImpl i już możemy używać metod, które są dostępne wśród wszystkich dziedziczonych przez nas interfejsów (ItemRepository - JPARepository - PagingAndSortingRepository - CrudRepository). Dla naszego interfejsu określiliśmy również dwa parametry typu. Pierwszy - Item, wskazuje na klasę encji, a drugi - Long, reprezentuje typ klucza jaki jest używany przez encję Item.

Możemy teraz w naszym serwisie zdefiniować metodę do pobierania itemu po id (getAppaItem) i wykonać w niej operację pobrania encji po id:
@Service
public class ItemServiceImpl implements ItemService {

    private final Logger LOG = LoggerFactory.getLogger(ItemServiceImpl.class);
    
    private ItemRepository itemRepository;
    
    ...
    
    @Autowired
    public ItemServiceImpl(ItemRepository itemRepository, ...) {
    	this.itemRepository = itemRepository;
    }

    @Override
    public ItemResponseDTO getAppaItem(Long id) {
    
    	Item appaItem = itemRepository.findOne(id);
    
    	...
    
    	return appaItemResponseDTO;
    }
}
Oczywiście nasz interfejs nie musi być pusty. W miarę potrzeby możemy do niego dokładać własne metody, a nawet tworzyć całe zapytania, ale o tym już w kolejnych rozdziałach. Na koniec zobaczmy jeszcze jak wygląda zbiorcza lista metod, jakie możemy wykonać na naszym ItemRepository, zgodnie z tym co pisaliśmy o dziedziczeniu interfejsów:
Rekomendacja
W projekcie można zaimplementować każdy z powyższych interfejsów, niemniej ze względu na to, że interfejs JPARepository ma najszerszy zakres, warto od samego początku używać własnie jego. Nawet jeśli w początkowej fazie wydaje się nam, że nie będziemy potrzebować sortowania, stronicowania, bądź też operowania na klasie przykładu, to prędzej czy później przychodzi taki czas, że projekt się rozrasta, a początkowe założenia się zmieniają i nagle może być wymagane dodanie wspomnianych funkcjonalności. Profesjonalni programiści powinni być na to przygotowani już wcześniej, prawda?
Praktyka


W każdym z naszych kursów poświęconych Springowi, kod działa na poziomie warstw kontrolera, serwisu i repozytorium. Wszystkie klasy serwisowe są wyposażone w wstrzyknięte interfejsy, które umożliwiają wykonanie potrzebnych operacji.
Zdjęcie autora
Autor: Jarek Klimas
Data: 03 stycznia 2024
Labele: Backend, Podstawowy, Java
W tej strefie znajdziesz wszystko co niezbędne, aby komfortowo uczyć się Hibernate'a. Doskonale opisany kod nie zawiera zbędnych komplikacji, tylko samą esencję w postaci praktycznych przykładów. Tutaj odnajdziesz wszystko co jest istotne w danym temacie. Otrzymujesz pakiet złożony z kilku projektów wraz z obszernym wytłumaczeniem kodu.
Topowe Materiały
Spring IO: Spring Data
Baeldung: Introduction to Spring Data JPA

Udemy: [NEW] Spring Boot 3, Spring 6 & Hibernate for Beginners  —  polskie napisy

Stale się rozwijamy, a więc bądź na bieżąco!
Na ten adres będziemy przesyłać informacje o ważniejszych aktualizacjach, a także o nowych materiałach pojawiających się na stronie.
Polub nas na Facebooku:
Nasi partnerzy: stackshare
Javappa to również profesjonalne usługi programistyczne oparte o technologie JAVA. Jeśli chesz nawiązać z nami kontakt w celu uzyskania doradztwa bądź stworzenia aplikacji webowej powinieneś poznać nasze doświadczenia.
Kliknij O nas .


Pozycjonowanie stron: Grupa TENSE