556 Stimmen

Unendliche Rekursion mit Jackson JSON und Hibernate JPA Problem

Wenn ich versuche, ein JPA-Objekt mit einer bidirektionalen Assoziation in JSON zu konvertieren, erhalte ich immer

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

Alles, was ich gefunden habe, ist ce fil die im Wesentlichen mit der Empfehlung schließt, bidirektionale Assoziationen zu vermeiden. Hat jemand eine Idee für eine Umgehung dieses Frühjahrsfehlers?

------ BEARBEITEN 2010-07-24 16:26:22 -------

Codesnippets:

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;
}

Controller:

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 der DAO des Auszubildenden:

@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();
    }
}

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

810voto

Kurt Bourbaki Punkte 10990

JsonIgnoreProperties [2017 Update]:

Sie können nun Folgendes verwenden JsonIgnoreProperties a Unterdrückung der Serialisierung von Eigenschaften (während der Serialisierung) oder Ignorieren der Verarbeitung von gelesenen JSON-Eigenschaften (während der Deserialisierung) . Wenn dies nicht das ist, wonach Sie suchen, lesen Sie bitte weiter unten.

(Dank an As Zammel AlaaEddine für diesen Hinweis).


JsonManagedReference und JsonBackReference

Seit Jackson 1.6 kann man zwei Annotationen verwenden, um das Problem der unendlichen Rekursion zu lösen, ohne die Getter/Setter während der Serialisierung zu ignorieren: @JsonManagedReference y @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.

Jackson nimmt also den vorderen Teil der Referenz (Ihre Set<BodyStat> bodyStats in der Klasse Trainee) und wandelt sie in ein json-ähnliches Speicherformat um; dies ist die sogenannte Rangieren Prozess. Dann sucht Jackson nach dem hinteren Teil der Referenz (d.h. Trainee trainee in der Klasse BodyStat) und belässt sie so wie sie ist, ohne sie zu serialisieren. Dieser Teil der Beziehung wird bei der Deserialisierung wieder rekonstruiert ( Abrufen ) der Vorwärtsreferenz.

Sie können Ihren Code wie folgt ändern (ich lasse die überflüssigen Teile weg):

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 Json und Jackson Stackoverflow-Fragen zu Keenformatics , mein Blog.

EDIT :

Eine weitere nützliche Bemerkung, die Sie überprüfen können, ist @JsonIdentityInfo Wenn Sie diese Funktion verwenden, fügt Jackson jedes Mal, wenn Sie Ihr Objekt serialisieren, eine ID (oder ein anderes Attribut Ihrer Wahl) hinzu, so dass es nicht jedes Mal komplett neu "gescannt" wird. Dies kann nützlich sein, wenn Sie eine Kettenschleife zwischen mehreren miteinander verbundenen Objekten haben (zum Beispiel: Bestellung -> OrderLine -> Benutzer -> Bestellung und wieder zurück).

In diesem Fall müssen Sie vorsichtig sein, da Sie die Attribute Ihres Objekts mehr als einmal lesen müssen (z. B. in einer Produktliste mit mehreren Produkten, die denselben Verkäufer haben), und diese Annotation verhindert, dass Sie dies tun können. Ich schlage vor, immer einen Blick in die Firebug-Logs zu werfen, um die Json-Antwort zu überprüfen und zu sehen, was in Ihrem Code vor sich geht.

Quellen:

361voto

axtavt Punkte 233070

Sie können verwenden @JsonIgnore um den Kreislauf zu durchbrechen ( Referenz ).

Sie müssen Folgendes importieren org.codehaus.jackson.annotate.JsonIgnore (ältere Versionen) oder com.fasterxml.jackson.annotation.JsonIgnore (aktuelle Versionen).

135voto

tero17 Punkte 1480

Die neue Annotation @JsonIgnoreProperties löst viele der 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<>();
   ....
}

Sehen Sie es sich hier an. Es funktioniert genau wie in der Dokumentation:
http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html

55voto

Marcus Punkte 1954

Mit Jackson 2.0+ können Sie auch @JsonIdentityInfo . Dies funktionierte für meine Hibernate-Klassen viel besser als @JsonBackReference y @JsonManagedReference was bei mir zu Problemen führte und das Problem nicht löste. Fügen Sie einfach etwas 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.

19voto

StaxMan Punkte 107669

Außerdem bietet Jackson 1.6 Unterstützung für Umgang mit bidirektionalen Referenzen ... was so aussieht wonach Sie suchen ( dieser Blogeintrag erwähnt die Funktion ebenfalls)

Und seit Juli 2011 gibt es auch " jackson-module-hibernate "die bei einigen Aspekten des Umgangs mit Hibernate-Objekten hilfreich sein können, wenn auch nicht unbedingt bei diesem speziellen Objekt (das Anmerkungen erfordert).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X