Voici d'abord mes entités.
Joueur :
@Entity
@JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Player {
// other fields
@ManyToOne
@JoinColumn(name = "pla_fk_n_teamId")
private Team team;
// methods
}
Équipe :
@Entity
@JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Team {
// other fields
@OneToMany(mappedBy = "team")
private List<Player> members;
// methods
}
Comme de nombreux sujets ont déjà été mentionnés, vous pouvez éviter de plusieurs façons StackOverflowExeption dans votre WebService avec Jackson.
C'est cool et tout, mais JPA construit toujours une entité avec une récursion infinie vers une autre entité avant la sérialisation. C'est tout simplement laid et la demande prend beaucoup plus de temps. Vérifiez cette capture d'écran: débogueur IntelliJ
Y a-t-il un moyen de le réparer ? Sachant que je veux des résultats différents selon le point final. Exemples :
Je vous remercie!
EDIT: peut-être que la question n'est pas très claire en donnant les réponses que j'obtiens alors je vais essayer d'être plus précis.
Je sais qu'il est possible d'empêcher la récursion infinie soit avec Jackson (@JSONIgnore, @ JsonManagedReference/@ JSONBackReference etc.) ou en faisant un mappage dans DTO. Le problème que je vois toujours est le suivant: les deux ci-dessus sont un traitement post-requête. L'objet renvoyé par Spring JPA sera toujours (par exemple) une équipe, contenant une liste de joueurs, contenant une équipe, contenant une liste de joueurs, etc. etc.
Je voudrais savoir s'il existe un moyen de dire à JPA ou au référentiel (ou quoi que ce soit) de ne pas lier des entités au sein d'entités encore et encore?
Voici comment je gère ce problème dans mes projets.
J'ai utilisé le concept d'objets de transfert de données, implémenté en deux versions: un objet complet et un objet léger.
Je définis un objet contenant les entités référencées comme List as Dto
(objet de transfert de données qui ne contient que des valeurs sérialisables) et je définis un objet sans les entités référencées comme Info
.
Un objet Info
ne contient que des informations sur l'entité même et non sur les relations.
Maintenant, lorsque je livre un objet Dto
sur une API REST, je mets simplement des objets Info
pour les références.
Supposons que je distribue un PlayerDto
sur GET /players/1
:
public class PlayerDto{
private String playerName;
private String playercountry;
private TeamInfo;
}
Alors que l'objet TeamInfo
ressemble à
public class TeamInfo {
private String teamName;
private String teamColor;
}
par rapport à un TeamDto
public class TeamDto{
private String teamName;
private String teamColor;
private List<PlayerInfo> players;
}
Cela évite une sérialisation sans fin et fait également une fin logique pour vos ressources de repos, sinon vous devriez pouvoir GET /player/1/team/player/1/team
De plus, le concept sépare clairement la couche de données de la couche client (dans ce cas, l'API REST), car vous ne passez pas l'objet réellement entité à l'interface. Pour cela, vous convertissez l'entité réelle à l'intérieur de votre couche de service en Dto
ou Info
. J'utilise http://modelmapper.org/ pour cela, car c'est super facile (un court appel de méthode).
Je récupère également toutes les entités référencées paresseusement . Ma méthode de service qui obtient l'entité et la convertit en Dto
pour les exécutions à l'intérieur d'une portée de transaction, ce qui est de toute façon une bonne pratique.
Pour dire à JPA de récupérer une entité paresseusement, modifiez simplement votre annotation de relation en définissant le type de récupération. La valeur par défaut est fetch = FetchType.EAGER
ce qui dans votre situation est problématique. C'est pourquoi vous devriez le changer en fetch = FetchType.LAZY
public class TeamEntity {
@OneToMany(mappedBy = "team",fetch = FetchType.LAZY)
private List<PlayerEntity> members;
}
De même, le Player
public class PlayerEntity {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "pla_fk_n_teamId")
private TeamEntity team;
}
Lorsque vous appelez votre méthode de référentiel à partir de votre couche de service, il est important que cela se produise dans un @Transactional
scope, sinon, vous ne pourrez pas obtenir l'entité référencée paresseusement. Qui ressemblerait à ceci:
@Transactional(readOnly = true)
public TeamDto getTeamByName(String teamName){
TeamEntity entity= teamRepository.getTeamByName(teamName);
return modelMapper.map(entity,TeamDto.class);
}
Vous pouvez utiliser @ JsonIgnoreProperties annotation pour éviter une boucle infinie, comme ceci:
@JsonIgnoreProperties("members")
private Team team;
ou comme ça:
@JsonIgnoreProperties("team")
private List<Player> members;
ou les deux.
Dans mon cas, j'ai résolu cela en réalisant que je n'avais pas besoin d'une relation OneToMany - ManyToOne (bidirectionnelle).
Quelque chose comme ça a résolu mon problème
// Team Class:
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Player> members = new HashSet<Player>();
// Player Class:
// - I removed the 3 lines
Voici un lien avec plus d'exemples: https://github.com/thombergs/code-examples/tree/master/spring-data/spring-data-rest-associations/src/main/Java/com/example/démo
Project Lombok génère également ce même problème. J'ai essayé @ ToString et @ EqualsAndHashCode pour corriger cela:
par exemple.
@Data
@Entity
@EqualsAndHashCode(exclude = { "members"}) // This,
@ToString(exclude = { "members"}) // and this
public class Team implements Serializable {
// ...
En outre, voici un guide utile sur les annotations empêchant les boucles
https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion