Recursión infinita con Jackson JSON y problema de Hibernate JPA

Resuelto Ta Sas asked hace 14 años • 29 respuestas

Cuando intento convertir un objeto JPA que tiene una asociación bidireccional a JSON, sigo obteniendo

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

Todo lo que encontré es este hilo que básicamente concluye recomendando evitar asociaciones bidireccionales. ¿Alguien tiene una idea para solucionar este error de primavera?

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

Fragmentos de código:

Objeto de negocio 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 ...
}

Objeto de negocio 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;
}

Controlador:

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

Implementación JPA del aprendiz 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();
    }
}

persistencia.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>
Ta Sas avatar Jul 24 '10 21:07 Ta Sas
Aceptado

JsonIgnoreProperties [Actualización de 2017]:

Ahora puede usar JsonIgnoreProperties para suprimir la serialización de propiedades (durante la serialización) o ignorar el procesamiento de las propiedades JSON leídas (durante la deserialización) . Si esto no es lo que estás buscando, sigue leyendo a continuación.

(Gracias a As Zammel AlaaEddine por señalar esto).


JsonManagedReference y JsonBackReference

Desde Jackson 1.6 puedes usar dos anotaciones para resolver el problema de recursividad infinita sin ignorar los getters/setters durante la serialización: @JsonManagedReferencey @JsonBackReference.

Explicación

Para que Jackson funcione bien, uno de los dos lados de la relación no debe serializarse, para evitar el bucle infinito que causa el error de desbordamiento de pila.

Entonces, Jackson toma la parte delantera de la referencia (la que está Set<BodyStat> bodyStatsen la clase Trainee) y la convierte en un formato de almacenamiento similar a json; este es el llamado proceso de marshalling . Luego, Jackson busca la parte posterior de la referencia (es decir, Trainee traineeen la clase BodyStat) y la deja como está, sin serializarla. Esta parte de la relación se reconstruirá durante la deserialización ( unmarshalling ) de la referencia directa.

Puedes cambiar tu código de esta manera (me salto las partes inútiles):

Objeto de negocio 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;

Objeto de negocio 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;

Ahora todo debería funcionar correctamente.

Si desea obtener más información, escribí un artículo sobre problemas de Json y Jackson Stackoverflow en Keenformatics , mi blog.

EDITAR:

Otra anotación útil que puede verificar es @JsonIdentityInfo : usándola, cada vez que Jackson serializa su objeto, le agregará una identificación (u otro atributo de su elección), de modo que no lo "escanee" por completo cada vez. Esto puede resultar útil cuando tiene un bucle de cadena entre más objetos interrelacionados (por ejemplo: Pedido -> Línea de pedido -> Usuario -> Pedido y otra vez).

En este caso debes tener cuidado, ya que podrías necesitar leer los atributos de tu objeto más de una vez (por ejemplo en una lista de productos con más productos que comparten el mismo vendedor), y esta anotación te lo impide. Sugiero siempre echar un vistazo a los registros de Firebug para verificar la respuesta Json y ver qué sucede en su código.

Fuentes:

  • Keenformatics: cómo resolver la recursividad infinita JSON Stackoverflow (mi blog)
  • Experiencia personal
Kurt Bourbaki avatar Aug 17 '2013 12:08 Kurt Bourbaki

Puedes usarlo @JsonIgnorepara romper el ciclo ( referencia ).

Debe importar org.codehaus.jackson.annotate.JsonIgnore(versiones heredadas) o com.fasterxml.jackson.annotation.JsonIgnore(versiones actuales).

axtavt avatar Jul 25 '2010 14:07 axtavt