Fetch data with Spring Data JPA DTO Projections
When using Spring Data JPA to fetch data, you often use queries with all the properties of an Entity, for example:
package org.o7planning.spring_jpa_projections.repository;
import org.o7planning.spring_jpa_projections.entity.Employee;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
@Query("SELECT e FROM Employee e " //
+ " where e.empNo = :empNo")
public Employee getByEmpNo(String empNo);
}
Each Entity represents a table in the database, it has many properties, each property corresponds to a column in the table. Sometimes you only need to query a few columns instead of all, which increases application performance.
Employee table.
In this article I show you how to use Spring Data JPA DTO Projections to create custom queries, including only the properties you are interested in.
No ADS
Semantically "Projection" refers to a mapping between a JPA query to the properties of a custom Java DTO.
DTOThe concept of DTO (Data Transfer Object) is explained in a complicated way online, but it is simply a class with properties to hold data, used to transfer data from one place to another. In case of Spring Data JPA, data is transferred from Database to your application.
In Spring Data JPA, you will use an interface to describe a DTO with properties you are interested in. The rest will be done by Spring Data JPA, which will create a proxy DTO class that implements this interface at runtime of the application. The proxy DTO class will contain the actual data.
package org.o7planning.spring_jpa_projections.view;
public interface EmployeeView {
public String getEmpNo();
public String getFullName();
public String getEmail();
}
1. Entity Classes
In this article I will use the two Entity classes below to illustrate in the examples:
Department.java
package org.o7planning.spring_jpa_projections.entity;
import java.io.Serializable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
@Entity
@Table(name = "Department", uniqueConstraints = { //
@UniqueConstraint(name = "DEPARTMENT_UK", columnNames = { "Dept_No" }), })
public class Department implements Serializable {
private static final long serialVersionUID = 2091523073676133566L;
@Id
@GeneratedValue(generator = "my_seq")
@SequenceGenerator(name = "my_seq", //
sequenceName = "main_seq", allocationSize = 1)
private Long id;
@Column(name = "Dept_No", length = 32, nullable = false)
private String deptNo;
@Column(name = "Name", length = 128, nullable = false)
private String name;
// Getters & Setters
}
Employee.java
package org.o7planning.spring_jpa_projections.entity;
import java.io.Serializable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
@Entity
@Table(name = "Employee", uniqueConstraints = { //
@UniqueConstraint(name = "EMPLOYEE_UK", columnNames = { "Emp_No" }), })
public class Employee implements Serializable {
private static final long serialVersionUID = 8195147871240380311L;
@Id
@GeneratedValue(generator = "my_seq")
@SequenceGenerator(name = "my_seq", //
sequenceName = "main_seq", allocationSize = 1)
private Long id;
@Column(name = "Emp_No", length = 32, nullable = false)
private String empNo;
@Column(name = "First_Name", length = 64, nullable = false)
private String firstName;
@Column(name = "Last_Name", length = 64, nullable = false)
private String lastName;
@Column(name = "Phone_Number", length = 32, nullable = true)
private String phoneNumber;
@Column(name = "Email", length = 64, nullable = false)
private String email;
@ManyToOne
@JoinColumn(name = "Department_Id", nullable = false, //
foreignKey = @ForeignKey(name = "Dept_Emp_Fk"))
private Department department;
// Getters & Setters
}
2. Basic example
The steps to deploy Spring Data JPA Projections are very simple, you only need an interface that describes a DTO with the properties you are interested in. Then write a JPA query to map the columns to the DTO's properties. The rest will be done by Spring Data JPA, which will create a proxy DTO class, that implements this interface at runtime of the application. The proxy DTO class is what actually holds the data.
In this example we create an interface that describes a DTO, with a few properties, corresponding to a few columns of data we are interested in.
EmployeeView.java
package org.o7planning.spring_jpa_projections.view;
public interface EmployeeView {
public String getEmpNo();
public String getFullName();
public String getEmail();
}
Then write a query to map the data columns to the properties of the DTO created in the step above.
EmployeeRepository.java (*)
package org.o7planning.spring_jpa_projections.repository;
import java.util.List;
import org.o7planning.spring_jpa_projections.entity.Employee;
import org.o7planning.spring_jpa_projections.view.EmployeeView;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
//
// IMPORTANT: Required: "as empNo", "as fullName", "as email".
//
@Query("SELECT e.empNo as empNo, " //
+ " concat(e.firstName, ' ', e.lastName) as fullName, " //
+ " e.email as email " //
+ " FROM Employee e")
public List<EmployeeView> listEmployeeViews();
// Other methods ..
}
3. Open Projections
In this example the "fullName" property of the DTO will not appear in the JPA query. But its value is still determined correctly based on the @Value annotation.
EmployeeView2.java
package org.o7planning.spring_jpa_projections.view;
import org.springframework.beans.factory.annotation.Value;
public interface EmployeeView2 {
public String getEmpNo();
@Value("#{target.firstName + ' ' + target.lastName}")
public String getFullName();
public String getEmail();
}
The JPA query below does not include "fullName", but it provides "firstName" and "lastName" to calculate "fullName" for the DTO.
EmployeeRepository.java (** ex2)
package org.o7planning.spring_jpa_projections.repository;
import java.util.List;
import org.o7planning.spring_jpa_projections.entity.Employee;
import org.o7planning.spring_jpa_projections.view.EmployeeView;
import org.o7planning.spring_jpa_projections.view.EmployeeView2;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
//
// IMPORTANT: Required: "as empNo", "as firstName", "as lastName", "as email".
//
@Query("SELECT e.empNo as empNo, " //
+ " e.firstName as firstName, " // (***)
+ " e.lastName as lastName, " // (***)
+ " e.email as email " //
+ " FROM Employee e")
public List<EmployeeView2> listEmployeeView2s();
// Other methods ..
}
4. Nested Projections
Next is an example with nested Projections.
EmployeeView3.java
package org.o7planning.spring_jpa_projections.view;
public interface EmployeeView3 {
public String getEmpNo();
public String getFullName();
public String getEmail();
public DepartmentView3 getDepartment();
}
DepartmentView3.java
package org.o7planning.spring_jpa_projections.view;
import org.springframework.beans.factory.annotation.Value;
public interface DepartmentView3 {
// Map to "deptNo" property of Department entity.
// Same property names --> No need @Value
public String getDeptNo();
// Map to "name" property of Department entity.
// Different property names --> NEED @Value
@Value("#{target.name}")
public String getDeptName();
}
EmployeeRepository.java (** ex3)
package org.o7planning.spring_jpa_projections.repository;
import java.util.List;
import org.o7planning.spring_jpa_projections.entity.Employee;
import org.o7planning.spring_jpa_projections.view.EmployeeView3;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
//
// IMPORTANT: Required: "as empNo", "as fullName", "as email", "as department".
//
@Query("SELECT e.empNo as empNo, " //
+ " concat(e.firstName, ' ', e.lastName) as fullName, " //
+ " e.email as email, " //
+ " d as department " //
+ " FROM Employee e " //
+ " Left join e.department d ")
public List<EmployeeView3> listEmployeeView3s();
// Other methods ..
}
5. Repository method with parameters
EmployeeRepository.java (** ex4)
package org.o7planning.spring_jpa_projections.repository;
import org.o7planning.spring_jpa_projections.entity.Employee;
import org.o7planning.spring_jpa_projections.view.EmployeeView;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
//
// IMPORTANT: Required: "as empNo", "as fullName", "as email".
//
@Query("SELECT e.empNo as empNo, " //
+ " concat(e.firstName, ' ', e.lastName) as fullName, " //
+ " e.email as email " //
+ " FROM Employee e " //
+ " WHERE e.empNo = :empNo")
public EmployeeView findByEmpNo(String empNo);
// Other methods ..
}
6. Repository method naming convention
In some cases you may not need to write a JPA query if the Repository method name and its parameters conform to Spring Data JPA conventions. This means you do not need to use the @Query annotation.
For example, we create a method with a name according to the following rule:
- "find" + "By" + "PropertyName"
- "find" + "Xxx" + "By" + "PropertyName"
No ADS
EmployeeRepository.java (** ex5)
package org.o7planning.spring_jpa_projections.repository;
import org.o7planning.spring_jpa_projections.entity.Employee;
import org.o7planning.spring_jpa_projections.view.EmployeeView5;
import org.springframework.data.repository.CrudRepository;
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
//
// Without @Query annotation. (Ex5)
// - "find" + "By" + "PropertyName".
// - "find" + "Xxx" + "By" + "PropertyName".
//
public EmployeeView5 findEmployeeView5ByEmpNo(String empNo);
// Other methods ..
}
Note, the properties of the Interface must also be the same as the properties of the original Entity.
EmployeeView5.java
package org.o7planning.spring_jpa_projections.view;
public interface EmployeeView5 {
public String getEmpNo();
public String getFirstName();
public String getLastName();
public String getEmail();
}
Another example, without using @Query annotation:
EmployeeRepository.java (** ex6)
package org.o7planning.spring_jpa_projections.repository;
import org.o7planning.spring_jpa_projections.entity.Employee;
import org.o7planning.spring_jpa_projections.view.EmployeeView6;
import org.springframework.data.repository.CrudRepository;
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
//
// Without @Query annotation. (Ex6)
// - "find" + "By" + "PropertyName".
// - "find" + "Xxx" + "By" + "PropertyName".
//
public EmployeeView6 findView6ByEmpNo(String empNo);
// Other methods ..
}
EmployeeView6.java
package org.o7planning.spring_jpa_projections.view;
import org.springframework.beans.factory.annotation.Value;
public interface EmployeeView6 {
public String getEmpNo();
@Value("#{target.firstName + ' ' + target.lastName}")
public String getFullName();
public String getEmail();
}
In the case of a method without parameters, the naming rule is:
- "find" + "By"
- "find" + "Xxx" + "By"
EmployeeRepository.java (** ex7)
package org.o7planning.spring_jpa_projections.repository;
import java.util.List;
import org.o7planning.spring_jpa_projections.entity.Employee;
import org.o7planning.spring_jpa_projections.view.EmployeeView6;
import org.springframework.data.repository.CrudRepository;
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
//
// Without @Query annotation. (Ex7)
// - "find" + "By"
// - "find" + "Xxx" + "By"
//
public List<EmployeeView6> findView6By();
// Other methods ..
}
No ADS
In short, to avoid having to use the @Query annotation you need to follow the naming rules for the Repository method. For me, I like to use @Query to make things clearer and to be able to name methods arbitrarily, even though it takes a bit more coding.
- "find" + "By" + "PropertyNames" + "Keyword"
- "find" + "Xxx" + "By" + "PropertyNames" + "Keyword"
Keyword | Method Name | JPA Query |
GreaterThan | findByAgeGreaterThan(int age) | Select {properties} from Person e where e.age > :age |
LessThan | findByAgeLessThan(int age) | Select {properties} from Person e where e.age < :age |
Between | findByAgeBetween(int from, int to) | Select {properties} from Person e where e.age between :from and :to |
IsNotNull, NotNull | findByFirstnameNotNull() | Select {properties} from Person e where e.firstname is not null |
IsNull, Null | findByFirstnameNull() | Select {properties} from Person e where e.firstname is null |
Like | findByFirstnameLike(String name) | Select {properties} from Person e where e.firstname like :name |
(No Keyword) | findByFirstname(String name) | Select {properties} from Person e where e.firstname = :name |
Not | findByFirstnameNot(String name) | Select {properties} from Person e where e.firstname <> :name |
... |
7. Dynamic Projections
An Entity can have one or more Projections, depending on your application logic. You might consider using the Repository method with the Class parameter as follows:
EmployeeRepository.java (** ex8)
package org.o7planning.spring_jpa_projections.repository;
import java.util.List;
import org.o7planning.spring_jpa_projections.entity.Employee;
import org.springframework.data.repository.CrudRepository;
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
// (Ex8)
// - "find" + "By"
// - "find" + "Xxx" + "By"
//
public <T> List<T> findViewByEmpName(String empNo, Class<T> type);
// Other methods ..
}
No ADS
Spring Boot Tutorials
- Deploy Spring Boot Application on Oracle WebLogic Server
- Create a User Registration Application with Spring Boot, Spring Form Validation
- Spring Boot File Upload Example
- Spring Boot and Groovy Tutorial with Examples
- Spring Boot and MongoDB Tutorial with Examples
- Spring Boot, Hibernate and Spring Transaction Tutorial with Examples
- Spring Boot and Spring Data JPA Tutorial with Examples
- Secure Spring Boot RESTful Service using Auth0 JWT
- Spring Email Tutorial with Examples
- Spring Boot, Apache Tiles, JSP Tutorial with Examples
- Create a Login Application with Spring Boot, Spring Security, JPA
- Use Twitter Bootstrap in Spring Boot
- Spring Tutorial for Beginners
- Spring Boot Interceptors Tutorial with Examples
- Create a Login Application with Spring Boot, Spring Security, Spring JDBC
- Spring Boot File Upload with jQuery Ajax Example
- Spring Boot and JSP Tutorial with Examples
- Install Spring Tool Suite for Eclipse
- Spring Boot, JPA and Spring Transaction Tutorial with Examples
- Spring Boot and Thymeleaf Tutorial with Examples
- Integrating Spring Boot, JPA and H2 Database
- Spring Boot File Upload with AngularJS Example
- Fetch data with Spring Data JPA DTO Projections
- Use Multiple DataSources with Spring Boot and JPA
- CRUD Example with Spring Boot, REST and AngularJS
- Spring Boot and Mustache Tutorial with Examples
- Spring JDBC Tutorial with Examples
- Install a free Let's Encrypt SSL certificate for Spring Boot
- Spring Boot Tutorial for Beginners
- Spring Boot Common Properties
- Spring Boot, Spring JDBC and Spring Transaction Tutorial with Examples
- Use multiple ViewResolvers in Spring Boot
- Deploy Spring Boot Application on Tomcat Server
- Configure Spring Boot to redirect HTTP to HTTPS
- Example of OAuth2 Social Login in Spring Boot
- Create a simple Chat application with Spring Boot and Websocket
- Use Multiple DataSources with Spring Boot and RoutingDataSource
- Spring Boot and FreeMarker Tutorial with Examples
- Create a Shopping Cart Web Application with Spring Boot, Hibernate
- Spring Boot Restful Client with RestTemplate Example
- Secure Spring Boot RESTful Service using Basic Authentication
- Spring Boot File Download Example
- CRUD Restful Web Service Example with Spring Boot
- Create a Multi Language web application with Spring Boot
- Use Logging in Spring Boot
- Run background scheduled tasks in Spring
- Application Monitoring with Spring Boot Actuator
Show More