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.
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"
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 ..
}
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 ..
}
Spring Boot Tutorials
- Install Spring Tool Suite for Eclipse
- Spring Tutorial for Beginners
- Spring Boot Tutorial for Beginners
- Spring Boot Common Properties
- Spring Boot and Thymeleaf Tutorial with Examples
- Spring Boot and FreeMarker Tutorial with Examples
- Spring Boot and Groovy Tutorial with Examples
- Spring Boot and Mustache Tutorial with Examples
- Spring Boot and JSP Tutorial with Examples
- Spring Boot, Apache Tiles, JSP Tutorial with Examples
- Use Logging in Spring Boot
- Application Monitoring with Spring Boot Actuator
- Create a Multi Language web application with Spring Boot
- Use multiple ViewResolvers in Spring Boot
- Use Twitter Bootstrap in Spring Boot
- Spring Boot Interceptors Tutorial with Examples
- Spring Boot, Spring JDBC and Spring Transaction Tutorial with Examples
- Spring JDBC Tutorial with Examples
- Spring Boot, JPA and Spring Transaction Tutorial with Examples
- Spring Boot and Spring Data JPA Tutorial with Examples
- Spring Boot, Hibernate and Spring Transaction Tutorial with Examples
- Integrating Spring Boot, JPA and H2 Database
- Spring Boot and MongoDB Tutorial with Examples
- Use Multiple DataSources with Spring Boot and JPA
- Use Multiple DataSources with Spring Boot and RoutingDataSource
- Create a Login Application with Spring Boot, Spring Security, Spring JDBC
- Create a Login Application with Spring Boot, Spring Security, JPA
- Create a User Registration Application with Spring Boot, Spring Form Validation
- Example of OAuth2 Social Login in Spring Boot
- Run background scheduled tasks in Spring
- CRUD Restful Web Service Example with Spring Boot
- Spring Boot Restful Client with RestTemplate Example
- CRUD Example with Spring Boot, REST and AngularJS
- Secure Spring Boot RESTful Service using Basic Authentication
- Secure Spring Boot RESTful Service using Auth0 JWT
- Spring Boot File Upload Example
- Spring Boot File Download Example
- Spring Boot File Upload with jQuery Ajax Example
- Spring Boot File Upload with AngularJS Example
- Create a Shopping Cart Web Application with Spring Boot, Hibernate
- Spring Email Tutorial with Examples
- Create a simple Chat application with Spring Boot and Websocket
- Deploy Spring Boot Application on Tomcat Server
- Deploy Spring Boot Application on Oracle WebLogic Server
- Install a free Let's Encrypt SSL certificate for Spring Boot
- Configure Spring Boot to redirect HTTP to HTTPS
- Fetch data with Spring Data JPA DTO Projections
Show More