Spring Data JPA Projections: A Beginners Guide
Did you know that you can use Spring Data JPA projections to reduce the amount of data you fetch from the database and improve the performance of your queries? In this article, you will learn how to use projections effectively.
Spring Data JPA is a popular Java specification that simplifies the development of web applications that use data from relational databases. It offers many features and benefits, such as auto-configuration, query methods, repositories, and more. However, one of the challenges of using Spring Data JPA is how to optimize the queries and avoid fetching unnecessary data from the database. “According to the Spring Data JPA documentation, projections can reduce the amount of data transferred between the database and the application by up to 80%, depending on the use case.” This can affect the performance, memory usage, and bandwidth of your application.
One way to solve this problem is to use Spring Data JPA projections. Projections are a feature that allows you to define interfaces that represent a subset of the properties of your domain entities. This way, you can customize the results of your queries and only retrieve the data you need. You can also use projections to transform the data into different shapes or formats, such as DTOs, JSON, XML, etc.
In this article, you will learn how to use projections effectively. By the end of this article, you will be able to use projections confidently and efficiently in your Spring Data JPA projects.
Prerequisites
Before you start writing the code for this article, you need to have the following tools, libraries, and frameworks installed on your machine:
- Java 11 or higher
- Spring Boot 2.5.4 or higher
- Spring Data JPA 2.5.4 or higher
- H2 Database 1.4.200 or higher
- Maven 3.8.1 or higher:
- IDE of your choice:
You can also download the complete source code for this article from here or use the following command to clone the repository:
git clone https://github.com/eidankhan/spring-data-jpa-projections-example.git
Setting Up the Project
To create a Spring Boot project with Spring Data JPA and H2 dependencies, you can use Spring Initializer, a web tool that generates a project skeleton for you. You can access Spring Initializer from here.
On the Spring Initializer page, you can choose the following options:
- Project: Maven Project
- Language: Java
- Spring Boot: 2.5.4 or higher
- Project Metadata: You can fill in the group, artifact, name, description, and package name as you like. For example, you can use
com.projections
as the group,spring-data-jpa-projections-example
as the artifact, andcom.projections.springdatajpaprojectionsexample
as the package name. - Dependencies: You need to add the following dependencies to your project: Spring Web, Spring Data JPA, and H2 Database. You can search for them in the Add dependencies field and click on them to add them to your project.
After choosing the options, you can click on the Generate button to download a zip file containing your project. You can unzip the file and open it in your IDE of choice.
To configure the application properties, you need to edit the application.properties
file in the src/main/resources
folder. You can add the following properties to the file:
# Specify the database URL, username, and password
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username=sa
spring.datasource.password=
# Enable the H2 console for accessing the database from the browser
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# Specify the JPA properties, such as the dialect, the show-sql flag, and the ddl-auto mode
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create-drop
These properties will set up an in-memory H2 database for your application, enable the H2 console for accessing the database from the browser, and configure the JPA properties for your application.
Creating the Entity Classes
To use Spring Data JPA projections, you need to have some entity classes that represent your domain model. In this article, we will use a simple example of a Person and an Address entity. A Person has an id, a name, an email, and an address. An Address has an id, a street, a city, a state, and a zip code. A Person and an Address have a bidirectional one-to-one relationship, meaning that a Person has one and only one Address, and an Address belongs to one and only one Person.
To create the entity classes, you need to use the @Entity
annotation to mark them as JPA entities. You also need to use the @Id
annotation to mark the primary key field, and the @GeneratedValue
annotation to specify the generation strategy for the primary key. You can use the @Column
annotation to customize the column name, length, or nullable properties. You can use the @OneToOne
annotation to define the one-to-one relationship between the entities, and the @JoinColumn
annotation to specify the foreign key column. You can also use the @MapsId
annotation to share the primary key value between the entities.
Here is the code for the Person entity class:
@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "name", length = 50, nullable = false)
private String name;
@Column(name = "email", length = 50, nullable = false, unique = true)
private String email;
@OneToOne
@JoinColumn(name = "address_id")
private Address address;
// Constructors, getters, setters, equals, hashcode, and toString methods
}
Here is the code for the Address entity class:
@Entity
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "street", length = 50, nullable = false)
private String street;
@Column(name = "city", length = 50, nullable = false)
private String city;
@Column(name = "state", length = 50, nullable = false)
private String state;
@Column(name = "zip_code", length = 10, nullable = false)
private String zipCode;
@OneToOne(mappedBy = "address")
private Employee employee;
// Constructors, getters, setters, equals, hashcode, and toString methods
}
Creating the Projection Interfaces
A projection interface is an interface that represents a partial view of an entity class. It only contains the properties that you want to select from the entity class. You can use a projection interface to customize the results of your queries and improve the performance of your application.
To create a projection interface, you need to follow these steps:
- Declare an interface with the name of your choice. For example, you can name it
EmployeeDetails
. - Add getter methods for the properties that you want to select from the entity class. The names of the methods must match the names of the properties in the entity class. For example, if you want to select the
name, email, city and state
of the employee you can add the following methods:
package com.projections.springdatajpaprojectionsexample.projection;
import org.springframework.beans.factory.annotation.Value;
public interface EmployeeDetails {
String getName();
String getEmail();
@Value("#{target.address.city}")
String getCity();
@Value("#{target.address.state}")
String getState();
}
Here, we use the @Value
annotation to access the city property of the address object.
To use the projection interfaces in our repository methods, we just need to use them as the return type, as shown in the following example:
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
EmployeeDetails findByEmail(String email);
}
Creating the Service Classes
A service class is a class that contains the business logic of your application. It usually interacts with the repository layer to perform CRUD operations on the entities. To create a service class for our example, you need to follow these steps:
- Declare a class with the name of your choice. For example, you can name it
EmployeeService
. - Annotate the class with
@Service
to indicate that it is a Spring component that provides business services. - Inject the
EmployeeRepository
bean using the@Autowired
annotation. - Define methods that use the projection interfaces as parameters or return types. For example, you can define a method that returns the employee details by email:
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
public EmployeeDetails getEmployeeDetailsByEmail(String email) {
return employeeRepository.findByEmail(email);
}
}
Creating the Controller Classes
A controller class is a class that handles the HTTP requests from the clients. It usually interacts with the service layer to perform the required operations and return the appropriate responses. To create a controller class for our example, you need to follow these steps:
- Declare a class with the name of your choice. For example, you can name it
EmployeeController
. - Annotate the class with
@RestController
to indicate that it is a Spring component that handlesRESTful web services
. - Inject the
EmployeeService
bean using the @Autowired annotation. - Define methods that map to the HTTP endpoints using the
@GetMapping, @PostMapping, @PutMapping, or @DeleteMapping
annotations. For example, you can define a method that returns the employee details by email using the@GetMapping
annotation:
@RestController
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@GetMapping("/employees/{email}")
public EmployeeDetails getEmployeeDetailsByEmail(@PathVariable String email) {
return employeeService.getEmployeeDetailsByEmail(email);
}
}
In this article, we have explored the different basics of projections that Spring Data JPA offers to customize the results of our queries. We have learned how to use interface-based projections. Projections are a useful feature that can help us improve the performance and readability of our data access layers. They can also help us design our data transfer objects according to our needs and preferences. We hope you have enjoyed this article and learned something new. If you have any questions or feedback, please leave a comment below. Thank you for reading! 😊