Unendliche Rekursion mit Jackson JSON und Hibernate JPA-Problem


411

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>

Hinzufügen @Transientzu Trainee.bodyStats.
Phil294

Ab 2017 @JsonIgnorePropertiesist die sauberste Lösung. Weitere Informationen finden Sie in der Antwort von Zammel AlaaEddine .
Utku

Wie ist die Schuld dieses Frühlings?
Nathan Hughes

Antworten:


293

Sie können verwenden @JsonIgnore, um den Zyklus zu unterbrechen.


1
@ Ben: Eigentlich weiß ich es nicht. Möglicherweise wurde seine Unterstützung nicht aktiviert: wiki.fasterxml.com/JacksonJAXBAnnotations
axtavt

40
Seit Jackson 1.6 gibt es eine bessere Lösung: Sie können zwei neue Annotationen verwenden, um das Problem der unendlichen Rekursion zu lösen, ohne die Getter / Setter während der Serialisierung zu ignorieren. Siehe meine Antwort unten für Details.
Kurt Bourbaki

1
@axtavt Danke für die perfekte Antwort. Übrigens habe ich eine andere Lösung gefunden: Sie können einfach vermeiden, Getter für diesen Wert zu erstellen, sodass Spring beim Erstellen von JSON nicht darauf zugreifen kann (aber ich denke nicht, dass dies für jeden Fall geeignet ist, daher ist Ihre Antwort besser).
Semyon Danilov

11
Alle oben genannten Lösungen müssen die Domänenobjekte durch Hinzufügen von Anmerkungen ändern. Wenn ich Klassen von Drittanbietern serialisiere, kann ich sie nicht ändern. Wie kann ich dieses Problem vermeiden?
Jianwu Chen

2
Diese Lösung funktioniert in einigen Situationen nicht. In der relationalen Datenbank mit jpa, wenn Sie setzen, werden @JsonIgnoreSie in "Fremdschlüssel" null, wenn Sie die Entität aktualisieren ...
schlank

