- Published on
Projection in Spring Data JPA
- Authors
- Name
- Sunil Kanzar
- @SunilKanzar
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
andAuthor
@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.
- Rename interface method to
getOrganization_name
- 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);