I'm working on a Spring Boot application using JPA and Jackson for serializing entities to JSON. I have three models, two with bidirectional relationships, and I'm encountering an infinite recursion issue when trying to serialize one of the objects.
Here are my models:
Competence.java
@Data @AllArgsConstructor @NoArgsConstructor @Entity
public class Competence {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(unique=true)
private String nom;
}
CompetenceUtilisateur**.java**
//@JsonIdentityInfo(
// generator = ObjectIdGenerators.PropertyGenerator.class,
// property = "id")
@Data @AllArgsConstructor @NoArgsConstructor @Entity
public class CompetenceUtilisateur {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String nom;
@Enumerated(EnumType.STRING)
private Niveau niveau;
private Integer idListDesCompetences;
//============ Relation =============//
@ManyToOne
@JoinColumn(name = "Competence_Id", nullable = false)
private Competence competence;
@ManyToOne
// @JsonBackReference
// @JsonIgnore
private ListCompetencesUtilisateur ListDesCompetences;
//============ Les Methodes =============//
public void setCompetenceDeProjet(ListCompetencesRequise listcompetenceDeProjet) {
this.ListDesCompetencesRequise = listcompetenceDeProjet;
this.idListCompetencesRequise = listcompetenceDeProjet.getId();
}
public void setCompetences(Competence competences) {
this.competences = competences;
this.nom = competences.getNom();
}
}
ListCompetencesUtilisateur .java
@Data @AllArgsConstructor @NoArgsConstructor @Entity
public class ListCompetencesUtilisateur {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Integer idUtilisateur;
//============ Relation =============//
@OneToOne @JsonIgnore
private Utilisateur utilisateur;
@OneToMany( mappedBy = "ListDesCompetences")
// @JsonManagedReference
private List<CompetenceUtilisateur> CompetenceUtilisateur;
//============ Les Methodes =============//
public void setcompetenceDeUtilisateur(Utilisateur utilisateur) {
this.utilisateur = utilisateur;
this.idUtilisateur = utilisateur.getId();
}
}
When I call the REST API to fetch an object of type ListCompetencesUtilisateur
, the response JSON results in an infinite recursion because of the bidirectional relationships.
Here's an example:
{
"id": 1,
"idUtilisateur": 3,
"competenceUtilisateur": [
{
"id": 1,
"nom": "Java",
"listDesCompetences": {
"id": 1,
"idUtilisateur": 3,
"competenceUtilisateur": [
{
"id": 1,
"nom": "Java",
"listDesCompetences": {
...
}
}
]
}
}
]
}
Added @JsonIgnore
on one side of the relationship.
Used @JsonManagedReference
and @JsonBackReference
annotations.
Tried @JsonIdentityInfo
with ObjectIdGenerators.PropertyGenerator.class
It works if I use RestController but the problem is that I use @RepositoryRestResource and I get this error: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid Object Id definition for `xxx.xxxxx.appservice.Entity.ListCompetencesUtilisateur`: cannot find property with name 'id' (through reference chain: org.springframework.hateoas.PagedModel["_embedded"]->java.util.LinkedHashMap["listCompetencesUtilisateurs"]->java.util.ArrayList[0])
None of these solutions resolved the issue. I still get infinite recursion when serializing objects.
Is GraphQL a perfect solution for this problem ?
When fetching an object of type ListCompetencesUtilisateur
, I want to include the list of CompetenceUtilisateur
, but without the nested references that cause an infinite loop.
For example:
{
"id": 1,
"idUtilisateur": 3,
"competenceUtilisateur": [
{
"id": 1,
"nom": "Java",
"competences": {
"id": 1,
"nom": "Java"
}
}
]
}
To decouple the database layer from the web layer in Spring Boot, I primarily use view models in the web layer. View models are lightweight, easy to implement (often as records), and serve as a dedicated representation of data for the client. This separation ensures that the web layer does not directly expose or depend on the structure of the underlying database entities, promoting better maintainability and flexibility.
In this case, three view models should suffice. Note the static methods for converting from entity to view model.
ListCompetencesUtilisateurVm
public record ListCompetencesUtilisateurVm(
Integer id,
Integer idUtilisateur,
List<CompetenceUtilisateurVm> competenceUtilisateur
) {
public static ListCompetencesUtilisateurVm fromListCompetencesUtilisateur(
ListCompetencesUtilisateur source) {
return new ListCompetencesUtilisateurVm(
source.getId(),
source.getIdUtilisateur(),
source.getCompetenceUtilisateur().stream()
.map(CompetenceUtilisateurVm::fromCompetenceUtilisateur)
.toList()
);
}
}
CompetenceUtilisateurVm
public record CompetenceUtilisateurVm(
Integer id,
String nom,
CompetenceVm competence
) {
public static CompetenceUtilisateurVm fromCompetenceUtilisateur(CompetenceUtilisateur source) {
return new CompetenceUtilisateurVm(
source.getId(), source.getNom(),
new CompetenceVm(source.getCompetence().getId(), source.getCompetence().getNom())
);
}
}
CompetenceVm
public record CompetenceVm(
Integer id,
String nom) {
}