Spring Data JPA - Using Projections for Selective Data Retrieval
Using Projections for Selective Data Retrieval
In a typical data-driven application, there are often scenarios where you don’t need to fetch an entire entity with all its attributes. Instead, you might only need a subset of them. Spring Data JPA offers a compelling solution to this challenge with the concept of projections, enabling you to shape the data you retrieve from your database more selectively and efficiently.
What Are Projections?
At its essence, a projection is a reduced view of your data. In Spring Data JPA, projections can be seen as interfaces or DTOs (Data Transfer Objects) that define a contract on which data you wish to retrieve.
Interface-based Projections
The simplest form of projections in Spring Data JPA is through interfaces. By defining an interface that your repository can return, you can selectively retrieve attributes of an entity.
For instance, let’s say you have a User entity with name, email, and dateOfBirth attributes but you only want name and email:
public interface UserNameAndEmail {
String getName();
String getEmail();
}
public interface UserRepository extends JpaRepository<User, Long> {
List<UserNameAndEmail> findByName(String name);
}
Here, the method findByName will return a list of projections containing only the name and email.
Class-based Projections (DTOs)
If you need to use data from various entities or require more complex transformations, DTO projections might be more suitable. These are essentially classes with a set of fields, a constructor that matches the JPQL query, and getters:
public class UserNameAndEmailDto {
private final String name;
private final String email;
public UserNameAndEmailDto(String name, String email) {
this.name = name;
this.email = email;
}
// Getters...
}
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT new com.example.UserNameAndEmailDto(u.name, u.email) FROM User u WHERE u.name = ?1")
List<UserNameAndEmailDto> findByName(String name);
}
Dynamic Projections
One powerful feature of Spring Data JPA projections is the ability to define dynamic projections. This means you can let the client of the repository decide which projection type should be used during runtime:
<T> List<T> findByName(String name, Class<T> type);
By calling this method with different types (interfaces or DTOs), you can retrieve different views of your data without creating separate repository methods.
Benefits of Using Projections
- Performance: By fetching only the data you need, you reduce the overhead of data retrieval and transmission.
- Flexibility: Projections, especially dynamic ones, allow you to adapt to varying data requirements without changing the underlying query.
- Encapsulation: Instead of exposing your entire entity, you expose only what’s necessary, promoting better data encapsulation.
Considerations and Best Practices
- Stay Lean: Always aim to retrieve only the data you need. Fetching unnecessary data can lead to performance inefficiencies.
- Avoid Overusing Dynamic Projections: While they provide great flexibility, they can also make the code harder to read and maintain if used indiscriminately.
- Test: Make sure your projections are doing what you expect. It’s easy for small bugs to slip through, especially when the DTOs get more complicated.
DTO projections
What are they?
DTOs are easy to use and the most efficient projection for read-only operations. So, whenever you don’t need to change the requested information, you should prefer a DTO projection.
What is a DTO?
DTO is an abbreviation that stands for Data Transfer Object. Originally, Martin Fowler defined a DTO in his famous book Patterns of Enterprise Application Architecture as:
An object that carries data between processes in order to reduce the number of method calls. Source: https://martinfowler.com/eaaCatalog/dataTransferObject.html
You can find DTOs in almost all applications. They are used as a specialized class to transfer data that you selected in a database query. Now, the goal of a DTO is to read the required information with as few database queries as possible and to provide it in an efficient and easy to use form.
Why use them?
If you’re building an online book store, you probably have a Book and an Author entity and a many-to-one association between them.
These entities model all information about books and their authors. But when a user searches for a book by its title or author, you don’t need all this information. On the search result page, you probably only want to show the title, price, and the name of the author.
This is where a DTO class comes into play. It’s a simple Java class that you can specifically design for this use case. So, in this example, the BookWithAuthorNames class only has the 4 attributes that are required for the search result page. These are the id, title, and price of the book and a String with the name of the author.
This representation is obviously easier to use and more efficient than a Book entity with a List of associated Author entities. The DTO only contains the required information, and it already concatenated the author’s first and last name into a String.
How do DTO projections work with JPA and Hibernate
Your database and the SQL language don’t know about your Java classes. They only know tables, columns, views, stored procedures, and other database-related concepts.
So, your persistence provider, e.g., Hibernate or EclipseLink, need to handle the DTO projection. It does that when it processes the result set of your query. Instead of mapping each row to an Object[], your persistence provider calls the constructor of your DTO to instantiate a new object. So, you need to make sure that your DTO always has a constructor that matches the columns selected by your query. But more about that later.
Using DTO projections with JPA and Hibernate
After you defined your DTO class, you can use it as a projection with JPQL, criteria and native queries. For each kind of query, you need to define the DTO projection differently, but the result is always the same. Your persistence provider instantiates a new DTO object for each record in the result set.
Reference
- https://docs.spring.io/spring-data/jpa/reference/repositories/projections.html
- https://docs.spring.io/spring-data/relational/reference/repositories/projections.html
- Why, When and How to Use DTO Projections with JPA and Hibernate: https://thorben-janssen.com/dto-projections
- https://thorben-janssen.com/spring-data-jpa-dto-native-queries/
- https://www.baeldung.com/spring-data-jpa-projections