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>

2voto

Aus irgendeinem Grund funktionierte es in meinem Fall nicht mit Set. Ich musste es in List ändern und @JsonIgnore und @ToString.Exclude verwenden, damit es funktionierte.

Ersetzen Sie Set durch Liste:

//before
@OneToMany(mappedBy="client")
private Set<address> addressess;

//after
@OneToMany(mappedBy="client")
private List<address> addressess;

Und fügen Sie die Anmerkungen @JsonIgnore und @ToString.Exclude hinzu:

@ManyToOne
@JoinColumn(name="client_id", nullable = false)
@JsonIgnore
@ToString.Exclude
private Client client;

2voto

Sachithra Dilshan Punkte 358

Wenn Sie @JsonManagedReference , @JsonBackReference o @JsonIgnore Annotation werden einige Felder ignoriert und Infinite Recursion mit Jackson JSON gelöst.

Aber wenn Sie die @JsonIdentityInfo die auch die unendliche Rekursion vermeiden und Sie können alle Feldwerte erhalten, daher schlage ich vor, dass Sie @JsonIdentityInfo Bemerkung.

@JsonIdentityInfo(generator= ObjectIdGenerators.UUIDGenerator.class, property="@id")

Siehe diesen Artikel https://www.toptal.com/javascript/bidirectional-relationship-in-json um ein gutes Verständnis zu bekommen über @JsonIdentityInfo Bemerkung.

2voto

emily Punkte 45

Dieser Beitrag: https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion hat eine vollständige Erklärung.

Wenn Sie Jackson mit älteren Versionen verwenden, können Sie @jsonmanagedreference + @jsonbackreference ausprobieren. Wenn Ihr Jackson über 2 ist (1.9 funktioniert auch nicht, soweit ich weiß), versuchen Sie stattdessen @JsonIdentityInfo.

0voto

Morik Punkte 21

Falls Sie Spring Data Rest verwenden, kann das Problem durch die Erstellung von Repositories für jede Entität, die an zyklischen Referenzen beteiligt ist, gelöst werden.

0voto

OJVM Punkte 1233

Ich hatte dieses Problem, aber ich wollte nicht Annotation in meine Entitäten zu verwenden, so löste ich durch die Erstellung eines Konstruktors für meine Klasse, dieser Konstruktor muss nicht einen Verweis zurück zu den Entitäten, die diese Entität verweist. 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, an die Ansicht die Klasse B o A mit @ResponseBody kann es zu einer Endlosschleife kommen. Sie können einen Konstruktor in Ihrer Klasse schreiben und eine Abfrage mit Ihrer entityManager wie diese.

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

}

Allerdings gibt es einige Einschränkungen bei dieser Lösung, wie Sie sehen können, habe ich im Konstruktor keinen Verweis auf Liste bs Dies liegt daran, dass Hibernate dies nicht zulässt, zumindest nicht in Version 3.6.10.Final Wenn ich also beide Entitäten in einer Ansicht anzeigen muss, 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, wenn Sie eine Eigenschaft hinzufügen oder entfernen, Ihren Konstruktor und alle Ihre Abfragen aktualisieren müssen.

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