wake-up-neo.net

Unendliche Rekursion mit Jackson JSON und Hibernate JPA

Beim Versuch, ein JPA-Objekt mit einer bidirektionalen Assoziation in JSON zu konvertieren, erhalte ich immer wieder

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

Alles, was ich gefunden habe, ist dieser Thread , was 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 Trainees 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>
358
Ta Sas

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

258
axtavt

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.

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

Erklärung

Damit Jackson gut funktioniert, sollte eine der beiden Seiten der Beziehung nicht serialisiert werden, um die Endlosschleife zu vermeiden, die Ihren Stackoverflow-Fehler verursacht.

Also nimmt Jackson den vorderen Teil der Referenz (Ihr Set<BodyStat> bodyStats in der Klasse Trainee) und konvertiert es in ein json-ähnliches Speicherformat. Dies ist der sogenannte Rangierprozess . Dann sucht Jackson nach dem hinteren Teil der Referenz (d. H. Trainee trainee in der BodyStat-Klasse) und lasse es so wie es ist, ohne es zu serialisieren. Dieser Teil der Beziehung wird während des Deserialisierens ( Unmarshalling ) der Vorwärtsreferenz wiederhergestellt.

Sie können Ihren Code wie folgt ä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, schrieb ich einen Artikel über Json und Jackson Stackoverflow-Probleme bei Keenformatics , mein Blog.

EDIT:

Eine weitere nützliche Annotation, die Sie überprüfen können, ist @ JsonIdentityInfo : Wenn Jackson Ihr Objekt bei jeder Serialisierung verwendet, fügt er ihm eine ID (oder ein anderes Attribut Ihrer Wahl) hinzu, damit es nicht ganz " scannen "es immer wieder. Dies kann nützlich sein, wenn Sie eine Kettenschleife zwischen mehr miteinander verbundenen Objekten haben (zum Beispiel: Auftrag -> Auftragszeile -> Benutzer -> Auftrag und immer wieder).

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 mehreren Produkten, die denselben Verkäufer haben). Diese Anmerkung verhindert dies. Ich empfehle immer einen Blick auf die Firebug-Protokolle zu werfen, um die Json-Antwort zu überprüfen und zu sehen, was in Ihrem Code vor sich geht.

Quellen:

552
Kurt Bourbaki

Die neue Annotation @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 genauso wie in der Dokumentation:
http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html

87
tero17

Mit Jackson 2.0+ können Sie auch @JsonIdentityInfo. Dies funktionierte viel besser für meine Winterschlaf-Klassen als @JsonBackReference und @JsonManagedReference, der Probleme für mich hatte und das Problem nicht löste. Fügen Sie einfach Folgendes hinzu:

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

44
Marcus

Außerdem bietet Jackson 1.6 Unterstützung für mgang mit bidirektionalen Referenzen ..., was genau das ist, wonach Sie suchen ( dieser Blogeintrag erwähnt auch die Funktion).

Ab Juli 2011 gibt es auch " jackson-module-hibernate ", das in einigen Aspekten des Umgangs mit Objekten aus dem Ruhezustand hilfreich sein kann, obwohl dies nicht unbedingt der Fall ist (für das Anmerkungen erforderlich sind).

19
StaxMan

Jetzt unterstützt Jackson das Vermeiden von Zyklen, ohne die Felder zu ignorieren:

Jackson - Serialisierung von Entitäten mit bidirektionalen Beziehungen (Vermeidung von Zyklen)

11
Eugene Retunsky

Das hat bei mir prima geklappt. Fügen Sie der untergeordneten Klasse die Anmerkung @JsonIgnore hinzu, in der Sie den Verweis auf die übergeordnete Klasse erwähnen.

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

Funktioniert gut für mich Beheben Sie das Problem mit Json Infinite Recursion, wenn Sie mit Jackson arbeiten

Das habe ich in oneToMany und ManyToOne Mapping gemacht

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


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

Es gibt jetzt ein Jackson-Modul (für Jackson 2), das speziell für die Behandlung von Problemen mit der verzögerten Hibernate-Initialisierung 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 den ObjectMapper von Jackson 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 .

6
Shane

Für mich ist die beste Lösung, @JsonView Zu verwenden und spezifische Filter für jedes Szenario zu erstellen. Sie können auch @JsonManagedReference Und @JsonBackReference Verwenden, dies ist jedoch eine hardcodierte Lösung für nur eine Situation, in der der Eigentümer immer auf die Besitzerseite verweist und niemals auf das Gegenteil. Wenn Sie in einem anderen Serialisierungsszenario das Attribut anders mit Anmerkungen versehen müssen, ist dies nicht möglich.

