Projections in Spring Data JPA is a way to retrieve only specific columns from the database or combine fields from multiple entities into a single DTO. This is particularly useful when you need specific fields from entities.
Here is how you can do projection:
- create JPA entity.
@Entity
@Table(name = "employee")
public class Employee{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private String email;
// getters and setters
}
- create projection interface that defines the fields we want to retrieve.
public interface EmployeeProjection {
String getFirstName();
String getLastName();
}
- Create JPA repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
List<EmployeeProjection> findAllProjectedBy();
}
- Now in service layer you can call this method and it will fetch only firstName and lastName.
@Autowired
private EmployeeRepository repository;
public List<EmployeeProjection> getAllEmployee() {
return repository.findAllProjectedBy();
}
Here is how you can do nested projection:
Lets assume we have a Asset class which has id, assetName and employee object. If we want to fetch assetName from Asset class and firstName, lastName and email from Employee class.
- Create projection interface
public interface EmployeeAssetProjection {
interface EmployeeProjection {
String getFirstName();
String getLastName();
String getEmail();
}
interface AssetProjection {
String getAssetName();
}
EmployeeProjection getEmployee();
AssetProjection getAsset();
}
- modify JPA repository as below
public interface AssetRepository extends JpaRepository<Asset, Long> {
List<EmployeeAssetProjection> findAllProjectedBy();
}
- in service layer call this method
@Service
public class Service {
@Autowired
private AssetRepository assetRepository ;
public List<EmployeeAssetProjection> getAllAssetEmployee() {
return assetRepository.findAllProjectedBy();
}
}
Here is how you can do Dynamic projection:
We can also use dynamic projections to select a subset of fields based on runtime conditions. This is useful when we need to retrieve different fields based on the context of the query.
Lets consider we have an Asset entity with id, assetName, category, price. we want to retrieve either the assetName and category fields or the assetName and price fields based on a runtime condition. We can create a dynamic projection interface as follows:
public interface AssetProjection {
String getAssetName();
default Object getDynamicField() {
return null;
}
}
Modify JPA repository as below:
public interface AssetRepository extends JpaRepository<Asset, Long> {
<T> List<T> findAllProjectedBy(Class<T> type);
}
We can then use this repository method in a service to retrieve the desired fields based on the runtime condition:
@Service
public class AssetService {
@Autowired
private AssetRepository assetRepository ;
public List<AssetProjection> getAllAssetProjections(boolean includeCategory) {
if (includeCategory) {
return assetRepository.findAllProjectedBy(AssetProjectionWithCategory.class);
} else {
return assetRepository.findAllProjectedBy(AssetProjectionWithoutCategory.class);
}
}
private interface AssetProjectionWithCategory extends AssetRepository {
String getCategory();
@Override
default Object getDynamicField() {
return getCategory();
}
}
private interface AssetProjectionWithoutCategory extends AssetRepository {
BigDecimal getPrice();
@Override
default Object getDynamicField() {
return getPrice();
}
}
}
In summary, Spring Data JPA projections allow us to retrieve a subset of data from the database, which can improve performance and reduce the amount of data that needs to be transferred over the network. We can use basic projections to select a subset of fields from a single entity, nested projections to select a subset of fields from related entities, and dynamic projections to select a subset of fields based on runtime conditions.
Other blogs