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