Introduction to Spring Data Projections
Spring Data Projections provide a way to customize the query results by selecting only a subset of properties from the entities. This guide covers key concepts and steps for getting started with Spring Data Projections, including defining projections, using projections in repositories, and implementing both interface-based and class-based projections.
Key Concepts of Spring Data Projections
- Projections: Mechanism to retrieve a subset of properties from an entity.
- Interface-based Projections: Define projections using interfaces.
- Class-based Projections: Define projections using classes.
- DTO (Data Transfer Object): An object that carries data between processes.
Defining Interface-based Projections
Create an interface that defines the projection:
Example: UserProjection.java
// UserProjection.java
package com.example.myapp.projections;
public interface UserProjection {
String getUsername();
String getPassword();
}
Using Interface-based Projections in Repositories
Use the projection in your repository interface:
Example: UserRepository.java
// UserRepository.java
package com.example.myapp.repository;
import com.example.myapp.model.User;
import com.example.myapp.projections.UserProjection;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface UserRepository extends JpaRepository {
List findAllProjectedBy();
}
Defining Class-based Projections
Create a class that defines the projection:
Example: UserDTO.java
// UserDTO.java
package com.example.myapp.dto;
public class UserDTO {
private String username;
private String password;
public UserDTO(String username, String password) {
this.username = username;
this.password = password;
}
// Getters and setters
}
Using Class-based Projections in Repositories
Use the projection in your repository interface:
Example: UserRepository.java
// UserRepository.java
package com.example.myapp.repository;
import com.example.myapp.model.User;
import com.example.myapp.dto.UserDTO;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
@Repository
public interface UserRepository extends JpaRepository {
@Query("SELECT new com.example.myapp.dto.UserDTO(u.username, u.password) FROM User u")
List findAllUserDTOs();
}
Using Projections in the Service Layer
Use the projections in your service layer to retrieve and manipulate the data:
Example: UserService.java
// UserService.java
package com.example.myapp.service;
import com.example.myapp.dto.UserDTO;
import com.example.myapp.projections.UserProjection;
import com.example.myapp.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public List getAllUserProjections() {
return userRepository.findAllProjectedBy();
}
public List getAllUserDTOs() {
return userRepository.findAllUserDTOs();
}
}
Testing Spring Data Projections
Test your Spring Data Projections setup to ensure it works as expected:
Example: UserServiceTests.java
// UserServiceTests.java
package com.example.myapp;
import com.example.myapp.dto.UserDTO;
import com.example.myapp.projections.UserProjection;
import com.example.myapp.repository.UserRepository;
import com.example.myapp.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@SpringBootTest
public class UserServiceTests {
@Autowired
private UserService userService;
@MockBean
private UserRepository userRepository;
@Test
public void testGetAllUserProjections() {
UserProjection projection = new UserProjection() {
@Override
public String getUsername() {
return "testuser";
}
@Override
public String getPassword() {
return "password";
}
};
when(userRepository.findAllProjectedBy()).thenReturn(List.of(projection));
List projections = userService.getAllUserProjections();
assertThat(projections).isNotEmpty();
assertThat(projections.get(0).getUsername()).isEqualTo("testuser");
}
@Test
public void testGetAllUserDTOs() {
UserDTO dto = new UserDTO("testuser", "password");
when(userRepository.findAllUserDTOs()).thenReturn(List.of(dto));
List dtos = userService.getAllUserDTOs();
assertThat(dtos).isNotEmpty();
assertThat(dtos.get(0).getUsername()).isEqualTo("testuser");
}
}
Key Points
- Projections: Mechanism to retrieve a subset of properties from an entity.
- Interface-based Projections: Define projections using interfaces.
- Class-based Projections: Define projections using classes.
- DTO (Data Transfer Object): An object that carries data between processes.
- Create an interface that defines the projection for interface-based projections.
- Use the projection in your repository interface for interface-based projections.
- Create a class that defines the projection for class-based projections.
- Use the projection in your repository interface for class-based projections.
- Use the projections in your service layer to retrieve and manipulate the data.
- Test your Spring Data Projections setup to ensure it works as expected.
Conclusion
Spring Data Projections provide a way to customize the query results by selecting only a subset of properties from the entities. By understanding and implementing interface-based and class-based projections, you can effectively manage data in your Spring Boot application. Happy coding!