627

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: @JsonManagedReferenceund@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> bodyStatsin 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 (dh Trainee traineein 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:

@Entity
@Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
public class Trainee extends BusinessObject {

    @OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @Column(nullable = true)
    @JsonManagedReference
    private Set<BodyStat> bodyStats;

Geschäftsobjekt 2:

@Entity
@Table(name = "ta_bodystat", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
public class BodyStat extends BusinessObject {

    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name="trainee_fk")
    @JsonBackReference
    private Trainee trainee;

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:


29
Danke für die klare Antwort. Dies ist eine bequemere Lösung als das Zurücksetzen einer @JsonIgnoreReferenz.
Utku Özdemir

3
Dies ist definitiv der richtige Weg. Wenn Sie dies auf der Serverseite so tun, weil Sie Jackson dort verwenden, spielt es keine Rolle, welchen JSON-Mapper Sie auf der Clientseite verwenden, und Sie müssen das untergeordnete Link-Handbuch nicht auf das übergeordnete Link einstellen. Es funktioniert einfach. Danke Kurt
flosk8

1
Schöne, detaillierte Erklärung und definitiv bessere und aussagekräftigere Herangehensweise als @JsonIgnore.
Piotr Nowicki

2
Vielen Dank! Die @JsonIdentityInfo arbeitete für zyklische Referenzen, an denen mehrere Entitäten in vielen überlappenden Schleifen beteiligt waren.
n00b

1
Ich kann das nicht für mein Leben zum Laufen bringen. Ich denke, ich habe ein ziemlich ähnliches Setup, aber ich habe offensichtlich etwas falsch gemacht, da ich nur unendliche Wiederholungsfehler bekommen kann:
SWV

102

Die neue Anmerkung @JsonIgnoreProperties behebt viele Probleme mit den anderen Optionen.

@Entity

public class Material{
   ...    
   @JsonIgnoreProperties("costMaterials")
   private List<Supplier> costSuppliers = new ArrayList<>();
   ...
}

@Entity
public class Supplier{
   ...
   @JsonIgnoreProperties("costSuppliers")
   private List<Material> costMaterials = new ArrayList<>();
   ....
}

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


@tero - Auch bei diesem Ansatz erhalten wir keine Daten, die der Entität zugeordnet sind.
Ash_P

@ PAA HEY PAA Ich denke, das ist mit der Entität verbunden! warum sagst du das ?
Tero17

1
@ tero17 Wie verwaltest du die unendliche Rekursion, wenn du mehr als 2 Klassen hast? Zum Beispiel: Klasse A -> Klasse B -> Klasse C -> Klasse A. Ich habe es mit JsonIgnoreProperties ohne Glück versucht
Villat

@Villat dies ist ein weiteres Problem zu lösen, ich schlage vor, eine neue Nachfrage dafür zu eröffnen.
Tero17

+1 für das Codebeispiel, als Jackson-Neuling war die Verwendung von @JsonIgnoreProperties beim Lesen des JavaDoc
Wecherowski

47

Mit Jackson 2.0+ können Sie auch verwenden @JsonIdentityInfo. Dies funktionierte für meine Winterschlafklassen viel besser als @JsonBackReferenceund @JsonManagedReference, was Probleme für mich hatte und das Problem nicht löste. Fügen Sie einfach etwas hinzu wie:

@Entity
@Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@traineeId")
public class Trainee extends BusinessObject {

@Entity
@Table(name = "ta_bodystat", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@bodyStatId")
public class BodyStat extends BusinessObject {

und es sollte funktionieren.


Können Sie bitte erklären "Das hat viel besser funktioniert"? Gibt es ein Problem mit der verwalteten Referenz?
Utku Özdemir

@ UtkuÖzdemir Ich habe Details zu @JsonIdentityInfomeiner obigen Antwort hinzugefügt .
Kurt Bourbaki

1
Dies ist die beste Lösung, die wir bisher gefunden haben, da die get-Methode bei Verwendung von "@JsonManagedReference" die Werte ohne Stackoverflow-Fehler erfolgreich zurückgegeben hat. Aber als wir versuchten, Daten mit dem Beitrag zu speichern, gab es einen Fehler von 415 (nicht unterstützter Medienfehler) zurück
cuser

1
Ich habe @JsonIdentityInfomeinen Entitäten Anmerkungen hinzugefügt , aber das Rekursionsproblem wird dadurch nicht gelöst. Nur @JsonBackReferenceund @JsonManagedReferencelöst, aber sie entfernen zugeordnete Eigenschaften aus JSON.
Oleg Abrazhaev

19

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


Links sind tot. Haben Sie etwas dagegen, sie zu aktualisieren oder Ihre Antwort zu bearbeiten?
GetMeARemoteJob


9

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.

@ManyToOne
@JoinColumn(name = "ID", nullable = false, updatable = false)
@JsonIgnore
private Member member;

2
Ich denke, @JsonIgnoreignoriert dieses Attribut, wenn es auf die Clientseite abgerufen wird. Was ist, wenn ich dieses Attribut mit seinem Kind brauche (wenn es ein Kind hat)?
Khasan 24-7

6

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):

<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-hibernate4</artifactId>
  <version>2.4.0</version>
</dependency>

und registrieren Sie dann das Modul, wenn Sie Jacksons ObjectMapper initialisieren:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate4Module());

Die Dokumentation ist derzeit nicht großartig. Die verfügbaren Optionen finden Sie im Hibernate4Module-Code .


Das Problem ist dann zu lösen, weil es interessant aussieht. Ich habe das gleiche Problem wie das OP und alle oben genannten Tricks haben nicht funktioniert.
user1567291

6

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

@ManyToOne
@JoinColumn(name="Key")
@JsonBackReference
private LgcyIsp Key;


@OneToMany(mappedBy="LgcyIsp ")
@JsonManagedReference
private List<Safety> safety;

Ich habe Hibernate Mapping in Spring Boot-Anwendung verwendet
Prabu M

Hallo Autor, danke für die netten Tutorials und tollen Beiträge. Allerdings fand ich , dass @JsonManagedReference, @JsonBackReferencegibt Ihnen nicht die Daten im Zusammenhang mit @OneToManyund @ManyToOneSzenario, auch bei der Verwendung von @JsonIgnorePropertiesnicht überspringt zugehörige Objektdaten. Wie kann man das lösen?
Ash_P

5

Für mich ist die beste Lösung @JsonView, für jedes Szenario spezifische Filter zu verwenden und zu erstellen. Sie könnten auch @JsonManagedReferenceund 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, Companyund Employeewo Sie eine zyklische Abhängigkeit zwischen ihnen:

public class Company {

    private Employee employee;

    public Company(Employee employee) {
        this.employee = employee;
    }

    public Employee getEmployee() {
        return employee;
    }
}

public class Employee {

    private Company company;

    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }
}

Und die Testklasse, die versucht, mit ObjectMapper( Spring Boot ) zu serialisieren :

@SpringBootTest
@RunWith(SpringRunner.class)
@Transactional
public class CompanyTest {

    @Autowired
    public ObjectMapper mapper;

    @Test
    public void shouldSaveCompany() throws JsonProcessingException {
        Employee employee = new Employee();
        Company company = new Company(employee);
        employee.setCompany(company);

        String jsonCompany = mapper.writeValueAsString(company);
        System.out.println(jsonCompany);
        assertTrue(true);
    }
}

Wenn Sie diesen Code ausführen, erhalten Sie Folgendes:

org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError)

Lösung mit `@ JsonView`

@JsonViewMit 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:

public class Filter {

    public static interface EmployeeData {};

    public static interface CompanyData extends EmployeeData {};

} 

Denken Sie daran, dass die Filter Dummy-Klassen sind, die nur zum Angeben der Felder mit der @JsonViewAnmerkung 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 unsere CompanyKlasse kommentieren :

public class Company {

    @JsonView(Filter.CompanyData.class)
    private Employee employee;

    public Company(Employee employee) {
        this.employee = employee;
    }

    public Employee getEmployee() {
        return employee;
    }
}

und ändern Sie den Test, damit der Serializer die Ansicht verwenden kann:

@SpringBootTest
@RunWith(SpringRunner.class)
@Transactional
public class CompanyTest {

    @Autowired
    public ObjectMapper mapper;

    @Test
    public void shouldSaveCompany() throws JsonProcessingException {
        Employee employee = new Employee();
        Company company = new Company(employee);
        employee.setCompany(company);

        ObjectWriter writter = mapper.writerWithView(Filter.CompanyData.class);
        String jsonCompany = writter.writeValueAsString(company);

        System.out.println(jsonCompany);
        assertTrue(true);
    }
}

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 @JsonViewFilter 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:

import static org.junit.Assert.assertTrue;

import javax.transaction.Transactional;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;

import com.fasterxml.jackson.annotation.JsonView;

1
Dies ist ein schöner Artikel, der viele alternative Lösungen zur Lösung von Rekursionen erklärt: baeldung.com/…
Hugo Baés

5

@JsonIgnoreProperties ist die Antwort.

Verwenden Sie so etwas ::

@OneToMany(mappedBy = "course",fetch=FetchType.EAGER)
@JsonIgnoreProperties("course")
private Set<Student> students;

Verwenden Sie dies sicher, wie ich gesehen habe. Jhipster verwendet dies in seinem generierten Code
ifelse.codes

Danke für die Antwort. Allerdings fand ich , dass @JsonManagedReference, @JsonBackReferencegibt Ihnen nicht die Daten im Zusammenhang mit @OneToManyund @ManyToOneSzenario, auch bei der Verwendung von @JsonIgnorePropertiesnicht überspringt zugehörige Objektdaten. Wie kann man das lösen?
Ash_P

4

In meinem Fall hat es gereicht, die Beziehung zu ändern von:

@OneToMany(mappedBy = "county")
private List<Town> towns;

zu:

@OneToMany
private List<Town> towns;

eine andere Beziehung blieb wie sie war:

@ManyToOne
@JoinColumn(name = "county_id")
private County county;

2
Ich denke, es ist besser, Kurts Lösung zu verwenden. Weil die JoinColumn-Lösung in nicht referenzierten Daten-Totkörpern enden kann.
Flosk8

1
Dies ist eigentlich das einzige, was mir geholfen hat. Keine anderen Lösungen von oben haben funktioniert. Ich bin mir immer noch nicht sicher warum ...
Deniss M.

4

Stellen Sie sicher, dass Sie überall com.fasterxml.jackson verwenden . Ich habe viel Zeit damit verbracht, es herauszufinden.

<properties>
  <fasterxml.jackson.version>2.9.2</fasterxml.jackson.version>
</properties>

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>${fasterxml.jackson.version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>${fasterxml.jackson.version}</version>
</dependency>

Dann benutze @JsonManagedReferenceund @JsonBackReference.

Schließlich können Sie Ihr Modell in JSON serialisieren:

import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(model);

4

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

@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name="trainee_fk")
private Trainee trainee;

Auszubildender

@OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Column(nullable = true)
private Set<BodyStat> bodyStats;

Daher müssen Sie den obigen Teil in Trainee kommentieren / weglassen


1
In meinem Fall funktioniert es nicht. Könnten Sie bitte einen Blick darauf werfen: github.com/JavaHelper/issue-jackson-boot ?
Ash_P

3

Ich habe auch das gleiche Problem getroffen. Ich habe @JsonIdentityInfoden ObjectIdGenerators.PropertyGenerator.classGeneratortyp verwendet.

Das ist meine Lösung:

@Entity
@Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Trainee extends BusinessObject {
...

2
Die gute Antwort
LAGRIDA

1
Ich würde das gleiche beantworten !!! Schön;)
Felipe Desiderati

3

Sie sollten @JsonBackReference mit der Entität @ManyToOne und @JsonManagedReference mit der Entität @onetomany verwenden, die Entitätsklassen enthält.

@OneToMany(
            mappedBy = "queue_group",fetch = FetchType.LAZY,
            cascade = CascadeType.ALL
        )
    @JsonManagedReference
    private Set<Queue> queues;



@ManyToOne(cascade=CascadeType.ALL)
        @JoinColumn(name = "qid")
       // @JsonIgnore
        @JsonBackReference
        private Queue_group queue_group;

Wenn ich dem Kind eine @ jsonIgnore-Anmerkung hinzufüge. Ich konnte kein übergeordnetes Objekt vom Kind erhalten, wenn ich versuche, das Kind zu nehmen. Warum das übergeordnete Objekt nicht kommt, wird von @ jsonignore ignoriert. Sag mir, wie ich von Kind zu Eltern und von Eltern zu Kind komme.
Kumaresan Perumal

Sie müssen @JsonIgnore nicht verwenden. Verwenden Sie einfach die obigen Anmerkungen und Um Objekte von Eltern und Kind abzurufen, verwenden Sie Getters und Setter. und Jsonignore macht das auch, aber es wird eine unendliche Rekursion erzeugen. Wenn Sie Ihren Code teilen, kann ich überprüfen, warum Sie keine Objekte erhalten. Denn für mich kommen beide.
Shubham

Ich wollte sagen. wenn Sie einen Elternteil nehmen. Der Elternteil sollte mit einem untergeordneten Objekt kommen. bei der Aufnahme eines untergeordneten Objekts. Das Kind sollte mit einem Elternteil kommen. In diesem Szenario funktioniert es nicht. Kannst du mir bitte Helfen?
Kumaresan Perumal

1

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 :)


1

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:

    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private Trainee trainee;

Wenn ich dem Kind eine @ jsonIgnore-Anmerkung hinzufüge. Ich konnte kein übergeordnetes Objekt vom Kind erhalten, wenn ich versuche, das Kind zu nehmen. Warum das übergeordnete Objekt nicht kommt, wird von @ jsonignore ignoriert. Sag mir, wie ich von Kind zu Eltern und von Eltern zu Kind komme.
Kumaresan Perumal

0

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.

public class A{
   private int id;
   private String code;
   private String name;
   private List<B> bs;
}

public class B{
   private int id;
   private String code;
   private String name;
   private A a;
}

Wenn Sie versuchen , die Klasse zu der Ansicht zu senden Boder Amit @ResponseBodykann es zu einer Endlosschleife führen. Sie können einen Konstruktor in Ihre Klasse schreiben und eine Abfrage mit entityManagerdieser erstellen .

"select new A(id, code, name) from A"

Dies ist die Klasse mit dem Konstruktor.

public class A{
   private int id;
   private String code;
   private String name;
   private List<B> bs;

   public A(){
   }

   public A(int id, String code, String name){
      this.id = id;
      this.code = code;
      this.name = name;
   }

}

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.

public A getAById(int id); //THE A id

public List<B> getBsByAId(int idA); //the A id.

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.


0

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.


0

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.


0

Der Punkt ist, den @JsonIgnore wie folgt in die Setter-Methode zu platzieren. in meinem Fall.

Township.java

@Access(AccessType.PROPERTY)
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name="townshipId", nullable=false ,insertable=false, updatable=false)
public List<Village> getVillages() {
    return villages;
}

@JsonIgnore
@Access(AccessType.PROPERTY)
public void setVillages(List<Village> villages) {
    this.villages = villages;
}

Village.java

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "townshipId", insertable=false, updatable=false)
Township township;

@Column(name = "townshipId", nullable=false)
Long townshipId;
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.