Problem: chcemy aby zapytania do naszych encji ściągały tylko te pola, które są nam potrzebne w danej sytuacji (np. do pokazania w specyficznej tabeli w UI).
Wymaganie: nasze rozwiązanie musi mieć możliwość przyjęcia dowolnej kompozycji filtrów.
Możliwe rozwiązania:
- Named Entity Graph ze standardu JPA.
- Projections - mechanizm z Spring Data
Zbadajmy je!
JPA Named Entity Graphs
Ten mechanizm pochodzi bezpośrednio ze specyfikacji JPA i jest wspierany przez repozytoria Spring Data. Aby go wykorzystać musimy najpierw zdefiniować graf na naszej encji. W owym grafie określimy, które pola mają być pobierane w ramach zapytania poprzez tzw. eager fetching.
Encja z przykładową definicją grafu
@NamedEntityGraph( name="Person.justName", attributeNodes={ @NamedAttributeNode("firstName"), @NamedAttributeNode("lastName") } ) @Entity public class Person { private UUID id; private String firstName; private String lastName; private String countryCode; // setters and getters }
Repozytorium Spring Data wykorzystujące przykładowy graf
public interface PersonRepository extends JpaRepository<Person, UUID> { @EntityGraph(value = "Person.justName", type = EntityGraphType.FETCH) List<Person> findByCountryCode(String countryCode); }
Jak widać wykorzystanie Named Entity Graphs jest trywialne w tego typu prostym przykładzie. Istnieje jednak poważny problem w Hibernate (najpopularniejszej implementacji JPA), który uwidacznia się przy nieco bardziej złożonych modelach danych. W bibliotece jest bug wiszący od 2014, który uniemożliwia wykorzystanie Named Entity Graphs w klasach @Embedded oraz @MappedSuperclass. Jeżeli wykorzystujesz inną implementację, która nie ma tego typu ograniczeń, to możesz dowiedzieć się więcej o Named Entity Graphs z dokumentacji JavaEE oraz dokumentacji Spring Data.
Spring Data Projections
Jest to funkcjonalność spoza specyfikacji JPA i jest wspierana wyłącznie w repozytoriach Spring Data dla JPA. Wykorzystuje interfejsy, aby zdefiniować zakres danych, który chcesz pobrać w zapytaniu poprzez tzw. eadger fetching.
Nasza encja
@Entity public class Person { private UUID id; private String firstName; private String lastName; private String countryCode; // setters and getters }
Interfejs definiujący przykładową projekcję
public interface PersonNameOnly { String getFirstName(); String getLastName(); }
Repozytorium Spring Data wykorzystujące przykładową projekcję
public interface PersonRepository extends JpaRepository<Person, UUID> { List<PersonNameOnly> findByCountryCode(String countryCode); }
Jak widać to rozwiązanie zwraca tylko interfejs z polami projekcji, a nie encję, z której można by w dalszym przetwarzaniu pobrać pozostałe pola poprzez tzw. lazy fetching. Można to postrzegać jako minus tego mechanizmu, ale jest to też bezpieczniejsze podejście jeżeli wydajność jest kluczowa dla Twojej aplikacji. Projekcje z Spring Data mają też parę ciekawych opcji, których Named Entity Graphs nie zapewniają. Można o nich poczytać w Spring Data JPA Reference Manual.
Wykorzystanie w zapytaniach dynamicznych
Oba mechanizmy opisane w niniejszym artykule wymagają nieco wysiłku aby zmusić je do współpracy z dynamicznymi zapytaniami tworzonymi przez Spring Data Speifications. Normalnie dodajemy mechanizm specyfikacji do naszych repozytoriów poprzez rozszerzenie ich interfejsem JpaSpecificationExecutor<T>, który to zapewnia zamknięty zbiór metod akceptujących Specification<T> jako argument (opcjonalnie z argumentem Pageable oraz Sort). Konwencje Spring Data JPA domyślnie nie pozwolą nam użyć Specification<T> w naszych metodach. Z tego powodu nie możemy dodać własnych metod, które zwracałyby interfejsy projekcji lub miały nad sobą adnotacje @EntityGraph. Aby rozwiązać ten problem musimy rozszerzyć wewnętrzną funkcjonalność Spring Data - na szczęście mamy gotowe biblioteki, które to robią.
Dla Named Entity Graphs mamy bibliotekę spring-data-jpa-entity-graph. Należy jednak mieć na uwadze, że w przypadku Hibernate nadal nie rozwiąże to wcześniej wspomnianego buga.
Zależność Maven
<dependency> <groupId>com.cosium.spring.data</groupId> <artifactId>spring-data-jpa-entity-graph</artifactId> <version>${springDataEntityGraphVersion}</version> </dependency>
Konfiguracja
@Configuration @EnableJpaRepositories(repositoryFactoryBeanClass = EntityGraphJpaRepositoryFactoryBean.class) public class DataRepositoryConfiguration { //... }
Repozytorium
public interface PersonRepository extends JpaRepository<Person, UUID>, EntityGraphJpaSpecificationExecutor<Person> { }
Wykorzystanie
Page<Persion> filteredPeople = personRepository.findAll( filterSpecifications, pageRequest, new EntityGraph("Person.justName") );
Dla Spring Data Projections istnieje mała biblioteka zwana specification-with-projection. Jest też zgłoszenie od 2016 aby dodać tą funkcjonalność do głównej biblioteki.
Zależność Maven
<dependency> <groupId>th.co.geniustree.springdata.jpa</groupId> <artifactId>specification-with-projections</artifactId> <version>${springDataSpecificationWithProjectionsVersion}</version> </dependency>
Konfiguracja
@Configuration @EnableJpaRepositories(repositoryFactoryBeanClass = JpaSpecificationExecutorWithProjectionImpl.class) public class DataRepositoryConfiguration { //... }
Repozytorium
public interface PersonRepository extends JpaRepository<Person, UUID>, JpaSpecificationExecutorWithProjection<Person> { }
Wykorzystanie
Page<PersonNameOnly> filteredPeople = personRepository.findAll( filterSpecifications, PersonNameOnly.class pageRequest );
Ten artykuł jest wynikiem naszej współpracy z Nextbuy - firmą dostarczającą w modelu SaaS platformę zakupową i przetargową, która łączy kupców i dostawców. Świadczymy dla nich usługi doradcze oraz wsparcie w pracach programistycznych. Jesteśmy wdzięczni, że zgodzili się upublicznić część dokumentów projektowo-rozwojowych powstałych, w wyniku tego. Możecie sprawdzić ich świetną platformę na www.nextbuy24.com
Czy potrzebujesz pomocy z którymś tematem poruszonym na naszym blogu? Jeżeli tak, skontaktuj się z nami. Możemy pomóc poprzez doradztwo oraz usługi audytowe lub zorganizować warsztaty szkoleniowe dla Twoich pracowników. Możemy także wspomóc proces wytwarzania oprogramowania w Twojej firmie poprzez outsourcing naszych programistów.