Understanding Data Projection in Spring Boot with Hibernate

18 / Apr / 2024 by Ankit Kumar 0 comments

This article will delve into data projection in the context of Spring Boot and Hibernate. This blog will examine the significance of data projection, its advantages, and disadvantages. Furthermore, I will provide a comprehensive explanation of how to implement it in your Spring Boot applications efficiently.

What is data Projection?

Data projection in database querying involves selecting specific attributes from one or multiple entities or tables and retrieving only those attributes in the result set. This technique is useful when you do not require the complete entity object and aims to enhance performance by fetching only the necessary data.

Why use data projection?

Using data projection in Spring Boot offers several benefits:

  1. Performance Optimization: Data projection enables the retrieval of specific fields from database queries, rather than fetching complete objects. This leads to a notable decrease in data transfer between the application and the database, ultimately improving response times and enhancing overall performance.
  2. Bandwidth Efficiency: Opting for essential fields helps reduce the network bandwidth usage of your application, which is crucial when dealing with extensive datasets. This is especially significant for applications operating in settings with restricted network resources or when data transfer expenses need to be considered.
  3. Reduced Memory Usage: By fetching only the necessary fields from the database, it reduces the amount of memory required to store the data. This optimization can significantly enhance memory usage efficiency, particularly when dealing with extensive datasets.
  4. Improved Security: Data projection can enhance security by restricting the disclosure of sensitive or redundant data fields in the responses of your application.

Data Projection Techniques :

  • JPQL (Java Persistence Query Language) Queries: JPQL allows developers to write SQL-like queries to retrieve specific fields from entities. By selecting only the required fields in the SELECT clause, you can achieve data projection.
     @Query("SELECT NEW com.ttn.dataProjection.dto.UserDetailsProjectionDTO(u.name, u.email, a.city, a.pincode) FROM UserDetails u JOIN u.address a")
     List<UserDetailsProjectionDTO> findAllByActiveTrueOrderByNameDesc();
  • Spring Data JPA Projections: Spring Data JPA provides a powerful feature called projections, which allows defining interfaces to represent the desired subset of data. These projections can be used directly in repository methods.
@Data
public class UserDetailsProjectionDTO {
private String name;
private String email;
private String city;
private String pincode;

public UserDetailsProjectionDTO(String name, String email, String city, String pincode) {
this.name = name;
this.email = email;
this.city = city;
this.pincode = pincode;
}
}

public interface RolesProjection {

String getName();
}


public interface AddressProjection {
String getCity();
String getPincode();
}
  • DTO (Data Transfer Object) Projection: Another common approach is to use DTOs to represent the projected data. DTOs are plain Java objects that contain only the fields needed for a particular use case.
//create a class with name UserDetailsDto,AddressDto and RolesDto and add below code.
@Data
@JsonPropertyOrder({"name","email","mobile","address","roles"})
public class UserDetailsDto {
private String name;
private String email;
private String mobile;
private List<AddressDto> address;
private List<RolesDto> roles;
}

@Data
public class AddressDto {
private String city;
private String pincode;
}

@Data
public class RolesDto {
private String name;
}

 public List<UserDetailsDto> getAllByDto() {
List<UserDetails> userDetailsList= userDetailsRepository.findAll();
List<UserDetailsDto> userDetailsDtoList=new ArrayList<>();
userDetailsList.forEach(userDetails -> {
UserDetailsDto userDetailsDto=new UserDetailsDto();
BeanUtils.copyProperties(userDetails,userDetailsDto);
List<AddressDto> addressDtoList=new ArrayList<>();
userDetails.getAddress().forEach(address -> {
AddressDto addressDto=new AddressDto();
BeanUtils.copyProperties(address,addressDto);
addressDtoList.add(addressDto);
});
userDetailsDto.setAddress(addressDtoList);
List<RolesDto> rolesDtoList=new ArrayList<>();
userDetails.getRoles().forEach(roles -> {
RolesDto rolesDto=new RolesDto();
BeanUtils.copyProperties(roles,rolesDto);
rolesDtoList.add(rolesDto);
});
userDetailsDto.setRoles(rolesDtoList);
userDetailsDtoList.add(userDetailsDto);
});
return userDetailsDtoList;
}

Project Details :

