Spring Data JPA - data projection in dynamic queries

in #java5 years ago

Problem: we want queries to our entities to eagerly fetch only the fields that we need in the given context (for example to show in a specific UI data table).

Requirement: our solution must be able to accept dynamic filter compositions.

Possible solutions:

  • Named Entity Graphs from the JPA standard
  • Projections mechanism from Spring Data

Lets research them!

spring-data.png

JPA Named Entity Graphs

This mechanism comes directly from the JPA specifications and is supported by Spring Data repositories. To use it we must first specify a graph on our entity. In that graph we define the fields to be eagerly fetched.

Entity with an example graph definition

@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
}

Spring Data repository which uses the example entity graph

public interface PersonRepository extends JpaRepository {
 
    @EntityGraph(value = "Person.justName", type = EntityGraphType.FETCH)
    List findByCountryCode(String countryCode);
}

As you can see using entity graphs is trivial in this simple example. However, there is a crucial problem for more complex data models if you choose Hibernate as your JPA implementation. There is a bug hanging around since 2014 which makes using named entity graphs impossible in @Embedded and @MappedSuperclass classes. If you're using another implementation that does not suffer from this limitation then you can read more about entity graphs in the Java EE documentation and the Springs Data documentation.

Spring Data Projections

This is a feature from outside of the JPA specs and is supported only by Spring Data repositories for JPA. It uses interfaces to define the scope of data which you want to be fetched eagerly.

Our entity

@Entity
public class Person {
 
    private UUID id;
    private String firstName;
    private String lastName;
    private String countryCode;
    
    // setters and getters
}

Interface which defines an example projections

public interface PersonNameOnly {
 
  String getFirstName();
  String getLastName();
}

Spring Data repository which uses the example projection

public interface PersonRepository extends JpaRepository {
 
    List findByCountryCode(String countryCode);
}

As you can see this solutions returns only an interfaces with the projected fields and not an entity in which you could choose to get other properties using lazy fetching. This can be seen as a downside but in fact its a safer approach if performance is crucial to your app. Spring Data projections also have some interesting additional features that named entity graphs cannot provide. Your can read more about them in the Spring Data JPA Reference Manual.

Usage in dynamic queries

Both mechanisms described in this article require some effort to make them work with dynamic queries constructed by the Specifications mechanism from Spring Data. Normaly we add the specifications mechanism to our repositories by extending the JpaSpecificationExecutor interface which gives a closed set of methods that accept Specification as their argument (optionally with the Pageable and Sort argument). Out of the box Spring Data JPA conventions do not let us to use Specification in our own methods. Because of this we cannot add our own dynamic query methods which would return a projection interface or have an @EntityGraph annotation. To solve this problem we must extend the internals of Spring Data - fortunately there are working third party libraries that do just that.

For Named Entity Graphs you have the spring-data-jpa-entity-graph library. However keep in mind that in the case of Hibernate it still will not solve the bug we mentioned earlier.

Maven dependancy

    com.cosium.spring.data
    spring-data-jpa-entity-graph
    ${springDataEntityGraphVersion}

Configuration

@Configuration
@EnableJpaRepositories(repositoryFactoryBeanClass = EntityGraphJpaRepositoryFactoryBean.class)
public class DataRepositoryConfiguration {
    //...
}

Repository

public interface PersonRepository
    extends JpaRepository, EntityGraphJpaSpecificationExecutor { }

Usage

Page filteredPeople = personRepository.findAll(
    filterSpecifications, pageRequest,
    new EntityGraph("Person.justName")
);

For Spring Data Projections there is a small library called specification-with-projection. There is also a feature request since 2016 to add this to the main library.

Maven dependancy

    th.co.geniustree.springdata.jpa
    specification-with-projections
    ${springDataSpecificationWithProjectionsVersion}

Configuration

@Configuration
@EnableJpaRepositories(repositoryFactoryBeanClass = JpaSpecificationExecutorWithProjectionImpl.class)
public class DataRepositoryConfiguration {
    //...
}

Repository

public interface PersonRepository
    extends JpaRepository, JpaSpecificationExecutorWithProjection { }

Usage

Page filteredPeople = personRepository.findAll(
    filterSpecifications,
    PersonNameOnly.class
    pageRequest
);

Source: https://walczak.it/blog/spring-data-jpa-projection-dynamic-queries


This article is a result of our cooperation with Nextbuy - a SaaS company which develops a procurement and online auction platform to connect buyers and suppliers. We provide various consulting and software development services to them and they have kindly allowed us to publish part of the resulting research / design documents. You can checkout their amazing platform at www.nextbuy24.com

Sort:  

Congratulations @walczakit! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!

Coin Marketplace

STEEM 0.19
TRX 0.25
JST 0.038
BTC 97484.70
ETH 3417.93
USDT 1.00
SBD 3.04