Spring Data JPA Interface Projections
In Spring we declare entity/root classes to hold the data we want in our application. And we use repository methods to query data from those entity classes and they usually return one or more instances of your entity class. Now, there may be scenarios where
- You want your repository method to return only a set of fields from the entity class or combine multiple fields from different entity class.
- You do not want to directly use your entity classes in the business logic layer for security reasons.
In such scenarios, projection comes handy. Projection is selecting only some columns from your root class which you need as per your use case.
There are different kinds of projections in Spring Data JPA —
a. Interface based projections
b. Class based projections
c. Dynamic projections
Entity Classes
Let’s define two entity classes — Employee class and an Address class
@Entity
public class Employee {
@Id
private Long employeeId;
private String firstName;
private String lastName;
@OneToOne(mappedBy = "person")
private Address address;
// getters and setters
}
@Entity
public class Address {
@Id
private Long EmployeeId;
@OneToOne
private Employee employee;
private String state;
private String city;
private String street;
private String zipCode;
// getters and setters
}
Now, let’s look at how we can use projections with these entity classes to fit our use cases.
Interface Based Projections
In this type of projection, we create interfaces where we provide the getter methods for only the fields we need as a part of our repository method.
In the background, Spring creates a proxy instance of this projection interface for each entity object and all the calls to proxy are forwarded to the entity object.
Interface based projections can be further divided into two —
- Closed Projections
An entity has a lot of fields but not all of them are useful for all repository use cases. Hence, here we can define the getter methods of the fields that we need for our specific use case. Setter methods for these interfaces will be needed if we need to write unit test cases.
Let’s create a projection interface for Employee class —
public interface EmployeeView {
String getFirstName();
String getLastName();
}
Then, we ‘ll use the EmployeeView interface in the following repository method. It’s easy to see that defining a repository method with a projection interface is pretty much the same as if we would be when using an entity class. The only difference is the return type.
public interface EmployeeRepository extends Repository<Employee, Long> {
List<EmployeeView> getAllEmployees();
}
Now, let’s also take a look at if we create a nested projection. Notice the method that returns the nested projection must have the same name as the method in the root class that returns the related entity.
public interface AddressView {
// ...
String getStreet();
EmployeeView getEmployee();
}
Note that recursive/nested projections only work if we traverse from the owning side to the inverse side (Employee -> Address). If we do it the other way around, the nested projection would be set to null.
2. Open Projections
These projections only differ from closed ones, that in Closed projections all the fields are an exact match of the fields defined in entity classes. While in open projections we can define custom fields for the interface like combining first name and last name into a single field called full name.
public interface PersonView {
// ...
@Value("#{target.firstName + ' ' + target.lastName}")
String getFullName();
}
Please find a summary of other two types of projections below.
Class Based Projections
Instead of using proxies defined by Interface based projections, we can declare projection classes. These are called DTOs. Data Transfer Objects also helps in transferring data between processes and reduces the number of calls between server and client. With class-based approach, we can’t use nested projections.
Dynamic Projections
An entity class may use many projections. Sometimes we may need to use one projection while sometimes we may need another. And in some cases we might need to use the entity class itself. Hence, at some places we are not really sure what all fields we would need and we need something that supports multiple return types. Dynamic projections come into picture.