Próbuję przekonwertować obiekt JPA, który ma dwukierunkowe powiązanie na JSON, ciągle się pojawia
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError)
Znalazłem tylko ten wątek, który zasadniczo kończy się zaleceniem unikania skojarzeń dwukierunkowych. Czy ktoś ma pomysł na obejście tego wiosennego błędu?
------ EDYCJA 2010-07-24 16:26:22 -------
Fragmenty kodu:
Obiekt biznesowy 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 ...
Obiekt biznesowy 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;
Kontroler:
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;
}
}
Wdrożenie JPA stażysty 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
doTrainee.bodyStats
.@JsonIgnoreProperties
jest najczystszym rozwiązaniem. Sprawdź odpowiedź Zammel AlaaEddine jest więcej szczegółów.Odpowiedzi:
Możesz użyć,
@JsonIgnore
aby przerwać cykl.źródło
@JsonIgnore
w „kluczu obcym” podczas aktualizacji encji ...JsonIgnoreProperties [aktualizacja 2017]:
Możesz teraz użyć JsonIgnoreProperties, aby ukryć serializację właściwości (podczas serializacji) lub zignorować przetwarzanie odczytanych właściwości JSON (podczas deserializacji) . Jeśli nie tego szukasz, czytaj dalej poniżej.
(Dzięki As Zammel AlaaEddine za zwrócenie na to uwagi).
JsonManagedReference i JsonBackReference
Od wersji Jackson 1.6 możesz użyć dwóch adnotacji, aby rozwiązać nieskończony problem rekurencji bez ignorowania programów pobierających / ustawiających podczas serializacji:
@JsonManagedReference
i@JsonBackReference
.Wyjaśnienie
Aby Jackson działał dobrze, jedna z dwóch stron relacji nie powinna być serializowana, aby uniknąć pętli nieskończoności, która powoduje błąd przepełnienia stosu.
Tak więc Jackson bierze do przodu część referencji (twoja
Set<BodyStat> bodyStats
klasa Trainee) i konwertuje ją na format pamięci podobny do jsona; jest to tak zwany proces gromadzenia . Następnie Jackson szuka tylnej części referencji (tj.Trainee trainee
W klasie BodyStat) i pozostawia ją taką, jaka jest, nie serializując jej. Ta część relacji zostanie przebudowana podczas deserializacji ( odblokowania ) odwołania do przodu.Możesz zmienić swój kod w ten sposób (pomijam niepotrzebne części):
Obiekt biznesowy 1:
Obiekt biznesowy 2:
Teraz wszystko powinno działać poprawnie.
Jeśli chcesz uzyskać więcej informacji, napisałem artykuł o problemach Json i Jackson Stackoverflow na moim blogu Keenformatics .
EDYTOWAĆ:
Inną przydatną adnotacją, którą można sprawdzić, jest @JsonIdentityInfo : używając go, za każdym razem, gdy Jackson serializuje Twój obiekt, doda do niego identyfikator (lub inny wybrany atrybut), aby za każdym razem nie „skanował” go ponownie. Może to być przydatne, gdy istnieje pętla łańcuchowa między bardziej powiązanymi obiektami (na przykład: Zamówienie -> Linia zamówienia -> Użytkownik -> Zamówienie i ponownie).
W takim przypadku musisz zachować ostrożność, ponieważ może być konieczne odczytanie atrybutów obiektu więcej niż jeden raz (na przykład na liście produktów zawierającej więcej produktów, które mają tego samego sprzedawcę), a ta adnotacja nie pozwala tego zrobić. Sugeruję, aby zawsze sprawdzać dzienniki firebug, aby sprawdzić odpowiedź Jsona i zobaczyć, co się dzieje w kodzie.
Źródła:
źródło
@JsonIgnore
powrót do referencji.@JsonIgnore
.Nowa adnotacja @JsonIgnoreProperties rozwiązuje wiele problemów z innymi opcjami.
Sprawdź to tutaj. Działa tak jak w dokumentacji:
http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html
źródło
Możesz także użyć Jackson 2.0+
@JsonIdentityInfo
. Działa to znacznie lepiej dla moich klas hibernacji niż@JsonBackReference
i@JsonManagedReference
, które miały dla mnie problemy i nie rozwiązały problemu. Po prostu dodaj coś takiego:i powinno działać.
źródło
@JsonIdentityInfo
powyższej odpowiedzi.@JsonIdentityInfo
adnotację do moich bytów, ale to nie rozwiązuje problemu rekurencji. Tylko@JsonBackReference
i@JsonManagedReference
rozwiązuje, ale są odwzorowywane usuń właściwości z JSON.Ponadto Jackson 1.6 obsługuje obsługę dwukierunkowych odniesień ... co wydaje się być tym, czego szukasz ( ten wpis na blogu również wspomina o tej funkcji)
Od lipca 2011 r. Istnieje także „ jackson-module-hibernacja ”, który może pomóc w niektórych aspektach radzenia sobie z obiektami Hibernacji, choć niekoniecznie w tym konkretnym (który wymaga adnotacji).
źródło
Teraz Jackson obsługuje unikanie cykli bez ignorowania pól:
Jackson - serializacja bytów z relacjami dwukierunkowymi (unikanie cykli)
źródło
Dla mnie to działało idealnie. Dodaj adnotację @JsonIgnore do klasy podrzędnej, w której wspominasz o odwołaniu do klasy nadrzędnej.
źródło
@JsonIgnore
ignoruje ten atrybut przed pobraniem po stronie klienta. Co jeśli potrzebuję tego atrybutu z jego dzieckiem (jeśli ma dziecko)?Jest teraz moduł Jacksona (dla Jackson 2) specjalnie zaprojektowany do obsługi leniwych problemów z inicjalizacją Hibernacji podczas serializacji.
https://github.com/FasterXML/jackson-datatype-hibernate
Po prostu dodaj zależność (zauważ, że dla Hibernacji 3 i Hibernacji 4 istnieją różne zależności):
a następnie zarejestruj moduł podczas inicjalizacji ObjectMapper Jacksona:
Dokumentacja obecnie nie jest świetna. Zobacz kod Hibernate4Module, aby uzyskać dostępne opcje.
źródło
Działa dobrze dla mnie Rozwiąż problem z Json Infinite Recursion podczas pracy z Jacksonem
Tak zrobiłem w mapowaniu oneToMany i ManyToOne
źródło
@JsonManagedReference
,@JsonBackReference
że nie daje ci danych powiązanych@OneToMany
i@ManyToOne
scenariusza, również przy użyciu@JsonIgnoreProperties
pomija powiązane dane encji. Jak to rozwiązać?Dla mnie najlepszym rozwiązaniem jest użycie
@JsonView
i stworzenie określonych filtrów dla każdego scenariusza. Można także użyć@JsonManagedReference
a@JsonBackReference
jednak jest to ustalony rozwiązanie tylko jednej sytuacji, gdzie właściciel zawsze odwołuje się do posiadającą bok, nigdy odwrotnie. Jeśli masz inny scenariusz serializacji, w którym musisz zmienić adnotację w inny sposób, nie będziesz w stanie tego zrobić.Problem
Użyjmy dwóch klas
Company
iEmployee
tam, gdzie istnieje między nimi cykliczna zależność:A klasa testowa, która próbuje serializować przy użyciu
ObjectMapper
( Spring Boot ):Jeśli uruchomisz ten kod, otrzymasz:
Rozwiązanie za pomocą `@ JsonView`
@JsonView
umożliwia korzystanie z filtrów i wybieranie pól, które mają być uwzględnione podczas serializacji obiektów. Filtr to po prostu odwołanie do klasy używane jako identyfikator. Stwórzmy najpierw filtry:Pamiętaj, że filtry to atrapy klasy, używane tylko do określania pól z
@JsonView
adnotacją, dzięki czemu możesz utworzyć tyle, ile chcesz i potrzebujesz. Zobaczmy to w akcji, ale najpierw musimy opisać nasząCompany
klasę:i zmień Test, aby serializator mógł korzystać z widoku:
Teraz, jeśli uruchomisz ten kod, problem nieskończonej rekurencji zostanie rozwiązany, ponieważ wyraźnie powiedziałeś, że chcesz po prostu serializować atrybuty, które zostały opatrzone adnotacjami
@JsonView(Filter.CompanyData.class)
.Gdy osiągnie referencję wstecz dla firmy w
Employee
, sprawdza, czy nie ma adnotacji i ignoruje serializację. Masz również potężne i elastyczne rozwiązanie do wyboru danych, które chcesz wysłać za pośrednictwem interfejsów API REST.Za pomocą Springa możesz opisywać metody kontrolerów REST za pomocą pożądanego
@JsonView
filtra, a serializacja jest stosowana w sposób przezroczysty do zwracanego obiektu.Oto importowane dane, które należy sprawdzić:
źródło
@JsonIgnoreProperties jest odpowiedzią.
Użyj czegoś takiego:
źródło
@JsonManagedReference
,@JsonBackReference
że nie daje ci danych powiązanych@OneToMany
i@ManyToOne
scenariusza, również przy użyciu@JsonIgnoreProperties
pomija powiązane dane encji. Jak to rozwiązać?W moim przypadku wystarczyło zmienić relację z:
do:
inna relacja pozostała taka, jaka była:
źródło
Pamiętaj, aby wszędzie używać com.fasterxml.jackson . Spędziłem dużo czasu, aby się tego dowiedzieć.
Następnie użyj
@JsonManagedReference
i@JsonBackReference
.Wreszcie możesz serializować swój model do JSON:
źródło
Możesz użyć @JsonIgnore , ale to zignoruje dane json, do których można uzyskać dostęp z powodu relacji klucza obcego. Dlatego jeśli potrzebujesz danych klucza obcego (przez większość wymaganego czasu), to @JsonIgnore ci nie pomoże. W takiej sytuacji postępuj zgodnie z poniższym rozwiązaniem.
otrzymujesz nieskończoną rekurencję, ponieważ klasa BodyStat ponownie odwołuje się do obiektu Trainee
BodyStat
Stażysta
Dlatego musisz skomentować / pominąć powyższą część w stażu
źródło
Spotkałem również ten sam problem. Kiedyś
@JsonIdentityInfo
„sObjectIdGenerators.PropertyGenerator.class
typ generatora.To jest moje rozwiązanie:
źródło
Powinieneś używać @JsonBackReference z encją @ManyToOne i @JsonManagedReference z @onetomany zawierającym klasy encji.
źródło
możesz użyć wzorca DTO stworzyć klasę TraineeDTO bez hibernacji anotacji, a także użyć mapera Jacksona do konwersji Stażysty na TraineeDTO i bingo komunikat o błędzie zniknie :)
źródło
Jeśli nie możesz zignorować właściwości, spróbuj zmodyfikować widoczność pola. W naszym przypadku nadal mieliśmy stary kod przesyłający jednostki z relacją, więc w moim przypadku była to poprawka:
źródło
Miałem ten problem, ale nie chciałem używać adnotacji w moich bytach, więc rozwiązałem to, tworząc konstruktor dla mojej klasy, ten konstruktor nie może mieć odwołania do bytów, które odwołują się do tego bytu. Powiedzmy, że ten scenariusz.
Jeśli spróbujesz wysłać do widoku, klasa
B
lubA
wraz z@ResponseBody
nią może spowodować nieskończoną pętlę. Możesz napisać konstruktor w swojej klasie i utworzyć zapytanie wentityManager
ten sposób.To jest klasa z konstruktorem.
Istnieją jednak pewne ograniczenia dotyczące tego rozwiązania, jak widać, w konstruktorze, o którym nie wspomniałem Listy bs, ponieważ Hibernate nie pozwala na to, przynajmniej w wersji 3.6.10. Końcowe , więc kiedy potrzebuję aby pokazać oba podmioty w widoku, wykonuję następujące czynności.
Innym problemem związanym z tym rozwiązaniem jest to, że jeśli dodajesz lub usuwasz właściwość, musisz zaktualizować konstruktor i wszystkie zapytania.
źródło
W przypadku korzystania z Spring Data Rest problem można rozwiązać, tworząc repozytoria dla każdej jednostki zaangażowanej w cykliczne odwołania.
źródło
Jestem spóźniony i to już tak długi wątek. Ale spędziłem kilka godzin, próbując to rozgryźć i chciałbym podać moją sprawę jako kolejny przykład.
Wypróbowałem zarówno rozwiązania JsonIgnore, JsonIgnoreProperties, jak i BackReference, ale o dziwo było tak, jakby ich nie odebrano.
Użyłem Lomboka i pomyślałem, że może to przeszkadza, ponieważ tworzy konstruktory i zastępuje toString (zobaczył toString w stosie stackoverflowerror).
Wreszcie nie była to wina Lomboka - użyłem automatycznego generowania jednostek JPA przez NetBeans z tabel bazy danych, nie zastanawiając się nad tym dobrze - a jedną z adnotacji dodanych do wygenerowanych klas było @XmlRootElement. Po usunięciu wszystko zaczęło działać. No cóż.
źródło
Chodzi o to, aby umieścić @JsonIgnore w metodzie ustawiającej w następujący sposób. w moim przypadku.
Township.java
Village.java
źródło