In the context of a User Management System, let’s consider three entities: UserDetails, Roles, and Address. Within this project, I have developed a set of five APIs to facilitate various functionalities.

  1. Create API for the user.
  2. Fetching All Users with Entity.
  3. Retrieving user details, including specific fields of an entity, can be accomplished by utilizing the “Spring Data JPA Projections” technique.
  4. Retrieving user details, including specific fields of an entity, can be accomplished by utilizing the “DTO (Data Transfer Object) Projection” technique.
  5. Retrieving user details, including specific fields of an entity, can be accomplished by utilizing the “JPQL (Java Persistence Query Language) Queries” technique.
@RequestMapping(value = "/user")
@RestController
public class UserController {

private final UserService userService;

@Autowired
public UserController(UserService userService) {
this.userService = userService;
}

@PostMapping(value = "/create")
public UserDetails createUser(@RequestBody UserDetails userDetails) {
return userService.createUser(userDetails);
}

@GetMapping(value = "/getAll")
public List<UserDetails> getAllUsers(){
return userService.getAllUsers();
}

@GetMapping(value = "/getAllByProjection")
public List<UserDetailsProjection> getAllByProjection(){
return userService.getAllUsersByProjection();
}
@GetMapping(value = "/getAllByDto")
public List<UserDetailsDto> getAllByDto(){
return userService.getAllByDto();
}

@GetMapping(value = "/getAllByJPQL")
public List<UserDetailsProjectionDTO> getAllByJPQL(){
return userService.getAllByJPQL();
}
}

Below are some PROS and CONS of spring data projection techniques through which you can find out which technique you can use according to your situation.

JPQL Queries:
Pros:

  1. Fine-grained Control: JPQL queries offer precise control over the fields to be selected, allowing developers to fetch exactly what they need.
  2. Flexibility: Developers can leverage the full power of JPQL, including joins, aggregates, and complex filtering conditions.
  3. Performance Optimization: By selecting only necessary fields, JPQL queries can optimize database performance by reducing the amount of data transferred.

Cons :

  1. Syntax Complexity: Writing JPQL queries can be more complex and verbose compared to other projection techniques, especially for developers less familiar with SQL-like syntax.
  2. Potential for Typos and Errors: Due to the dynamic nature of JPQL queries, there’s a higher likelihood of typos or errors in query construction, leading to runtime issues.

Spring Data JPA Projections:

Pros :

  1. Type Safety: Spring Data JPA projections provide type-safe interfaces or classes for representing projected data, reducing the risk of runtime errors.
  2. Simplicity: Projections can be defined as interfaces or classes with getter methods, making them easy to understand and maintain.
  3. Integrated with Repository: Projections can be seamlessly integrated into repository methods, allowing for clean and concise query definitions.
  4. Reduced Boilerplate Code: Projections eliminate the need for manually mapping query results to DTOs or other data structures, reducing boilerplate code.

Cons:

  1. Limited Expressiveness: When it comes to complex query constructs, JPQL queries have broader support in comparison to projections, which could limit their usefulness in certain situations.
  2. Static Definition: To create projections, it is necessary to have pre-defined interfaces. However, this approach might not be very flexible for dynamic projections.

DTO Projection:

Pros:

  1. Flexibility: DTO projection allows developers to define custom data transfer objects tailored to specific use cases, providing maximum flexibility.
  2. Serialization Control: DTOs enable fine-grained control over the data to be serialized and transmitted over the network, improving performance and reducing bandwidth usage.
  3. Encapsulation: DTOs encapsulate projected data, promoting better encapsulation and separation of concerns within the application architecture.
  4. Reusability: DTOs can be reused across multiple queries or use cases, promoting code reuse and maintainability.

Cons:

  1. Additional Mapping Overhead: DTO projection requires additional mapping code to transform entity objects into DTOs, potentially introducing overhead, especially for complex mappings.
  2. Manual Synchronization: Developers must ensure synchronization between entity attributes and DTO fields, which can be cumbersome and error-prone, especially in large projects.
  3. Boilerplate Code: DTOs often require writing additional boilerplate code for defining classes, constructors, getters, and setters, increasing development effort and verbosity.

Project Souce Code :

https://github.com/ankit25496kumar/data-projection

Reference :

https://medium.com/@ankit25496kumar/understanding-data-projection-in-spring-boot-with-hibernate-23a581a13781

FOUND THIS USEFUL? SHARE IT

Leave a Reply

Your email address will not be published. Required fields are marked *