Recursão Infinita com Jackson JSON e Hibernate JPA

Ao tentar converter um object JPA que possui uma associação bidirecional em JSON, continuo recebendo

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

Tudo o que eu encontrei é este segmento que basicamente conclui com a recomendação para evitar associações bidirecionais. Alguém tem uma idéia de uma solução 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 bodyStats; @OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL) @Column(nullable = true) private Set trainings; @OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL) @Column(nullable = true) private Set 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 trainees = new ConcurrentHashMap(); @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 da APP 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

   false     <!--  -->    

Você pode usar @JsonIgnore para quebrar o ciclo.

JsonIgnoreProperties [Atualização 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 isso não é o que você está procurando, por favor, continue lendo abaixo.

(Obrigado a As Zammel AlaaEddine por apontar isso).


JsonManagedReference e JsonBackReference

Desde o Jackson 1.6 você pode usar duas annotations para resolver o problema de recursion infinita sem ignorar os getters / setters durante a serialização: @JsonManagedReference e @JsonBackReference .

Explicação

Para Jackson funcionar bem, um dos dois lados do relacionamento não deve ser serializado, a fim de evitar o loop infinito que causa seu erro de stackoverflow.

Então, Jackson pega a parte anterior da referência (seu Set bodyStats na class Trainee) e a converte em um formato de armazenamento json-like; este é o chamado processo de marshalling . Em seguida, Jackson procura a parte de trás da referência (ou seja, Trainee trainee na class BodyStat) e deixa como está, não serializando-a. Essa parte do relacionamento será reconstruída durante a desserialização ( unmarshalling ) da referência direta.

Você pode alterar seu código assim (eu pulo as partes inúteis):

Objeto de negócios 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 bodyStats; 

Objeto de negócios 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; 

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 : usando-a, sempre que Jackson serializar seu object, ele adicionará um ID (ou outro atributo de sua escolha) a ele, para que ele não o “varrerá” novamente toda vez. Isso pode ser útil quando você tem um loop de corrente entre objects mais inter-relacionados (por exemplo: Order -> OrderLine -> User -> Order e novamente).

Nesse caso, você precisa ser cuidadoso, pois pode precisar ler os atributos do object 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ê o faça. 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:

  • Keenformatics – Como resolver JSON recursion infinita Stackoverflow (meu blog)
  • Referências de Jackson
  • Experiência pessoal

A nova anotação @JsonIgnoreProperties resolve muitos dos problemas com as outras opções.

 @Entity public class Material{ ... @JsonIgnoreProperties("costMaterials") private List costSuppliers = new ArrayList<>(); ... } @Entity public class Supplier{ ... @JsonIgnoreProperties("costSuppliers") private List costMaterials = new ArrayList<>(); .... } 

Confira aqui. Funciona como na documentação:
http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html

Além disso, usando 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. Apenas adicione algo como:

 @Entity @Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})}) @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@traineeId") public class Trainee extends BusinessObject { @Entity @Table(name = "ta_bodystat", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})}) @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@bodyStatId") public class BodyStat extends BusinessObject { 

e deve funcionar.

Além disso, Jackson 1.6 tem suporte para manipulação de referências bidirecionais … o que parece ser o que você está procurando ( esta input do blog também menciona o recurso)

E a partir de julho de 2011, há também ” jackson-module-hibernate “, que pode ajudar em alguns aspectos de lidar com objects do Hibernate, embora não necessariamente este em particular (o que requer annotations).

Agora Jackson suporta evitar ciclos sem ignorar os campos:

Jackson – serialização de entidades com relações bidirecionais (evitando ciclos)

Isso funcionou perfeitamente bem para mim. Adicione a anotação @JsonIgnore na class filha onde você menciona a referência à class pai.

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

Existe agora um módulo Jackson (para Jackson 2) especificamente projetado para lidar com problemas de boot lenta do Hibernate durante a serialização.

https://github.com/FasterXML/jackson-datatype-hibernate

Basta adicionar a dependência (note que existem dependencies diferentes para o Hibernate 3 e o Hibernate 4):

  com.fasterxml.jackson.datatype jackson-datatype-hibernate4 2.4.0  

e depois registrar o módulo ao inicializar o ObjectMapper de Jackson:

 ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new Hibernate4Module()); 

Atualmente, a documentação não é ótima. Veja o código do Hibernate4Module para opções disponíveis.

No meu caso, foi o suficiente para mudar a relação de:

 @OneToMany(mappedBy = "county") private List towns; 

para:

 @OneToMany private List towns; 

outra relação ficou como estava:

 @ManyToOne @JoinColumn(name = "county_id") private County county; 

Para mim, a melhor solução é usar o @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, onde o proprietário sempre faz referência ao lado proprietário e nunca o contrário. Se você tiver outro cenário de serialização em que precise anotar novamente o atributo de maneira diferente, não será possível.

Problema

