Ao tentar converter um objeto JPA que tenha uma associação bidirecional em JSON, continuo recebendo
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError)
Tudo o que eu encontrei é esse tópico que basicamente conclui com a recomendação de evitar associações bidirecionais. Alguém tem uma idéia para uma solução alternativa para este bug da primavera?
------ EDIT 2010-07-24 16:26:22 -------
Partes de codigo:
Objeto de negócios 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 negócios 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;
}
}
Implementação pela JPA do estagiário 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
é a solução mais limpa. Confira a resposta de Zammel AlaaEddine para obter mais detalhes.Respostas:
Você pode usar
@JsonIgnore
para interromper o ciclo.fonte
@JsonIgnore
você vai nulo em "chave estrangeira" quando você atualizar a entidade ...JsonIgnoreProperties [atualização de 2017]:
Agora você pode usar JsonIgnoreProperties para suprimir a serialização de propriedades (durante a serialização) ou ignorar o processamento das propriedades JSON lidas (durante a desserialização) . Se não é isso que você procura, continue lendo abaixo.
(Obrigado a As Zammel AlaaEddine por apontar isso).
JsonManagedReference e JsonBackReference
Desde o Jackson 1.6, você pode usar duas anotações para resolver o problema de recursão infinita sem ignorar os getters / setters durante a serialização:
@JsonManagedReference
e@JsonBackReference
.Explicação
Para que Jackson funcione bem, um dos dois lados do relacionamento não deve ser serializado, a fim de evitar o loop infinito que causa o erro de fluxo de pilha.
Então, Jackson pega a parte avançada da referência (sua
Set<BodyStat> bodyStats
na classe Trainee) e a converte em um formato de armazenamento semelhante ao json; esse é o chamado processo de empacotamento . Então, Jackson procura a parte de trás da referência (ou seja,Trainee trainee
na classe BodyStat) e a deixa como está, não a serializando. Essa parte do relacionamento será reconstruída durante a desserialização ( desserialização ) da referência futura.Você pode alterar seu código assim (pulo as partes inúteis):
Objeto de negócios 1:
Objeto de negócios 2:
Agora tudo deve funcionar corretamente.
Se você quiser mais informações, escrevi um artigo sobre os problemas de Json e Jackson Stackoverflow no Keenformatics , meu blog.
EDITAR:
Outra anotação útil que você pode verificar é @JsonIdentityInfo : ao usá-lo, toda vez que Jackson serializar seu objeto, ele adicionará um ID (ou outro atributo de sua escolha) a ele, para que ele não seja "digitalizado" novamente toda vez. Isso pode ser útil quando você tiver um loop em cadeia entre objetos mais inter-relacionados (por exemplo: Pedido -> Linha de pedidos -> Usuário -> Pedido e novamente).
Nesse caso, você precisa ter cuidado, pois pode ser necessário ler os atributos do seu objeto mais de uma vez (por exemplo, em uma lista de produtos com mais produtos que compartilham o mesmo vendedor), e essa anotação impede que você faça isso. Sugiro sempre dar uma olhada nos logs do firebug para verificar a resposta do Json e ver o que está acontecendo no seu código.
Fontes:
fonte
@JsonIgnore
a referência.@JsonIgnore
.A nova anotação @JsonIgnoreProperties resolve muitos dos problemas com as outras opções.
Confira aqui. Funciona como na documentação:
http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html
fonte
Além disso, usando o Jackson 2.0+ você pode usar
@JsonIdentityInfo
. Isso funcionou muito melhor para minhas aulas de hibernação do que@JsonBackReference
e@JsonManagedReference
, o que teve problemas para mim e não resolveu o problema. Basta adicionar algo como:e deve funcionar.
fonte
@JsonIdentityInfo
na minha resposta acima.@JsonIdentityInfo
anotações às minhas entidades, mas isso não resolve o problema de recursão. Somente@JsonBackReference
e@JsonManagedReference
resolve, mas eles são remover propriedades mapeadas do JSON.Além disso, o Jackson 1.6 tem suporte para lidar com referências bidirecionais ... o que parece o que você está procurando ( esta entrada de blog também menciona o recurso)
E a partir de julho de 2011, também há " jackson-module-hibernate " que pode ajudar em alguns aspectos ao lidar com objetos do Hibernate, embora não necessariamente este em particular (que requer anotações).
fonte
Agora Jackson suporta evitar ciclos sem ignorar os campos:
Jackson - serialização de entidades com relacionamentos bidirecionais (evitando ciclos)
fonte
Isso funcionou perfeitamente bem para mim. Adicione a anotação @JsonIgnore na classe filho em que você menciona a referência à classe pai.
fonte
@JsonIgnore
ignora esse atributo de ser recuperado para o lado do cliente. E se eu precisar desse atributo com seu filho (se ele tiver um filho)?Agora existe um módulo Jackson (para Jackson 2) projetado especificamente para lidar com problemas de inicialização lenta do Hibernate ao serializar.
https://github.com/FasterXML/jackson-datatype-hibernate
Basta adicionar a dependência (observe que existem diferentes dependências para o Hibernate 3 e Hibernate 4):
e registre o módulo ao inicializar o ObjectMapper de Jackson:
A documentação atualmente não é ótima. Veja o código Hibernate4Module para opções disponíveis.
fonte
Trabalhando bem para mim Resolver o problema de recursão infinita do Json ao trabalhar com Jackson
Foi o que fiz no mapeamento oneToMany e ManyToOne
fonte
@JsonManagedReference
,@JsonBackReference
não fornece os dados associados@OneToMany
e o@ManyToOne
cenário, também quando o uso@JsonIgnoreProperties
ignora os dados da entidade associada. Como resolver isso?Para mim, a melhor solução é usar
@JsonView
e criar filtros específicos para cada cenário. Você também pode usar@JsonManagedReference
e@JsonBackReference
, no entanto, é uma solução codificada para apenas uma situação, em que o proprietário sempre faz referência ao lado proprietário e nunca ao contrário. Se você tiver outro cenário de serialização em que precisará anotar novamente o atributo de maneira diferente, não poderá.Problema
Vamos usar duas classes
Company
eEmployee
onde você tem uma dependência cíclica entre elas:E a classe de teste que tenta serializar usando
ObjectMapper
( Spring Boot ):Se você executar esse código, obterá o:
Solução usando `@ JsonView`
@JsonView
permite usar filtros e escolher quais campos devem ser incluídos ao serializar os objetos. Um filtro é apenas uma referência de classe usada como identificador. Então, primeiro vamos criar os filtros:Lembre-se de que os filtros são classes fictícias, usadas apenas para especificar os campos com a
@JsonView
anotação, para que você possa criar quantos quiser e precisar. Vamos vê-lo em ação, mas primeiro precisamos anotar nossaCompany
classe:e mude o teste para que o serializador use a exibição:
Agora, se você executar esse código, o problema da Recursão Infinita será resolvido, porque você disse explicitamente que deseja serializar os atributos que foram anotados
@JsonView(Filter.CompanyData.class)
.Quando atinge a referência anterior da empresa no
Employee
, verifica se não está anotado e ignora a serialização. Você também tem uma solução poderosa e flexível para escolher quais dados deseja enviar por meio de suas APIs REST.Com o Spring, você pode anotar seus métodos dos controladores REST com o
@JsonView
filtro desejado e a serialização é aplicada de forma transparente ao objeto retornado.Aqui estão as importações usadas, caso você precise verificar:
fonte
@JsonIgnoreProperties é a resposta.
Use algo como isto ::
fonte
@JsonManagedReference
,@JsonBackReference
não fornece os dados associados@OneToMany
e o@ManyToOne
cenário, também quando o uso@JsonIgnoreProperties
ignora os dados da entidade associada. Como resolver isso?No meu caso, foi o suficiente para mudar a relação de:
para:
outra relação permaneceu como estava:
fonte
Certifique-se de usar com.fasterxml.jackson em todos os lugares. Passei muito tempo para descobrir.
Então use
@JsonManagedReference
e@JsonBackReference
.Por fim, você pode serializar seu modelo para JSON:
fonte
Você pode usar @JsonIgnore , mas isso ignorará os dados json que podem ser acessados devido ao relacionamento de Chave Estrangeira. Portanto, se você solicitar os dados da chave estrangeira (na maioria das vezes exigimos), o @JsonIgnore não ajudará. Em tal situação, siga a solução abaixo.
você está recebendo recursão infinita, devido à classe BodyStat referindo novamente o objeto Trainee
BodyStat
Estagiário
Portanto, você deve comentar / omitir a parte acima no Trainee
fonte
Eu também encontrei o mesmo problema. Eu usei
@JsonIdentityInfo
oObjectIdGenerators.PropertyGenerator.class
tipo de gerador.Essa é a minha solução:
fonte
Você deve usar @JsonBackReference com a entidade @ManyToOne e @JsonManagedReference com @onetomany contendo classes de entidade.
fonte
você pode usar o padrão DTO para criar a classe TraineeDTO sem qualquer anotação hiberbnate e pode usar o jackson mapper para converter Trainee em TraineeDTO e bingo a mensagem de erro desaparece :)
fonte
Se você não pode ignorar a propriedade, tente modificar a visibilidade do campo. No nosso caso, tínhamos código antigo ainda enviando entidades com o relacionamento, portanto, no meu caso, essa era a correção:
fonte
Eu tinha esse problema, mas não queria usar anotação em minhas entidades, então resolvi criando um construtor para minha classe; esse construtor não deve ter uma referência de volta às entidades que fazem referência a essa entidade. Vamos dizer esse cenário.
Se você tentar enviar para a exibição a classe
B
ouA
com@ResponseBody
ela, poderá causar um loop infinito. Você pode escrever um construtor em sua classe e criar uma consultaentityManager
como esta.Esta é a classe com o construtor.
No entanto, existem algumas restrições sobre esta solução, como você pode ver, no construtor não fiz referência à Lista bs porque o Hibernate não permite, pelo menos na versão 3.6.10.Final , então quando eu precisar para mostrar as duas entidades em uma exibição, faça o seguinte.
O outro problema com esta solução é que, se você adicionar ou remover uma propriedade, deverá atualizar seu construtor e todas as suas consultas.
fonte
Caso você esteja usando o Spring Data Rest, o problema pode ser resolvido criando Repositórios para cada Entidade envolvida em referências cíclicas.
fonte
Estou atrasado e já é um tópico tão longo. Mas passei algumas horas tentando descobrir isso também e gostaria de dar meu caso como outro exemplo.
Tentei as soluções JsonIgnore, JsonIgnoreProperties e BackReference, mas, estranhamente, era como se não fossem apanhadas.
Eu usei o Lombok e pensei que talvez isso interfira, pois ele cria construtores e substitui o toString (vi toString na stackoverflowerror stack).
Finalmente, não foi culpa de Lombok - usei a geração automática de entidades JPA do NetBeans a partir de tabelas de banco de dados, sem pensar muito - bem, e uma das anotações adicionadas às classes geradas foi @XmlRootElement. Depois que o removi, tudo começou a funcionar. Ah bem.
fonte
O objetivo é colocar o @JsonIgnore no método setter da seguinte maneira. No meu caso.
Township.java
Village.java
fonte