Cuando trato de convertir un objeto JPA que tiene una asociación bidireccional en JSON, sigo obteniendo
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError)
Todo lo que encontré es este hilo que básicamente concluye con la recomendación de evitar las asociaciones bidireccionales. ¿Alguien tiene una idea para una solución alternativa para este error de primavera?
------ EDITAR 2010-07-24 16:26:22 -------
Fragmentos de código:
Objeto comercial 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 comercial 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();
}
}
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>
@Transient
aTrainee.bodyStats
.@JsonIgnoreProperties
es la solución más limpia. Mira la respuesta de Zammel AlaaEddine para más detalles.Respuestas:
Puede usar
@JsonIgnore
para romper el ciclo.fuente
@JsonIgnore
, será nulo en "clave externa" cuando actualice la entidad ...JsonIgnoreProperties [Actualización 2017]:
Ahora puede usar JsonIgnoreProperties para suprimir la serialización de propiedades (durante la serialización) o ignorar el procesamiento de las propiedades de 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 puede usar dos anotaciones para resolver el problema de recursión infinita sin ignorar los captadores / establecedores durante la serialización:
@JsonManagedReference
y@JsonBackReference
.Explicación
Para que Jackson funcione bien, uno de los dos lados de la relación no debe ser serializado, para evitar el ciclo infite que causa su error de desbordamiento de pila.
Entonces, Jackson toma la parte hacia adelante de la referencia (su
Set<BodyStat> bodyStats
en la clase de Aprendiz), y la convierte en un formato de almacenamiento similar a json; Este es el llamado proceso de clasificación . Luego, Jackson busca la parte posterior de la referencia (es decir,Trainee trainee
en la clase BodyStat) y la deja como está, sin serializarla. Esta parte de la relación se reconstruirá durante la deserialización ( desarme ) de la referencia directa.Puede cambiar su código de esta manera (omito las partes inútiles):
Objeto comercial 1:
Objeto comercial 2:
Ahora todo debería funcionar correctamente.
Si desea más información, escribí un artículo sobre los problemas de Json y Jackson Stackoverflow en Keenformatics , mi blog.
EDITAR:
Otra anotación útil que puede verificar es @JsonIdentityInfo : al usarla, cada vez que Jackson serialice su objeto, agregará una ID (u otro atributo de su elección) para que no lo "escanee" por completo cada vez. Esto puede ser útil cuando tiene un bucle de cadena entre objetos más interrelacionados (por ejemplo: Order -> OrderLine -> User -> Order y otra vez).
En este caso, debe tener cuidado, ya que podría necesitar leer los atributos de su 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 lo impide. Sugiero que siempre eche un vistazo a los registros de Firebug para verificar la respuesta de Json y ver qué está sucediendo en su código.
Fuentes:
fuente
@JsonIgnore
de nuevo la referencia.@JsonIgnore
.La nueva anotación @JsonIgnoreProperties resuelve muchos de los problemas con las otras opciones.
Compruébalo aquí. Funciona igual que en la documentación:
http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html
fuente
Además, puede usar Jackson 2.0+
@JsonIdentityInfo
. Esto funcionó mucho mejor para mis clases de hibernación que@JsonBackReference
y@JsonManagedReference
, que tenía problemas para mí y no resolvió el problema. Solo agrega algo como:y debería funcionar
fuente
@JsonIdentityInfo
mi respuesta anterior.@JsonIdentityInfo
anotaciones a mis entidades, pero no resuelve el problema de recursión. Solo@JsonBackReference
y@JsonManagedReference
resuelve, pero se eliminan las propiedades mapeadas de JSON.Además, Jackson 1.6 tiene soporte para manejar referencias bidireccionales ... que parece ser lo que está buscando ( esta entrada de blog también menciona la característica)
Y a partir de julio de 2011, también hay " jackson-module-hibernate ", que podría ayudar en algunos aspectos del manejo de objetos Hibernate, aunque no necesariamente este en particular (que requiere anotaciones).
fuente
Ahora Jackson admite evitar ciclos sin ignorar los campos:
Jackson: serialización de entidades con relaciones bireccionales (evitando ciclos)
fuente
Esto funcionó perfectamente bien para mí. Agregue la anotación @JsonIgnore en la clase secundaria donde menciona la referencia a la clase primaria.
fuente
@JsonIgnore
ignora este atributo de ser recuperado al lado del cliente. ¿Qué sucede si necesito este atributo con su hijo (si tiene un hijo)?Ahora hay un módulo Jackson (para Jackson 2) diseñado específicamente para manejar los problemas de inicialización diferida de Hibernate al serializar.
https://github.com/FasterXML/jackson-datatype-hibernate
Simplemente agregue la dependencia (tenga en cuenta que hay diferentes dependencias para Hibernate 3 e Hibernate 4):
y luego registre el módulo al inicializar el ObjectMapper de Jackson:
La documentación actualmente no es genial. Consulte el código Hibernate4Module para ver las opciones disponibles.
fuente
Funciona bien para mí Resolver el problema de recursión infinita de Json al trabajar con Jackson
Esto es lo que he hecho en OneToMany y ManyToOne Mapping
fuente
@JsonManagedReference
,@JsonBackReference
no le da los datos asociados con@OneToMany
y@ManyToOne
escenario, también cuando se utiliza@JsonIgnoreProperties
no omitir datos de la entidad asociada. ¿Cómo resolver esto?Para mí, la mejor solución es usar
@JsonView
y crear filtros específicos para cada escenario. También puede usar@JsonManagedReference
y@JsonBackReference
, sin embargo, es una solución codificada para una sola situación, donde el propietario siempre hace referencia al lado propietario, y nunca al contrario. Si tiene otro escenario de serialización donde necesita volver a anotar el atributo de manera diferente, no podrá hacerlo.Problema
Usemos dos clases,
Company
yEmployee
donde tenga una dependencia cíclica entre ellas:Y la clase de prueba que intenta serializar usando
ObjectMapper
( Spring Boot ):Si ejecuta este código, obtendrá lo siguiente:
Solución usando `@ JsonView`
@JsonView
le permite usar filtros y elegir qué campos se deben incluir al serializar los objetos. Un filtro es solo una referencia de clase utilizada como identificador. Así que primero creemos los filtros:Recuerde, los filtros son clases ficticias, solo se utilizan para especificar los campos con la
@JsonView
anotación, para que pueda crear tantos como desee y necesite. Vamos a verlo en acción, pero primero tenemos que anotar nuestraCompany
clase:y cambie la Prueba para que el serializador use la Vista:
Ahora, si ejecuta este código, se resuelve el problema de recursión infinita, porque ha dicho explícitamente que solo desea serializar los atributos que se anotaron con
@JsonView(Filter.CompanyData.class)
.Cuando llega a la referencia posterior para la compañía en el
Employee
, verifica que no esté anotada e ignora la serialización. También tiene una solución potente y flexible para elegir qué datos desea enviar a través de sus API REST.Con Spring puede anotar sus métodos de Controladores REST con el
@JsonView
filtro deseado y la serialización se aplica de forma transparente al objeto que regresa.Aquí están las importaciones utilizadas en caso de que necesite verificar:
fuente
@JsonIgnoreProperties es la respuesta.
Usa algo como esto ::
fuente
@JsonManagedReference
,@JsonBackReference
no le da los datos asociados con@OneToMany
y@ManyToOne
escenario, también cuando se utiliza@JsonIgnoreProperties
no omitir datos de la entidad asociada. ¿Cómo resolver esto?En mi caso fue suficiente para cambiar la relación de:
a:
otra relación se mantuvo como estaba:
fuente
Asegúrese de usar com.fasterxml.jackson en todas partes. Pasé mucho tiempo para descubrirlo.
Luego usa
@JsonManagedReference
y@JsonBackReference
.Finalmente, puede serializar su modelo a JSON:
fuente
Puede usar @JsonIgnore , pero esto ignorará los datos json a los que se puede acceder debido a la relación de clave externa. Por lo tanto, si necesita los datos de la clave externa (la mayor parte del tiempo que necesitamos), entonces @JsonIgnore no lo ayudará. En tal situación, siga la siguiente solución.
está obteniendo una recursión infinita, debido a que la clase BodyStat vuelve a referirse al objeto Trainee
BodyStat
Aprendiz
Por lo tanto, debe comentar / omitir la parte anterior en Aprendiz
fuente
También me encontré con el mismo problema. Solía
@JsonIdentityInfo
'sObjectIdGenerators.PropertyGenerator.class
tipo de generador.Esa es mi solución:
fuente
Debe usar @JsonBackReference con la entidad @ManyToOne y @JsonManagedReference con las clases de entidad que contienen @onetomany.
fuente
puede usar el patrón DTO crear la clase TraineeDTO sin ninguna hibernación de anotación y puede usar jackson mapper para convertir Trainee a TraineeDTO y desaparecer el mensaje de error :)
fuente
Si no puede ignorar la propiedad, intente modificar la visibilidad del campo. En nuestro caso, teníamos código antiguo que aún enviaba entidades con la relación, por lo que en mi caso, esta fue la solución:
fuente
Tuve este problema, pero no quería usar anotaciones en mis entidades, así que resolví creando un constructor para mi clase, este constructor no debe tener una referencia a las entidades que hacen referencia a esta entidad. Digamos este escenario.
Si intenta enviar a la vista la clase
B
oA
con@ResponseBody
ella puede causar un bucle infinito. Puede escribir un constructor en su clase y crear una consultaentityManager
como esta.Esta es la clase con el constructor.
Sin embargo, hay algunas restricciones sobre esta solución, como puede ver, en el constructor no hice referencia a List bs, esto se debe a que Hibernate no lo permite, al menos en la versión 3.6.10.Final , así que cuando necesito para mostrar ambas entidades en una vista, hago lo siguiente.
El otro problema con esta solución es que si agrega o elimina una propiedad, debe actualizar su constructor y todas sus consultas.
fuente
En caso de que esté utilizando Spring Data Rest, el problema se puede resolver creando repositorios para cada entidad involucrada en referencias cíclicas.
fuente
Llego tarde y ya es un hilo tan largo. Pero también pasé un par de horas tratando de resolver esto, y me gustaría dar mi caso como otro ejemplo.
Probé las soluciones JsonIgnore, JsonIgnoreProperties y BackReference, pero, curiosamente, fue como si no hubieran sido recogidas.
Usé Lombok y pensé que tal vez interfiera, ya que crea constructores y anula toString (saw toString en stackoverflowerror stack).
Finalmente, no fue culpa de Lombok: utilicé la generación automática de NetBeans de entidades JPA a partir de tablas de bases de datos, sin pensarlo demasiado, bueno, y una de las anotaciones que se agregaron a las clases generadas fue @XmlRootElement. Una vez que lo eliminé, todo comenzó a funcionar. Oh bien.
fuente
El punto es colocar @JsonIgnore en el método setter de la siguiente manera. en mi caso.
Township.java
Village.java
fuente