1

I'm working in a project using Spring Boot 2.0, Hibernate and Spring Data REST. FrontEnd with React.
I've the situation where a User can be linked with several Companies (he owns more than one Company).
When I try to get some of the entities, using the UserRepository or CompanyRepository, I get the error: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError).

I have to use Projections to limit the data going to the FrontEnd and because I need the links to the entities, auto generated by Projections.

Follow the entities:

@Entity
public class User implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id_user")
    protected Long id;

    @OneToMany(cascade= { CascadeType.MERGE }, fetch = FetchType.EAGER, mappedBy="user")
    private List<Company> companyList;

    // Other data
    // Getters and Setters
}

@Entity
public class Company extends CadastroEmpresaUnica {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id_company")
    protected Long id;

    @ManyToOne(cascade= { CascadeType.MERGE })
    @JoinColumn(name="id_user", nullable = false)
    private User user;

    // Other data
    // Getters and Setters
}

The Projections:

@Projection(name = "userProjection", types = { User.class })
public interface UserProjection {

    List<CompanyProjection> getCompanyList();

    // Other Getters
}

@Projection(name = "companyProjection", types = { Company.class }) 
public interface CompanyProjection {
    UserProjection getUser();

    // Other Getters
}

One of the Repositories we are using:

@RepositoryRestResource(collectionResourceRel = "company", path = "companies", excerptProjection = CompanyProjection.class)
public interface CompanyRepository extends PagingAndSortingRepository<Company, Long>, CompanyRepositoryCustom, JpaSpecificationExecutor<Company> {}

Searching about bidirectional infinite recursion I found content about '@JsonManagedReference' and '@JsonBackReference', always been used directly in the Entities. So I tried to use in my Projections and it worked. So it resolve my problem of infinite recursion, but it generates another problem, I cant access my User from my Company (because apparently '@JsonBackReference' don't get it to stop the recursion).
Here is my Projections with this solution:

@Projection(name = "userProjection", types = { User.class })
public interface UserProjection {
    @JsonManagedReference
    List<CompanyProjection> getCompanyList();

    // Other Getters
}

@Projection(name = "companyProjection", types = { Company.class }) 
public interface CompanyProjection {
    @JsonBackReference
    UserProjection getUser();

    // Other Getters
}

Searching a little more I read about '@JsonIdentityInfo', again, been used in the Entity. So I tried to remove the other Json annotations and use '@JsonIdentityInfo' in my Projection. As the following examples:

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
@Projection(name = "userProjection", types = { User.class })
public interface UserProjection {
    Long getId();
    List<CompanyProjection> getCompanyList();

    // Other Getters
}

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
@Projection(name = "companyProjection", types = { Company.class }) 
public interface CompanyProjection {
    Long getId();
    UserProjection getUser();

    // Other Getters
}

It didn't work. Now the Json infinite recursion is happening again.
I am new with Spring Data REST and I am really trying to understand better Projections with Spring Data Rest, reading the Spring documentation and Stackoverflow topics. I would like to know what I'm doing wrong and, of course, if I'm using Projections the wrong way, but I need to go ahead with this project.

Robson Farias
  • 151
  • 1
  • 3
  • 14

3 Answers3

1

Firstly SDR does not like a bidirectional relation ))

To workaround this situation try to add to the User getter in the Company entity the following:

@RestResource(exported = false)
@JsonIgnore
public User getUser() {...}

Also you can try to use annotation @JsonIgnoreProperties.

Besides I think that if you need to get only the user's companies (and the company's user) you doesn't need to use projections. Associated resources in SDR are exported like this (in your case):

/users/{id}/companies
/companies/{id}/users
Cepr0
  • 28,144
  • 8
  • 75
  • 101
  • 1
    I'd already read this answer of Oliver about bidirectional relation. The problem is that sometimes it's needed. @JsonIgnore is not useful as I've explained, we need to access the User data in Company. About use Projections, we start using it since we saw that it comes with the data that we need AND the links. It seems very useful for our single page application, since we need just one acess to the backend to get all data. On trying to get the Company entity, for example, without Proj., it comes with a link to the User, so we need another get to get User data. (PS: Sorry for English errors). – Robson Farias Apr 13 '18 at 19:50
  • By the way, @JsonIgnoreProperties seems not to work with projections. Other annotations work well, but this tag seems to be ignored by Spring when serializing. – Robson Farias Apr 25 '18 at 12:10
  • Upvote for the annotation @JsonIgnoreProperties, that, if it was working, it would be the best way. – Robson Farias Apr 26 '18 at 13:40
1

A bit ugly, but simple solution could be one more projection (for example CompanyWithoutUserProjection) which could stop your recursion.

CompanyProjection {
  UserProjection getUser();
  //other getters
}


UserProjection {
  List<CompanyWithoutUserProjection> getCompanyList();
  //other getters
}


CompanyWithoutUserProjection {
  //other getters
}
  • This is a way to go, but this is just one of the entities that have this problem. For a bigger database this solution is not a good idea, because it can become a monster and very difficult to manage. – Robson Farias Apr 17 '18 at 11:28
  • Upvote because, while the annotation @JsonIgnoreProperties doesn't work for Projections with Spring Data REST, we had to use a similar solution to solve the situation, temporarily, as described in my answer. :) – Robson Farias Apr 26 '18 at 13:43
1

The best way we find would be with the Jackson annotation @JsonIgnoreProperties, that should be used in parent list to igore himself in the child. But, after a few tries, seems like this annotation is not working in projections, specifically for Spring Data REST.
Follow the example of what would be the correct way:

@Projection(name = "userProjection", types = { User.class })
public interface UserProjection {
    @JsonIgnoreProperties({"user"})
    List<CompanyProjection> getCompanyList();

    // Other Getters
}

@Projection(name = "companyProjection", types = { Company.class }) 
public interface CompanyProjection {
    UserProjection getUser();

    // Other Getters
}

We send a ticket for this Spring Data REST issue and it has been accepted. We believe in a near future it will be corrected and we can use it.
For now, we adjust our projections so the list object can use a "derivation" of the original projection, ignoring the property that causes the infinite recursion.
Follow the example:

@Projection(name = "userProjection", types = { User.class })
public interface UserProjection {
    List<CompanyProjectionWithoutUser> getCompanyList();

    // Other Getters

    // Projection without the User, that couses infinite recursion
    public interface CompanyProjectionWithoutUser extends CompanyProjection {
        @Override
        @JsonIgnore
        UserProjection getUser();
    }
}

@Projection(name = "companyProjection", types = { Company.class }) 
public interface CompanyProjection {
    UserProjection getUser();

    // Other Getters
}
GuiRitter
  • 697
  • 1
  • 11
  • 20
Robson Farias
  • 151
  • 1
  • 3
  • 14