Wenn ich versuche, ein JPA-Objekt mit einer bidirektionalen Zuordnung in JSON zu konvertieren, bekomme ich immer wieder
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError)
Alles, was ich gefunden habe, ist dieser Thread, der im Grunde mit der Empfehlung endet, bidirektionale Assoziationen zu vermeiden. Hat jemand eine Idee für eine Problemumgehung für diesen Frühlingsfehler?
------ EDIT 2010-07-24 16:26:22 -------
Code Ausschnitte:
Geschäftsobjekt 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 ...
Geschäftsobjekt 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;
Regler:
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;
}
}
JPA-Implementierung des Trainee 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
zuTrainee.bodyStats
.@JsonIgnoreProperties
ist die sauberste Lösung. Weitere Informationen finden Sie in der Antwort von Zammel AlaaEddine .Antworten:
Sie können verwenden
@JsonIgnore
, um den Zyklus zu unterbrechen.quelle
@JsonIgnore
Sie in "Fremdschlüssel" null, wenn Sie die Entität aktualisieren ...JsonIgnoreProperties [Update 2017]:
Sie können jetzt JsonIgnoreProperties verwenden , um die Serialisierung von Eigenschaften (während der Serialisierung) zu unterdrücken oder die Verarbeitung der gelesenen JSON-Eigenschaften (während der Deserialisierung) zu ignorieren . Wenn dies nicht das ist, wonach Sie suchen, lesen Sie bitte weiter unten.
(Danke an As Zammel AlaaEddine für den Hinweis).
JsonManagedReference und JsonBackReference
Seit Jackson 1.6 können Sie zwei Annotationen verwenden, um das Problem der unendlichen Rekursion zu lösen, ohne die Getter / Setter während der Serialisierung zu ignorieren:
@JsonManagedReference
und@JsonBackReference
.Erläuterung
Damit Jackson gut funktioniert, sollte eine der beiden Seiten der Beziehung nicht serialisiert werden, um die Infite-Schleife zu vermeiden, die Ihren Stackoverflow-Fehler verursacht.
Also nimmt Jackson den vorwärts gerichteten Teil der Referenz (Ihre
Set<BodyStat> bodyStats
in der Trainee-Klasse) und konvertiert sie in ein json-ähnliches Speicherformat. Dies ist der sogenannte Marshalling- Prozess. Dann sucht Jackson nach dem hinteren Teil der Referenz (dhTrainee trainee
in der BodyStat-Klasse) und lässt sie unverändert, ohne sie zu serialisieren. Dieser Teil der Beziehung wird während der Deserialisierung ( Unmarshalling ) der Vorwärtsreferenz neu konstruiert .Sie können Ihren Code folgendermaßen ändern (ich überspringe die nutzlosen Teile):
Geschäftsobjekt 1:
Geschäftsobjekt 2:
Jetzt sollte alles richtig funktionieren.
Wenn Sie weitere Informationen wünschen, habe ich einen Artikel über Probleme mit Json und Jackson Stackoverflow in Keenformatics , meinem Blog, geschrieben.
BEARBEITEN:
Eine weitere nützliche Anmerkung, die Sie überprüfen können, ist @JsonIdentityInfo : Wenn Sie sie verwenden, wird jedes Mal , wenn Jackson Ihr Objekt serialisiert, eine ID (oder ein anderes Attribut Ihrer Wahl) hinzugefügt, sodass es nicht jedes Mal vollständig "gescannt" wird. Dies kann nützlich sein, wenn Sie eine Kettenschleife zwischen mehr miteinander verbundenen Objekten haben (zum Beispiel: Order -> OrderLine -> User -> Order und noch einmal).
In diesem Fall müssen Sie vorsichtig sein, da Sie die Attribute Ihres Objekts möglicherweise mehrmals lesen müssen (z. B. in einer Produktliste mit mehr Produkten, die denselben Verkäufer haben), und diese Anmerkung verhindert dies. Ich schlage vor, immer einen Blick auf die Firebug-Protokolle zu werfen, um die Json-Antwort zu überprüfen und festzustellen, was in Ihrem Code vor sich geht.
Quellen:
quelle
@JsonIgnore
Referenz.@JsonIgnore
.Die neue Anmerkung @JsonIgnoreProperties behebt viele Probleme mit den anderen Optionen.
Schau es dir hier an. Es funktioniert genau wie in der Dokumentation:
http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html
quelle
Mit Jackson 2.0+ können Sie auch verwenden
@JsonIdentityInfo
. Dies funktionierte für meine Winterschlafklassen viel besser als@JsonBackReference
und@JsonManagedReference
, was Probleme für mich hatte und das Problem nicht löste. Fügen Sie einfach etwas hinzu wie:und es sollte funktionieren.
quelle
@JsonIdentityInfo
meiner obigen Antwort hinzugefügt .@JsonIdentityInfo
meinen Entitäten Anmerkungen hinzugefügt , aber das Rekursionsproblem wird dadurch nicht gelöst. Nur@JsonBackReference
und@JsonManagedReference
löst, aber sie entfernen zugeordnete Eigenschaften aus JSON.Außerdem unterstützt Jackson 1.6 den Umgang mit bidirektionalen Referenzen ... was genau das ist, wonach Sie suchen (in diesem Blogeintrag wird auch die Funktion erwähnt).
Und ab Juli 2011 gibt es auch " jackson-module-hibernate ", das bei einigen Aspekten des Umgangs mit Hibernate-Objekten hilfreich sein kann, obwohl dies nicht unbedingt der Fall ist (für den Anmerkungen erforderlich sind).
quelle
Jetzt unterstützt Jackson das Vermeiden von Zyklen, ohne die Felder zu ignorieren:
Jackson - Serialisierung von Entitäten mit birektionalen Beziehungen (Vermeidung von Zyklen)
quelle
Das hat bei mir sehr gut funktioniert. Fügen Sie der untergeordneten Klasse, in der Sie den Verweis auf die übergeordnete Klasse erwähnen, die Anmerkung @JsonIgnore hinzu.
quelle
@JsonIgnore
ignoriert dieses Attribut, wenn es auf die Clientseite abgerufen wird. Was ist, wenn ich dieses Attribut mit seinem Kind brauche (wenn es ein Kind hat)?Es gibt jetzt ein Jackson-Modul (für Jackson 2), das speziell für Probleme mit der verzögerten Initialisierung im Ruhezustand bei der Serialisierung entwickelt wurde.
https://github.com/FasterXML/jackson-datatype-hibernate
Fügen Sie einfach die Abhängigkeit hinzu (beachten Sie, dass es für Hibernate 3 und Hibernate 4 unterschiedliche Abhängigkeiten gibt):
und registrieren Sie dann das Modul, wenn Sie Jacksons ObjectMapper initialisieren:
Die Dokumentation ist derzeit nicht großartig. Die verfügbaren Optionen finden Sie im Hibernate4Module-Code .
quelle
Funktioniert gut für mich Beheben Sie das Problem mit Json Infinite Recursion, wenn Sie mit Jackson arbeiten
Dies habe ich in oneToMany und ManyToOne Mapping getan
quelle
@JsonManagedReference
,@JsonBackReference
gibt Ihnen nicht die Daten im Zusammenhang mit@OneToMany
und@ManyToOne
Szenario, auch bei der Verwendung von@JsonIgnoreProperties
nicht überspringt zugehörige Objektdaten. Wie kann man das lösen?Für mich ist die beste Lösung
@JsonView
, für jedes Szenario spezifische Filter zu verwenden und zu erstellen. Sie könnten auch@JsonManagedReference
und verwenden@JsonBackReference
es ist jedoch eine fest codierte Lösung für nur eine Situation, in der der Eigentümer immer auf die Besitzerseite verweist und niemals auf das Gegenteil. Wenn Sie ein anderes Serialisierungsszenario haben, in dem Sie das Attribut anders neu kommentieren müssen, können Sie dies nicht.Problem
Hier können zwei Klassen verwenden,
Company
undEmployee
wo Sie eine zyklische Abhängigkeit zwischen ihnen:Und die Testklasse, die versucht, mit
ObjectMapper
( Spring Boot ) zu serialisieren :Wenn Sie diesen Code ausführen, erhalten Sie Folgendes:
Lösung mit `@ JsonView`
@JsonView
Mit dieser Option können Sie Filter verwenden und auswählen, welche Felder beim Serialisieren der Objekte enthalten sein sollen. Ein Filter ist nur eine Klassenreferenz, die als Bezeichner verwendet wird. Erstellen wir also zuerst die Filter:Denken Sie daran, dass die Filter Dummy-Klassen sind, die nur zum Angeben der Felder mit der
@JsonView
Anmerkung verwendet werden, sodass Sie so viele erstellen können, wie Sie möchten und benötigen. Lassen Sie es uns in Aktion sehen, aber zuerst müssen wir unsereCompany
Klasse kommentieren :und ändern Sie den Test, damit der Serializer die Ansicht verwenden kann:
Wenn Sie diesen Code ausführen, ist das Problem der unendlichen Rekursion gelöst, da Sie ausdrücklich gesagt haben, dass Sie nur die Attribute serialisieren möchten, mit denen Anmerkungen versehen wurden
@JsonView(Filter.CompanyData.class)
.Wenn es die Rückreferenz für das Unternehmen in der erreicht
Employee
, prüft es, ob es nicht mit Anmerkungen versehen ist, und ignoriert die Serialisierung. Sie haben auch eine leistungsstarke und flexible Lösung, mit der Sie auswählen können, welche Daten Sie über Ihre REST-APIs senden möchten.Mit Spring können Sie Ihre REST-Controller-Methoden mit dem gewünschten
@JsonView
Filter versehen und die Serialisierung wird transparent auf das zurückkehrende Objekt angewendet.Hier sind die Importe, die für den Fall verwendet werden, dass Sie überprüfen müssen:
quelle
@JsonIgnoreProperties ist die Antwort.
Verwenden Sie so etwas ::
quelle
@JsonManagedReference
,@JsonBackReference
gibt Ihnen nicht die Daten im Zusammenhang mit@OneToMany
und@ManyToOne
Szenario, auch bei der Verwendung von@JsonIgnoreProperties
nicht überspringt zugehörige Objektdaten. Wie kann man das lösen?In meinem Fall hat es gereicht, die Beziehung zu ändern von:
zu:
eine andere Beziehung blieb wie sie war:
quelle
Stellen Sie sicher, dass Sie überall com.fasterxml.jackson verwenden . Ich habe viel Zeit damit verbracht, es herauszufinden.
Dann benutze
@JsonManagedReference
und@JsonBackReference
.Schließlich können Sie Ihr Modell in JSON serialisieren:
quelle
Sie können @JsonIgnore verwenden , dies ignoriert jedoch die JSON-Daten, auf die aufgrund der Fremdschlüsselbeziehung zugegriffen werden kann. Wenn Sie daher die Fremdschlüsseldaten benötigen (die meiste Zeit, die wir benötigen), dann @JsonIgnore hilft Ihnen nicht weiter. In einer solchen Situation folgen Sie bitte der folgenden Lösung.
Sie erhalten eine unendliche Rekursion, da die BodyStat- Klasse erneut auf das Trainee- Objekt verweist
BodyStat
Auszubildender
Daher müssen Sie den obigen Teil in Trainee kommentieren / weglassen
quelle
Ich habe auch das gleiche Problem getroffen. Ich habe
@JsonIdentityInfo
denObjectIdGenerators.PropertyGenerator.class
Generatortyp verwendet.Das ist meine Lösung:
quelle
Sie sollten @JsonBackReference mit der Entität @ManyToOne und @JsonManagedReference mit der Entität @onetomany verwenden, die Entitätsklassen enthält.
quelle
Sie können die DTO-Mustererstellungsklasse TraineeDTO ohne Anotation Hiberbnate verwenden und Sie können Jackson Mapper verwenden, um Trainee in TraineeDTO zu konvertieren und die Fehlermeldung verschwinden zu lassen :)
quelle
Wenn Sie die Eigenschaft nicht ignorieren können, ändern Sie die Sichtbarkeit des Felds. In unserem Fall hatten wir noch alten Code, der Entitäten mit der Beziehung übermittelte. In meinem Fall war dies also die Lösung:
quelle
Ich hatte dieses Problem, wollte aber keine Annotation in meinen Entitäten verwenden. Daher habe ich einen Konstruktor für meine Klasse erstellt. Dieser Konstruktor darf keinen Verweis auf die Entitäten haben, die auf diese Entität verweisen. Sagen wir dieses Szenario.
Wenn Sie versuchen , die Klasse zu der Ansicht zu senden
B
oderA
mit@ResponseBody
kann es zu einer Endlosschleife führen. Sie können einen Konstruktor in Ihre Klasse schreiben und eine Abfrage mitentityManager
dieser erstellen .Dies ist die Klasse mit dem Konstruktor.
Wie Sie sehen, gibt es jedoch einige Einschränkungen bei dieser Lösung. Im Konstruktor habe ich keinen Verweis auf List bs erstellt. Dies liegt daran, dass Hibernate dies nicht zulässt, zumindest in Version 3.6.10.Final , also wenn ich es brauche Um beide Entitäten in einer Ansicht anzuzeigen, gehe ich wie folgt vor.
Das andere Problem bei dieser Lösung besteht darin, dass Sie beim Hinzufügen oder Entfernen einer Eigenschaft Ihren Konstruktor und alle Ihre Abfragen aktualisieren müssen.
quelle
Wenn Sie Spring Data Rest verwenden, kann das Problem behoben werden, indem Repositorys für jede Entität erstellt werden, die an zyklischen Referenzen beteiligt ist.
quelle
Ich bin spät dran und es ist schon so ein langer Faden. Aber ich habe ein paar Stunden damit verbracht, dies auch herauszufinden, und möchte meinen Fall als weiteres Beispiel nennen.
Ich habe sowohl JsonIgnore- als auch JsonIgnoreProperties- und BackReference-Lösungen ausprobiert, aber seltsamerweise war es so, als würden sie nicht abgeholt.
Ich habe Lombok verwendet und dachte, dass es möglicherweise stört, da es Konstruktoren erstellt und toString überschreibt (saw toString im Stackoverflowerror-Stack).
Schließlich war es nicht Lomboks Schuld - ich habe die automatische NetBeans-Generierung von JPA-Entitäten aus Datenbanktabellen verwendet, ohne viel darüber nachzudenken - und eine der Anmerkungen, die den generierten Klassen hinzugefügt wurden, war @XmlRootElement. Sobald ich es entfernt hatte, fing alles an zu funktionieren. Naja.
quelle
Der Punkt ist, den @JsonIgnore wie folgt in die Setter-Methode zu platzieren. in meinem Fall.
Township.java
Village.java
quelle