Problem

Verwenden wir zwei Klassen, Company und Employee, zwischen denen eine zyklische Abhängigkeit besteht:

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 Verwenden Sie `@ JsonView`

Mit @JsonView Können Sie Filter verwenden und auswählen, welche Felder beim Serialisieren der Objekte einbezogen werden 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 es sich bei den Filtern um Dummy-Klassen handelt, die nur zum Angeben der Felder mit der Annotation @JsonView Verwendet werden, sodass Sie beliebig viele erstellen können. Lassen Sie es uns in Aktion sehen, aber zuerst müssen wir unsere Klasse Company mit Anmerkungen versehen:

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

@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 mit der unendlichen Rekursion behoben, da Sie ausdrücklich gesagt haben, dass Sie nur die Attribute serialisieren möchten, die mit @JsonView(Filter.CompanyData.class) kommentiert wurden.

Wenn es die Rückreferenz für Unternehmen in Employee erreicht, wird überprüft, ob es nicht mit Anmerkungen versehen ist, und die Serialisierung wird ignoriert. 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 Controllers-Methoden mit dem gewünschten @JsonView - Filter versehen und die Serialisierung wird transparent auf das zurückgegebene Objekt angewendet.

Hier sind die Importe für den Fall, 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;
5
fabioresner

@ JsonIgnoreProperties ist die Antwort.

Benutze so etwas:

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

Stellen Sie sicher, dass Sie com.fasterxml.jackson überall 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 @JsonManagedReference und @JsonBackReference.

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

import com.fasterxml.jackson.databind.ObjectMapper;

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

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;
4
Klapsa2503

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 abrufen (die meiste Zeit, die wir benötigen), hilft Ihnen @ JsonIgnore nicht weiter. In solchen Situationen folgen Sie bitte der unten stehenden Lösung.

sie erhalten eine unendliche Rekursion, da die Klasse BodyStat erneut auf das Objekt Trainee 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

2
RAJ KASHWAN

Ich bin auch auf das gleiche Problem gestoßen. Ich benutzte @JsonIdentityInfo 's ObjectIdGenerators.PropertyGenerator.class Generatortyp.

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 {
...
1
Arif Acar

sie können DTO Pattern Create Class Trainee DTO ohne Annotation Hibernate verwenden und Sie können Jackson Mapper verwenden, um Trainee zu TraineeDTO zu konvertieren und die Fehlermeldung Disapeare Bingo :)

1
Dahar Youssef

Sie sollten @JsonBackReference mit @ManyToOne-Entität und @JsonManagedReference mit @onetomany-Entitätsklassen verwenden.

@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;
1
Shubham

Wenn Sie Spring Data Rest verwenden, können Sie das Problem beheben, indem Sie für jede Entität, die an zyklischen Verweisen beteiligt ist, Repositorys erstellen.

0
Morik

Wenn Sie die Eigenschaft nicht ignorieren können, versuchen Sie, die Sichtbarkeit des Felds zu ändern. In unserem Fall hatten wir alten Code, der noch Entitäten mit der Beziehung übermittelte. In meinem Fall war dies der Fix:

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

Ich hatte dieses Problem, wollte aber keine Annotation in meinen Entitäten verwenden. Deshalb habe ich einen Konstruktor für meine Klasse erstellt. Dieser Konstruktor darf keinen Verweis auf die Entitäten enthalten, 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 B oder A mit @ResponseBody An die Ansicht zu senden, kann dies zu einer Endlosschleife führen. Sie können einen Konstruktor in Ihre Klasse schreiben und eine Abfrage mit Ihrem entityManager wie folgt 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;
   }

}

Es gibt jedoch einige Einschränkungen bezüglich dieser Lösung, wie Sie sehen können, im Konstruktor habe ich keinen Verweis auf Liste bs gemacht, da Hibernate dies nicht zulässt, zumindest in version 3.6.10.Final Wenn ich also beide Entitäten in einer Ansicht anzeigen möchte, 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 ist, dass Sie beim Hinzufügen oder Entfernen einer Eigenschaft Ihren Konstruktor und alle Ihre Abfragen aktualisieren müssen.

0
OJVM