Spring Data JPA 1.x

Spring Data JPA dostarcza narzędzia obsługi repozytorium danych dla Java Persistence API (JPA). Największą zaletą tego rozwiązania jest ułatwienie procesu zarządzania dostępem do źródeł danych. Mamy na przykład tabelę w bazie danych, dla której przygotowaliśmy encję w Hibernate i chcemy jej od razu używać. Tak po prostu. Dzięki Spring Data JPA nie musimy wymyślać koła od nowa, aby móc wykonywać najprostsze operacje bazodanowe w stylu CRUD. Od razu dostajemy gotowe interfejsy, które posiadają odpowiednio przygotowane podstawowe metody dostępowe. Możemy z łatwością tworzyć, usuwać i modyfikować rekordy bez przesadnego przeładowania kodem.

Oczywiście jako fani Spring Boot-a, od razu zabieramy się do przedstawienia szablonu startera, dzięki któremu uzyskujemy odpowiedni zestaw zależności. Wspomnijmy tylko, że w tym rozdziale kursu bazujemy na - ciągle jeszcze powszechnie używanym - Spring Boocie w wersji 1.x, który współpracuje z wersją Spring Data JPA 1.x. W rozdziale Spring Data JPA 2.x - Podstawowe funkcjonalności przedstawiamy najnowszą wersję działającą ze Spring Boot 2. 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 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:
Nasza rekomendacja
W projekcie można zaimplementować każdy z powyższych interfejsów, niemniej ze względu na to, że interfejs JPARepository jest najszerszy zakresem, 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?
Używamy w StartAPPa


We wszystkich kursach aplikacji kod operuje na warstwach kontrolera, serwisu i repository. Każda klasa serwisu posiada wstrzyknięty interfejs, za pomocą którego wykonujemy wymagane operacje. Przytoczony przykład pochodzi z kursu Formularz Zaawansowany (pobieranie itemu do edycji w formularzu). W wersji oryginalnej przykład jest oczywiście bardziej rozbudowany, ponieważ uwzględnia również przetwarzanie danych i zwrócenie ich do kontrolera.
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.
Linki
https://docs.spring.io/spring-data/jpa/docs/1.11.16.RELEASE/reference/html

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:
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 .