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>

14voto

Manjunath BR Punkte 121

Das hat bei mir einwandfrei funktioniert. Fügen Sie die Annotation @JsonIgnore in der untergeordneten Klasse hinzu, wo Sie den Verweis auf die übergeordnete Klasse erwähnen.

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

12voto

Eugene Retunsky Punkte 12771

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

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

11voto

Prabu M Punkte 160

Funktioniert bei mir einwandfrei Behebung des Problems der unendlichen Rekursion von Json bei der Arbeit mit Jackson

Das habe ich bei oneToMany und ManyToOne Mapping gemacht

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

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

8voto

fabioresner Punkte 875

Für mich ist die beste Lösung die Verwendung von @JsonView und erstellen Sie spezifische Filter für jedes Szenario. Sie könnten auch verwenden @JsonManagedReference y @JsonBackReference Es handelt sich jedoch um eine fest einkodierte Lösung für eine einzige Situation, in der der Eigentümer immer auf die besitzende Seite verweist und nie auf die entgegengesetzte. Wenn Sie ein anderes Serialisierungsszenario haben, in dem Sie das Attribut anders annotieren müssen, können Sie das nicht.

Problem

Verwenden wir zwei Klassen, Company y Employee wo eine zyklische Abhängigkeit zwischen ihnen 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, die Serialisierung mit ObjectMapper ( Spring Boot ) :

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

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

Lösung mit `@JsonView`

@JsonView ermöglicht die Verwendung von Filtern und die Auswahl der Felder, die bei der Serialisierung der Objekte berücksichtigt werden sollen. Ein Filter ist lediglich eine Klassenreferenz, die als Bezeichner verwendet wird. Lassen Sie uns also zunächst die Filter erstellen:

public class Filter {

    public static interface EmployeeData {};

    public static interface CompanyData extends EmployeeData {};

} 

Denken Sie daran, dass die Filter Dummy-Klassen sind, die nur für die Angabe der Felder mit der @JsonView Anmerkung, so dass Sie so viele erstellen können, wie Sie wollen und brauchen. Schauen wir uns das in Aktion an, aber zuerst müssen wir unsere Annotation Company Klasse:

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 Serialisierer 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 nun diesen Code ausführen, ist das Problem der unendlichen Rekursion gelöst, denn Sie haben explizit gesagt, dass Sie nur die Attribute serialisieren wollen, die mit @JsonView(Filter.CompanyData.class) .

Wenn sie die hintere Referenz für Unternehmen im Employee prüft es, dass es nicht kommentiert ist und ignoriert die Serialisierung. Sie haben auch eine leistungsstarke und flexible Lösung, um auszuwählen, welche Daten Sie über Ihre REST-APIs senden möchten.

Mit Spring können Sie Ihre REST-Controller-Methoden mit den gewünschten Eigenschaften annotieren. @JsonView Filter und die Serialisierung wird transparent auf das zurückkehrende Objekt angewendet.

Hier sind die verwendeten Importe, falls Sie sie überprüfen möchten:

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;

8voto

ifelse.codes Punkte 1929

@JsonIgnoreProperties ist die Antwort.

Verwenden Sie etwas wie dieses ::

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

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