Lorsque j'essaie de convertir un objet JPA qui a une association bidirectionnelle en JSON, je continue
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError)
Tout ce que j'ai trouvé, c'est ce fil qui se résume essentiellement à recommander d'éviter les associations bidirectionnelles. Quelqu'un at-il une idée d'une solution de contournement pour ce bug de printemps?
------ EDIT 2010-07-24 16:26:22 -------
Extraits de code:
Objet métier 1:
@Entity
@Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
public class Trainee extends BusinessObject {
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
@Column(name = "id", nullable = false)
private Integer id;
@Column(name = "name", nullable = true)
private String name;
@Column(name = "surname", nullable = true)
private String surname;
@OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Column(nullable = true)
private Set<BodyStat> bodyStats;
@OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Column(nullable = true)
private Set<Training> trainings;
@OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Column(nullable = true)
private Set<ExerciseType> exerciseTypes;
public Trainee() {
super();
}
... getters/setters ...
Objet métier 2:
import javax.persistence.*;
import java.util.Date;
@Entity
@Table(name = "ta_bodystat", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
public class BodyStat extends BusinessObject {
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
@Column(name = "id", nullable = false)
private Integer id;
@Column(name = "height", nullable = true)
private Float height;
@Column(name = "measuretime", nullable = false)
@Temporal(TemporalType.TIMESTAMP)
private Date measureTime;
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name="trainee_fk")
private Trainee trainee;
Manette:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Controller
@RequestMapping(value = "/trainees")
public class TraineesController {
final Logger logger = LoggerFactory.getLogger(TraineesController.class);
private Map<Long, Trainee> trainees = new ConcurrentHashMap<Long, Trainee>();
@Autowired
private ITraineeDAO traineeDAO;
/**
* Return json repres. of all trainees
*/
@RequestMapping(value = "/getAllTrainees", method = RequestMethod.GET)
@ResponseBody
public Collection getAllTrainees() {
Collection allTrainees = this.traineeDAO.getAll();
this.logger.debug("A total of " + allTrainees.size() + " trainees was read from db");
return allTrainees;
}
}
Mise en œuvre JPA du stagiaire DAO:
@Repository
@Transactional
public class TraineeDAO implements ITraineeDAO {
@PersistenceContext
private EntityManager em;
@Transactional
public Trainee save(Trainee trainee) {
em.persist(trainee);
return trainee;
}
@Transactional(readOnly = true)
public Collection getAll() {
return (Collection) em.createQuery("SELECT t FROM Trainee t").getResultList();
}
}
persistence.xml
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="RDBMS" transaction-type="RESOURCE_LOCAL">
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="hibernate.hbm2ddl.auto" value="validate"/>
<property name="hibernate.archive.autodetection" value="class"/>
<property name="dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
<!-- <property name="dialect" value="org.hibernate.dialect.HSQLDialect"/> -->
</properties>
</persistence-unit>
</persistence>
@Transient
àTrainee.bodyStats
.@JsonIgnoreProperties
c'est la solution la plus propre. Consultez la réponse de Zammel AlaaEddine pour plus de détails.Réponses:
Vous pouvez utiliser
@JsonIgnore
pour briser le cycle.la source
@JsonIgnore
vous annulerez la "clé étrangère" lorsque vous mettrez à jour l'entité ...JsonIgnoreProperties [mise à jour 2017]:
Vous pouvez désormais utiliser JsonIgnoreProperties pour supprimer la sérialisation des propriétés (pendant la sérialisation) ou ignorer le traitement des propriétés JSON lues (pendant la désérialisation) . Si ce n'est pas ce que vous recherchez, veuillez continuer à lire ci-dessous.
(Merci à As Zammel AlaaEddine de l'avoir signalé).
JsonManagedReference et JsonBackReference
Depuis Jackson 1.6, vous pouvez utiliser deux annotations pour résoudre le problème de récursion infinie sans ignorer les getters / setters pendant la sérialisation:
@JsonManagedReference
et@JsonBackReference
.Explication
Pour que Jackson fonctionne correctement, l'un des deux côtés de la relation ne doit pas être sérialisé, afin d'éviter la boucle infite qui provoque votre erreur de stackoverflow.
Ainsi, Jackson prend la partie avant de la référence (votre cours
Set<BodyStat> bodyStats
en classe stagiaire) et la convertit dans un format de stockage de type json; c'est ce que l'on appelle le processus de triage . Ensuite, Jackson recherche la partie arrière de la référence (c'est-Trainee trainee
à- dire dans la classe BodyStat) et la laisse telle quelle, sans la sérialiser. Cette partie de la relation sera reconstruite lors de la désérialisation ( dé-marshalling ) de la référence directe.Vous pouvez changer votre code comme ceci (je saute les parties inutiles):
Objet métier 1:
Objet métier 2:
Maintenant, tout devrait fonctionner correctement.
Si vous voulez plus d'informations, j'ai écrit un article sur les problèmes de Json et Jackson Stackoverflow sur Keenformatics , mon blog.
ÉDITER:
Une autre annotation utile que vous pouvez vérifier est @JsonIdentityInfo : en l'utilisant, chaque fois que Jackson sérialise votre objet, il lui ajoutera un ID (ou un autre attribut de votre choix), de sorte qu'il ne le "scannera" pas à chaque fois. Cela peut être utile lorsque vous avez une boucle de chaîne entre des objets plus interdépendants (par exemple: Order -> OrderLine -> User -> Order et encore).
Dans ce cas, vous devez être prudent, car vous pourriez avoir besoin de lire les attributs de votre objet plusieurs fois (par exemple dans une liste de produits avec plus de produits qui partagent le même vendeur), et cette annotation vous empêche de le faire. Je suggère de toujours regarder les journaux de Firebug pour vérifier la réponse Json et voir ce qui se passe dans votre code.
Sources:
la source
@JsonIgnore
référence.@JsonIgnore
.La nouvelle annotation @JsonIgnoreProperties résout de nombreux problèmes avec les autres options.
Vérifiez le ici. Cela fonctionne exactement comme dans la documentation:
http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html
la source
Vous pouvez également utiliser Jackson 2.0+
@JsonIdentityInfo
. Cela a fonctionné beaucoup mieux pour mes cours d'hibernation que@JsonBackReference
et@JsonManagedReference
, ce qui a eu des problèmes pour moi et n'a pas résolu le problème. Ajoutez simplement quelque chose comme:et ça devrait marcher.
la source
@JsonIdentityInfo
dans ma réponse ci-dessus.@JsonIdentityInfo
annotations à mes entités mais cela ne résout pas le problème de récursivité. Seulement@JsonBackReference
et@JsonManagedReference
résout, mais ils suppriment les propriétés mappées de JSON.En outre, Jackson 1.6 prend en charge la gestion des références bidirectionnelles ... ce qui semble être ce que vous recherchez ( cette entrée de blog mentionne également la fonctionnalité)
Et depuis juillet 2011, il existe également " jackson-module-hibernate " qui pourrait aider dans certains aspects de la gestion des objets Hibernate, mais pas nécessairement celui-ci (qui nécessite des annotations).
la source
Maintenant, Jackson prend en charge les cycles d'évitement sans ignorer les champs:
Jackson - sérialisation d'entités avec des relations bidirectionnelles (évitant les cycles)
la source
Cela a parfaitement fonctionné pour moi. Ajoutez l'annotation @JsonIgnore sur la classe enfant où vous mentionnez la référence à la classe parent.
la source
@JsonIgnore
qu'ignore cet attribut d'être récupéré côté client. Et si j'ai besoin de cet attribut avec son enfant (s'il a un enfant)?Il existe maintenant un module Jackson (pour Jackson 2) spécialement conçu pour gérer les problèmes d'initialisation paresseuse d'Hibernate lors de la sérialisation.
https://github.com/FasterXML/jackson-datatype-hibernate
Ajoutez simplement la dépendance (notez qu'il existe différentes dépendances pour Hibernate 3 et Hibernate 4):
puis enregistrez le module lors de l'initialisation de l'ObjectMapper de Jackson:
La documentation n'est actuellement pas géniale. Voir le code Hibernate4Module pour les options disponibles.
la source
Fonctionne bien pour moi Résoudre le problème de récursion Json Infinite lorsque vous travaillez avec Jackson
C'est ce que j'ai fait dans oneToMany et ManyToOne Mapping
la source
@JsonManagedReference
,@JsonBackReference
ne vous donne pas les données associées à@OneToMany
et le@ManyToOne
scénario, également lors de l'utilisation ignore les données d'@JsonIgnoreProperties
entité associées. Comment résoudre ça?Pour moi, la meilleure solution est d'utiliser
@JsonView
et de créer des filtres spécifiques pour chaque scénario. Vous pouvez également utiliser@JsonManagedReference
et@JsonBackReference
, cependant, c'est une solution codée en dur à une seule situation, où le propriétaire fait toujours référence au côté propriétaire, et jamais l'inverse. Si vous avez un autre scénario de sérialisation où vous devez ré-annoter l'attribut différemment, vous ne pourrez pas.Problème
Permet d'utiliser deux classes,
Company
etEmployee
où vous avez une dépendance cyclique entre elles:Et la classe de test qui essaie de sérialiser en utilisant
ObjectMapper
( Spring Boot ):Si vous exécutez ce code, vous obtiendrez:
Solution utilisant `@ JsonView`
@JsonView
vous permet d'utiliser des filtres et de choisir les champs à inclure lors de la sérialisation des objets. Un filtre n'est qu'une référence de classe utilisée comme identifiant. Créons donc d'abord les filtres:N'oubliez pas que les filtres sont des classes factices, juste utilisés pour spécifier les champs avec l'
@JsonView
annotation, vous pouvez donc en créer autant que vous le souhaitez et en avez besoin. Voyons cela en action, mais nous devons d'abord annoter notreCompany
classe:et modifiez le test pour que le sérialiseur utilise la vue:
Maintenant, si vous exécutez ce code, le problème de récursion infinie est résolu, car vous avez explicitement dit que vous souhaitez simplement sérialiser les attributs qui ont été annotés avec
@JsonView(Filter.CompanyData.class)
.Lorsqu'il atteint la référence arrière de la société dans le
Employee
, il vérifie qu'il n'est pas annoté et ignore la sérialisation. Vous disposez également d'une solution puissante et flexible pour choisir les données que vous souhaitez envoyer via vos API REST.Avec Spring, vous pouvez annoter vos méthodes de contrôleurs REST avec le
@JsonView
filtre souhaité et la sérialisation est appliquée de manière transparente à l'objet renvoyé.Voici les importations utilisées au cas où vous auriez besoin de vérifier:
la source
@JsonIgnoreProperties est la réponse.
Utilisez quelque chose comme ça ::
la source
@JsonManagedReference
,@JsonBackReference
ne vous donne pas les données associées à@OneToMany
et le@ManyToOne
scénario, également lors de l'utilisation ignore les données d'@JsonIgnoreProperties
entité associées. Comment résoudre ça?Dans mon cas, il suffisait de changer la relation de:
à:
une autre relation est restée telle qu'elle était:
la source
Assurez-vous d'utiliser com.fasterxml.jackson partout. J'ai passé beaucoup de temps à le découvrir.
Ensuite, utilisez
@JsonManagedReference
et@JsonBackReference
.Enfin, vous pouvez sérialiser votre modèle en JSON:
la source
Vous pouvez utiliser @JsonIgnore , mais cela ignorera les données json accessibles en raison de la relation de clé étrangère. Par conséquent, si vous demandez les données de clé étrangère (la plupart du temps, nous avons besoin), @JsonIgnore ne vous aidera pas. Dans une telle situation, veuillez suivre la solution ci-dessous.
vous obtenez une récursion infinie, car la classe BodyStat fait à nouveau référence à l' objet Trainee
BodyStat
Stagiaire
Par conséquent, vous devez commenter / omettre la partie ci-dessus dans le stagiaire
la source
J'ai également rencontré le même problème. J'ai utilisé
@JsonIdentityInfo
leObjectIdGenerators.PropertyGenerator.class
type de générateur.Voilà ma solution:
la source
Vous devez utiliser @JsonBackReference avec @ManyToOne entity et @JsonManagedReference avec @onetomany contenant des classes d'entités.
la source
vous pouvez utiliser le modèle DTO créer la classe TraineeDTO sans aucune annotation hiberbnate et vous pouvez utiliser jackson mapper pour convertir Trainee en TraineeDTO et bingo le message d'erreur disparaît :)
la source
Si vous ne pouvez pas ignorer la propriété, essayez de modifier la visibilité du champ. Dans notre cas, nous avions encore un ancien code soumettant des entités avec la relation, donc dans mon cas, c'était le correctif:
la source
J'ai eu ce problème, mais je ne voulais pas utiliser d'annotation dans mes entités, j'ai donc résolu en créant un constructeur pour ma classe, ce constructeur ne doit pas avoir de référence aux entités qui référencent cette entité. Disons ce scénario.
Si vous essayez d'envoyer à la vue la classe
B
ouA
avec@ResponseBody
elle peut provoquer une boucle infinie. Vous pouvez écrire un constructeur dans votre classe et créer une requête avec votreentityManager
comme ceci.Ceci est la classe avec le constructeur.
Cependant, comme vous pouvez le constater, dans le constructeur, je n'ai pas fait référence à List bs, car Hibernate ne le permet pas, du moins dans la version 3.6.10 . Enfin , donc quand j'ai besoin pour montrer les deux entités dans une vue, je fais ce qui suit.
L'autre problème avec cette solution est que si vous ajoutez ou supprimez une propriété, vous devez mettre à jour votre constructeur et toutes vos requêtes.
la source
Si vous utilisez Spring Data Rest, le problème peut être résolu en créant des référentiels pour chaque entité impliquée dans les références cycliques.
la source
Je suis un retardataire et c'est déjà un fil si long. Mais j'ai passé quelques heures à essayer de comprendre cela aussi, et je voudrais donner mon cas comme un autre exemple.
J'ai essayé les solutions JsonIgnore, JsonIgnoreProperties et BackReference, mais étrangement, c'était comme si elles n'avaient pas été récupérées.
J'ai utilisé Lombok et j'ai pensé que cela interférait peut-être, car il crée des constructeurs et remplace toString (scie toString dans la pile stackoverflowerror).
Enfin, ce n'était pas la faute de Lombok - j'ai utilisé la génération automatique NetBeans d'entités JPA à partir de tables de base de données, sans trop y réfléchir - et l'une des annotations qui ont été ajoutées aux classes générées était @XmlRootElement. Une fois que je l'ai retiré, tout a commencé à fonctionner. Tant pis.
la source
Le point est de placer le @JsonIgnore dans la méthode setter comme suit. dans mon cas.
Township.java
Village.java
la source