Published on

Projection in Spring Data JPA

Authors

Overview

Some time we have lots of fields/property in table/entity, and we don't want all of those in our operation or in API response, in this case we can use Projection. So far we have seen how to provide criteria while querying JPA repository by method name (Derived Query), i.e. WHERE SQL Clause. Now this post considers how to select the specific fields out of all the fields available in table/entity, i.e. SQL SELECT

Setup Project

  • Use popular Database
  • Add org.springframework.boot:spring-boot-starter-data-jpa:<version> dependency for data jpa and dependency for appropriate database driver
  • define entity classes needed lets take and example Book and Author
@Table(name = "authors")
@Entity
class Author {
    @Id
    @GeneratedValue
    private Long id;
    @OneToOne
    private Book book;
    private String name;
    private Integer age;
    @Column(name = "organization_name")
    private String organizationName;
    private Boolean active;
    private String qualification;

    //getters and setters or lombok annotation
}
@Table(name = "books")
@Entity
class Book {
    @Id
    @GeneratedValue
    private Integer id;
    private String title;
    private Boolean softCopy;
    private Date published;
    private Integer pages;
    private String category;
    @OneToOne(mappedBy = "book")
    private Author author;

    //getters and setters or lombok annotation
}

Here we have one-to-one relationship between Author and Book; Author is the owning side i.e. Author will have book id in it and Book will not have any reference of author.

We have a couple of ways to achieve projection. We will see it one by one

Projection with Interface

The Main benefit of this method it we are using interface so we don't need implementation.

There are two types of projection here to explore:

Closed Projections

The name of getter method should match with the name of the entity field is known as closed projection

Let's take an example of Author entity where we have many fields/property in it and all we want is author's name, age and organization name.

In that case, we need to create an interface with getter of that three fields only

public interface AuthorView {
    String getName();

    Integer getAge();

    String getOrganizationName();
}

Projections with Derived Query Methods

Once you're done with interface, now you need to return it from repository method.

public interface AuthorRepository extends Repository<Author, Long> {
    List<AuthorView> findByName(String name);
}

In the background, Spring creates a proxy instance of the interface used in projection for each entity object, and redirects all calls coming towards the interface to that object by using created proxy.

Projections with JPA Non-native Query Methods

Projection can be done with Using @Query method too similar to Derived query method

Example of projection with JPA non-native Query or @Query annotation:

public interface AuthorRepository extends Repository<Author, Long> {
    @Query(value = "from Author where name=:name", nativeQuery = false)
    List<AuthorView> getAuthor(@Param("name") String name);
}

Projections with JPA native Query Methods

Many people think that projection only works with native method like Derived query and JPA Non-native query, but it also works with a database native query too.

Note: Problem with a native query is interface method name must match with field name defined in the database not in the entity, when I say "in database" that means the name returned from a query

Example of Projection with a database native query.

public interface AuthorRepository extends Repository<Author, Long> {
    @Query(value = "select * from authors where name = :name", nativeQuery = false)
    List<AuthorView> getAuthor(@Param("name") String name);
}

Note: Here this example will return only two fields with value as "organizationName" is defined as "organization_name" in a database. In this case, you have two options to resolve that issue.

  1. Rename interface method to getOrganization_name
  2. Rename field in SQL query like: select name, age, organization_name as organizationName from authors where name = :name

Projections with Recursive Entity

We can use projection with relational entity class too. Here in our example Book

public interface BookView {
    String getTitle();

    Integer getPages();
}

Now we need to create method that return BookView inside the AuthorView like

public interface AuthorView {
    String getName();

    Integer getAge();

    BookView getBook();
}

Use owning side interface as retriever

public interface AuthorRepository extends Repository<Author, Long> {
    List<AuthorView> findByName(@Param("name") String name);
}

This will return BookView interface inside the AuthorView result

Note:

  • Method name used to return BookView projection interface should match with name of the field in entity class
  • Recursive projection only works in one direction, and that is owning side to inverse side

Open Projections

Till this point, we have seen a Closed Projection where we keep entity/database field name and getter method name in interface as same. There is another type of projection where we can return calculated field, and we can name that method anything we want.

Let's take an example of Author where we want to get an author introduction as single field which is not present in the database but an introduction will be calculated run time with projection.

public interface AuthorView {
    Long getId();

    @Value("#{'My name is ' + target.name + ', I am working at ' + target.organizationName + ' and my education background is ' + target.qualification}")
    String getAuthorIntro();
}

The Expression passed to the @Value annotation is the SpEL expression, in which the target designator is the entity object.

Here in the above example fully qualified name if @Value annotation is org.springframework.beans.factory.annotation.Value.

Repository interface will stay as it is:

public interface AuthorRepository extends Repository<Author, Long> {
    List<AuthorView> findByName(@Param("name") String name);
}

Each found object will have two properties in it one is id of Author and another is introduction line constructed using expression.

Projections with Class or Record

We can archive projection through class too, Here in this case spring will not use proxy as it will not be needed.

Example projection Class:

public class AuthorDTO {
    private String authorName;
    private Long age;

    public AuthorDTO(String name, Long age) {
        this.authorName = name;
        this.age = age;
    }

    //getters, equals and hashcode or lombok annotation
}

Note:

  • We have to define a constructor in class
  • The Name of the constructor parameter must match with entity field name (It doesn't matter what you name it in projection Class)
  • equals and hashCode are needed with getter method

Example of projection class:

public interface AuthorRepository extends Repository<Author, Long> {
    AuthorDTO findByName(String name);
}

As Heading of the topic suggests, java record also works with projection

Example of java record equivalent to the class

public record AuthorDTO(String name, Long age) {

}

Dynamic Projections

We can define repository method in a way that it can return many type of projection type on demand.

Repository Example:

public interface AuthorRepository extends Repository<Author, Long> {
    <T> T findByName(String name, Class<T> type);
}

Now you can define any projection type and get Value according to it event we can return the entity itself if we want.

public record AuthorDTO(String name, Long age) {

}
AuthorDTO authorDTO = repository.findByName("author name", AuthorDTO.class);

Author author = repository.findByName("author name", Author.class);