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!

spring-data.png

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

Śledź naszego bloga

facebook_2015_logo_detail.png

Logo - 4P.png

Nadchodzące szkolenia

tall-banner.png


Ładowanie Rozmowy