Lets assume I have the following entities:
@Entity
public class Registration {
@ManyToOne
private Student student;
//getters, setters
}
@Entity
public class Student {
private String id;
private String userName;
private String name;
private String surname;
//getters, setters
}
@Projection(name="minimal", types = {Registration.class, Student.class})
public interface RegistrationProjection {
String getUserName();
Student getStudent();
}
I'm trying to create the following JSON representation, So when I use http://localhost:8080/api/registrations?projection=minimal
I dont need all the user data to come along:
{
"_links": {
"self": {
"href": "http://localhost:8080/api/registrations{?page,size,sort,projection}",
}
},
"_embedded": {
"registrations": [
{
"student": {
"userName": "user1"
}
}
],
"_links": {
"self": {
"href": "http://localhost:8080/api/registrations/1{?projection}",
}
}
}
}
However the Projection I have created I get an exception(it works without the getUserName() statement. Obviously I have defined the interface in the wrong way...but which is the correct way to do something like this?
EDIT: Exception is the following
Invalid property 'userName' of bean class [com.test.Registration]:
Could not find field for property during fallback access! (through reference chain: org.springframework.hateoas.PagedResources["_embedded"]
->java.util.UnmodifiableMap["registrations"]->java.util.ArrayList[0]->org.springframework.data.rest.webmvc.json.["content"]
->$Proxy119["userName"])</div></body></html>
Spring data REST Projection supports projecting only a selected fields from an entity representation. To do that, we can define those specific fields into our @Projection interface. Let's create a custom view of our Student entity with first name and last name fields.
CrudRepository provides CRUD functions. PagingAndSortingRepository provides methods to do pagination and sort records. JpaRepository provides JPA related methods such as flushing the persistence context and delete records in a batch.
Real-world applications should avoid using Spring Data REST because the entities are exposed as RESTful Services. The two most critical considerations in designing a RESTful service are the domain model and the consumers.
A DTO projection is a Java Object that contains the column values that were fetched by a given SQL projection query. The DTO projection can be a POJO (Plain Old Java Object), a JPA Tuple , or a Java Record, and we can fetch all those DTO projection types using Spring Data JPA.
As exception said userName
is not a member of Registration
entity. That why it failed. But I think you already understand that.
First at all if you just want to expose its projection for Registration
you should first change the following line:
@Projection(name="minimal", types = {Registration.class, Student.class})
By setting types = {Registration.class, Student.class}
you asked Spring Data Rest to apply projection on Registration.class
and Student.class
. And that can cause some issue because depending of the type you should not have the same member/methods. In practice types
must share a common ancestor.
Otherwise for the main problem you should try virtual projections the following thing (I didn't try on your sample but it should work, I hope):
@Projection(name="minimal", types = {Registration.class})
public interface RegistrationProjection {
@Value("#{target.getStudent().getUserName()}")
String getUserName();
}
I don't know if you are familiar with SPeL but the previous code just create a getUserName
like this.getStudent().getUserName()
because target
is bound on the instance object (see documentation).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With