Ricorsione infinita con problema Jackson JSON e Hibernate JPA

Quando provo a convertire un object JPA che ha un’associazione bidirezionale in JSON, continuo a ricevere

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

Tutto quello che ho trovato è questa discussione che fondamentalmente si conclude con la raccomandazione di evitare associazioni bidirezionali. Qualcuno ha un’idea per una soluzione alternativa per questo bug di spring?

—— EDIT 2010-07-24 16:26:22 ——-

Frammenti di codice:

Business Object 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 ... 

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

Implementazione JPA del tirocinante 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     <!--  -->    

Puoi usare @JsonIgnore per interrompere il ciclo.

JsonIgnoreProperties [Aggiornamento 2017]:

È ora ansible utilizzare JsonIgnoreProperties per sopprimere la serializzazione delle proprietà (durante la serializzazione) o ignorare l’elaborazione delle proprietà JSON leggere (durante la deserializzazione) . Se questo non è quello che stai cercando, continua a leggere qui sotto.

(Grazie a As Zammel AlaaEddine per averlo indicato).


JsonManagedReference e JsonBackReference

Dal momento che Jackson 1.6 è ansible utilizzare due annotazioni per risolvere il problema della ricorsione infinita senza ignorare i getter / setter durante la serializzazione: @JsonManagedReference e @JsonBackReference .

Spiegazione

Perché Jackson funzioni bene, uno dei due lati della relazione non dovrebbe essere serializzato, al fine di evitare il ciclo infite che causa il tuo errore StackOverflow.

Quindi, Jackson prende la parte successiva del riferimento (il tuo Set bodyStats nella class Trainee) e la converte in un formato di archiviazione simile a JSON; questo è il cosiddetto processo di marshalling . Quindi, Jackson cerca la parte posteriore del riferimento (es. Trainee trainee nella class BodyStat) e la lascia così com’è, non la serializza. Questa parte della relazione verrà ricostruita durante la deserializzazione (non riarmo ) del riferimento futuro.

Puoi cambiare il tuo codice in questo modo (salti le parti inutili):

Business Object 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; 

Business Object 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; 

Ora tutto dovrebbe funzionare correttamente.

Se vuoi maggiori informazioni, ho scritto un articolo sui problemi di JSON e Jackson Stackoverflow su Keenformatics , il mio blog.

MODIFICARE:

Un’altra annotazione utile che puoi controllare è @JsonIdentityInfo : utilizzandola, ogni volta che Jackson serializza il tuo object, aggiungerà un ID (o un altro attributo di tua scelta) ad esso, in modo che non lo “scriva” completamente ogni volta. Questo può essere utile quando hai un ciclo di catena tra più oggetti correlati (ad esempio: Ordine -> OrdineLine -> Utente -> Ordine e altro ancora).

In questo caso devi stare attento, poiché potresti dover leggere gli attributi dell’object più di una volta (ad esempio in un elenco di prodotti con più prodotti che condividono lo stesso venditore) e questa annotazione ti impedisce di farlo. Suggerisco di dare sempre un’occhiata ai log di firebug per controllare la risposta Json e vedere cosa succede nel tuo codice.

fonti:

  • Keenformatics – Come risolvere la ricorsione infinita di JSON Stackoverflow (il mio blog)
  • Riferimenti di Jackson
  • Esperienza personale

La nuova annotazione @JsonIgnoreProperties risolve molti dei problemi con le altre opzioni.

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

Controllalo qui. Funziona proprio come nella documentazione:
http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html

Inoltre, usando Jackson 2.0+ puoi usare @JsonIdentityInfo . Questo ha funzionato molto meglio per le mie classi di ibernazione rispetto a @JsonBackReference e @JsonManagedReference , che ha avuto problemi per me e non ha risolto il problema. Aggiungi qualcosa come:

 @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 dovrebbe funzionare.

Inoltre, Jackson 1.6 ha il supporto per la gestione dei riferimenti bidirezionali … il che sembra quello che stai cercando ( questo post di blog menziona anche la funzione)

E a partire da luglio 2011, c’è anche ” jackson-module-hibernate ” che potrebbe aiutare in alcuni aspetti della gestione degli oggetti di Hibernate, sebbene non necessariamente questo particolare (che richiede annotazioni).

Ora Jackson supporta l’evitamento dei cicli senza ignorare i campi:

Jackson – serializzazione di quadro con relazioni birionali (evitando cicli)

Questo ha funzionato perfettamente bene per me. Aggiungi l’annotazione @JsonIgnore sulla class figlio in cui menzioni il riferimento alla class genitore.

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

C’è ora un modulo Jackson (per Jackson 2) specificamente progettato per gestire i problemi di inizializzazione pigri di Hibernate durante la serializzazione.

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

Basta aggiungere la dipendenza (nota che ci sono diverse dipendenze per Hibernate 3 e Hibernate 4):

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