Vamos usar duas classs, Company e Employee onde você tem uma dependência cíclica entre eles:

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

E a class de teste que tenta serializar usando o 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); } } 

Se você executar este código, você receberá o:

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

Solução Usando o `@ JsonView`

@JsonView permite que você use filtros e escolha quais campos devem ser incluídos durante a serialização dos objects. Um filtro é apenas uma referência de class usada como identificador. Então, vamos primeiro criar os filtros:

 public class Filter { public static interface EmployeeData {}; public static interface CompanyData extends EmployeeData {}; } 

Lembre-se, os filtros são classs fictícias, usadas apenas para especificar os campos com a anotação @JsonView , para que você possa criar quantos você quiser e precisar. Vamos ver isso em ação, mas primeiro precisamos anotar nossa class de Company :

 public class Company { @JsonView(Filter.CompanyData.class) private Employee employee; public Company(Employee employee) { this.employee = employee; } public Employee getEmployee() { return employee; } } 

e altere o teste para que o serializador use o modo de exibição:

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

Agora, se você executar este código, o problema de Recursão Infinita será resolvido, porque você disse explicitamente que deseja apenas serializar os atributos que foram anotados com @JsonView(Filter.CompanyData.class) .

Quando atinge a referência de volta para a empresa no Employee , ele 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 methods REST Controllers com o filtro @JsonView desejado e a serialização é aplicada de forma transparente ao object de retorno.

Aqui estão as importações usadas no caso de você precisar verificar:

 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; 

Certifique-se de usar com.fasterxml.jackson em todos os lugares. Passei muito tempo para descobrir.

  2.9.2    com.fasterxml.jackson.core jackson-annotations ${fasterxml.jackson.version}    com.fasterxml.jackson.core jackson-databind ${fasterxml.jackson.version}  

Em seguida, use @JsonManagedReference e @JsonBackReference .

Finalmente, você pode serializar seu modelo para JSON:

 import com.fasterxml.jackson.databind.ObjectMapper; ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(model); 

você pode usar o padrão DTO criar class TraineeDTO sem qualquer anotation hiberbnate e você pode usar o jackson mapper para converter Trainee para TraineeDTO e bingo a mensagem de erro disapeare 🙂

Eu também encontrei o mesmo problema. Eu usei o tipo de gerador ObjectIdGenerators.PropertyGenerator.class @JsonIdentityInfo .

Essa é a minha solução:

 @Entity @Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})}) @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class Trainee extends BusinessObject { ... 

Você pode usar @JsonIgnore , mas isso irá ignorar os dados json que podem ser acessados ​​por causa do relacionamento de Chave Estrangeira. Portanto, se você requisitar os dados da chave estrangeira (a maior parte do tempo que precisarmos), então o @JsonIgnore não ajudará você. Em tal situação, por favor siga a solução abaixo.

você está recebendo recursion Infinite, por causa da class BodyStat novamente referindo o object Trainee

BodyStat

 @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn(name="trainee_fk") private Trainee trainee; 

Estagiário

 @OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL) @Column(nullable = true) private Set bodyStats; 

Portanto, você tem que comentar / omitir a parte acima no Trainee

Eu tive esse problema, mas eu não queria usar anotação em minhas entidades, então resolvi criando um construtor para minha class, esse construtor não deve ter uma referência de volta para as entidades que fazem referência a essa entidade. Vamos dizer esse cenário.

 public class A{ private int id; private String code; private String name; private List bs; } public class B{ private int id; private String code; private String name; private A a; } 

Se você tentar enviar para a exibição a class B ou A com @ResponseBody , poderá causar um loop infinito. Você pode escrever um construtor em sua class e criar uma consulta com seu entityManager como este.

 "select new A(id, code, name) from A" 

Esta é a class com o construtor.

 public class A{ private int id; private String code; private String name; private List bs; public A(){ } public A(int id, String code, String name){ this.id = id; this.code = code; this.name = name; } } 

No entanto, existem algumas constrições sobre esta solução, como você pode ver, no construtor eu não fiz referência a List bs isso é porque o Hibernate não permite isso, pelo menos na versão 3.6.10.Final , então quando eu preciso para mostrar as duas entidades em uma visão, faço o seguinte.

 public A getAById(int id); //THE A id public List getBsByAId(int idA); //the A id. 

O outro problema com essa solução é que, se você adicionar ou remover uma propriedade, deverá atualizar seu construtor e todas as suas consultas.

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.

@JsonIgnoreProperties é a resposta.

Use algo assim ::

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

Trabalhando bem para mim Resolver problema de recursion Json Infinite ao trabalhar com Jackson

Isto é o que eu fiz no mapeamento oneToMany and ManyToOne

 @ManyToOne @JoinColumn(name="Key") @JsonBackReference private LgcyIsp Key; @OneToMany(mappedBy="LgcyIsp ") @JsonManagedReference private List safety;