5
votes

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"]
-&gt;$Proxy119[&quot;userName&quot;])</div></body></html>
1
Might help if you share which exception you got? - Erik Pragt

1 Answers

11
votes

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).