e quindi registra il modulo quando intializzi l’ObjectMapper di Jackson:

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

La documentazione al momento non è eccezionale. Vedi il codice Hibernate4Module per le opzioni disponibili.

Nel mio caso è bastato cambiare rapporto tra:

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

a:

 @OneToMany private List towns; 

un’altra relazione rimase come era:

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

Per me la soluzione migliore è usare @JsonView e creare filtri specifici per ogni scenario. È anche ansible utilizzare @JsonManagedReference e @JsonBackReference , tuttavia è una soluzione codificata a una sola situazione, in cui il proprietario fa sempre riferimento al lato proprietario e mai al contrario. Se si dispone di un altro scenario di serializzazione in cui è necessario re-annotare l’attributo in modo diverso, non sarà ansible.

Problema

Consente di utilizzare due classi, Company e Employee cui si ha una dipendenza ciclica tra di esse:

 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 la class di test che tenta di serializzare usando 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 esegui questo codice, otterrai:

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

Soluzione usando `@ JsonView`

@JsonView consente di utilizzare i filtri e scegliere i campi da includere durante la serializzazione degli oggetti. Un filtro è solo un riferimento di class usato come identificatore. Quindi, prima creiamo i filtri:

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

Ricorda, i filtri sono classi fittizie, utilizzate solo per specificare i campi con l’annotazione @JsonView , in modo da poterne creare quante ne vuoi e averne bisogno. Vediamolo in azione, ma prima dobbiamo annotare la nostra class Company :

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

e cambiare il test in modo che il serializzatore usi la vista:

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

Ora se si esegue questo codice, il problema della ricorsione infinita viene risolto, poiché si è esplicitamente detto che si desidera serializzare gli attributi annotati con @JsonView(Filter.CompanyData.class) .

Quando raggiunge il riferimento posteriore per la società nel Employee , controlla che non sia annotato e ignora la serializzazione. Hai anche una soluzione potente e flessibile per scegliere quali dati vuoi inviare tramite le tue API REST.

Con Spring è ansible annotare i metodi dei controller REST con il filtro @JsonView desiderato e la serializzazione viene applicata in modo trasparente all’object restituito.

Ecco le importazioni utilizzate nel caso in cui sia necessario controllare:

 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; 

Assicurati di usare com.fasterxml.jackson ovunque. Ho passato molto tempo a scoprirlo.

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

Quindi utilizzare @JsonManagedReference e @JsonBackReference .

Infine, puoi serializzare il tuo modello su JSON:

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

puoi usare il pattern DTO creare la class TraineeDTO senza alcuna hiberbnate di anotation e puoi usare jackson mapper per convertire Trainee in TraineeDTO e bingo il messaggio di errore disapeare 🙂

Ho anche incontrato lo stesso problema. Ho usato il tipo di generatore ObjectIdGenerators.PropertyGenerator.class @JsonIdentityInfo .

Questa è la mia soluzione:

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

È ansible utilizzare @JsonIgnore , ma questo ignorerà i dati JSON a cui è ansible accedere a causa della relazione Chiave esterna. Pertanto, se si richiedono i dati della chiave esterna (la maggior parte del tempo richiesto), @JsonIgnore non ti aiuterà. In tale situazione si prega di seguire la seguente soluzione.

stai ricevendo una ricorsione infinita, a causa della class BodyStat che si riferisce nuovamente all’object Trainee

Bodystat

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

apprendista

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

Pertanto, devi commentare / omettere la parte sopra in Apprendista

Ho avuto questo problema, ma non volevo utilizzare l’annotazione nelle mie entity framework, quindi ho risolto creando un costruttore per la mia class, questo costruttore non deve avere un riferimento alle quadro che fanno riferimento a questa quadro. Diciamo questo scenario.

 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 si tenta di inviare alla vista la class B o A con @ResponseBody , potrebbe causare un loop infinito. Puoi scrivere un costruttore nella tua class e creare una query con il tuo entityManager come questo.

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

Questa è la class con il costruttore.

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

Tuttavia, ci sono alcune costrizioni su questa soluzione, come puoi vedere, nel costruttore non ho fatto riferimento a List bs perché Hibernate non lo consente, almeno nella versione 3.6.10.Final , quindi quando ho bisogno per mostrare entrambe le quadro in una vista faccio quanto segue.

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

L’altro problema con questa soluzione è che se si aggiunge o si rimuove una proprietà è necessario aggiornare il costruttore e tutte le query.

Nel caso in cui si utilizzi Spring Data Rest, il problema può essere risolto creando dei repository per ogni quadro coinvolta nei riferimenti ciclici.

@JsonIgnoreProperties è la risposta.

Usa qualcosa come questo ::

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

Funzionando bene per me Risolvi il problema di JSON Infinite Recursion quando lavori con Jackson

Questo è ciò che ho fatto in OneToMany e ManyToOne Mapping

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