Почему этот @JsonIgnore исправляет мой бесконечный цикл?

Я попытался создать отношения родитель-потомок одного и того же типа сущности. Проблема, с которой я столкнулся, заключалась в том, что при попытке выполнить запрос GET для извлечения объекта, у которого дочерние элементы не пусты, я получал ошибку переполнения стека из-за того, что выглядело как бесконечный цикл. После некоторого осмотра я нашел исправление, просто добавив @JsonIgnore в родительское поле в классе Entity. Мой вопрос: почему это решает эту проблему? В чем была проблема с самого начала?

@Entity
@Table(name = "trash")
public class Trash {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    private String username;

    @NotBlank
    private String message;

    private Long likes;

    @ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
    @JsonIgnore
    private Trash parent;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval=true, fetch = FetchType.LAZY)
    private Collection<Trash> children;

Вот ошибка, которую я получил

2019-07-19 10:29:55.867 ERROR 14156 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: com.example.litter.model.Trash["parent"]->com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.litter.model.Trash["parent"]->com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.litter.model.Trash["parent"]->com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.litter.model.Trash["parent"]->com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.litter.model.Trash["parent"]->com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.litter.model.Trash["parent"]->com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.litter.model.Trash["parent"]-
repeats for awhile....
>com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.litter.model.Trash["parent"])] with root cause

java.lang.StackOverflowError: null
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
0
987
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

У вас был бесконечный цикл, потому что каждый экземпляр вашего класса ссылается как на своего родителя, так и на своих дочерних элементов.

Таким образом, чтобы сериализовать дочерний элемент, вы должны сериализовать его родителя, а чтобы сериализовать родителя, вы должны сериализовать его дочерние элементы. Включая ребенка, с которым вы начали.

Добавленный вами тег @JsonIgnore устраняет эту проблему, потому что теперь для сериализации дочернего элемента вам не нужно сериализовать его родителя. Больше нет бесконечного цикла.

У вас есть циклическая ссылка между вашими родительскими и дочерними объектами: каждый ссылается друг на друга. Поскольку JSON создается путем расширения любых объектов, на которые есть ссылки (без ссылок), вы получите бесконечную иерархию через родительские/дочерние поля.

Когда вы игнорируете одно из полей (родительское в вашем случае), обход осуществляется только к дочерним элементам, но не назад, поэтому он больше не является круговым.

Ответ принят как подходящий

Исключение поясняет, что:

Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) 
(through reference chain: 
>com.example.litter.model.Trash["parent"]-
>com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]-
>org.hibernate.collection.internal.PersistentBag[0]- >com.example.litter.model.Trash["parent"]-
>com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]-
>org.hibernate.collection.internal.PersistentBag[0]-
>com.example.litter.model.Trash["parent"]-
>com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]-
>org.hibernate.collection.internal.PersistentBag[0]-
>com.example.litter.model.Trash["parent"]-
and so for...

Сериализация поля parent запускает сериализацию поля children, которая сама запускает сериализацию поля parent, и так для...
Это может выглядеть достаточно абстрактно, поэтому вот конкретный пример.

Предположим, эти три экземпляра Trash: один родитель имеет двух дочерних элементов.

Trash-1 (children = 2,3)

Trash-2 (parent : 1)

Trash-3 (parent : 1)

Предположим, вы хотите сериализовать Trash-1 (родительский).
Вот как поступает Джексон:

1) Джексон пытается сериализовать поля Trash-1, которые нас интересуют поля children (Trash-2 и Trash-3). Так начинается сериализация children.
2) Но для сериализации Trash-2 Джексону также необходимо сериализовать свои поля, а именно поле parent, то есть Trash-1 !
3) Возвращаемся к первому шагу.

И так продолжается до тех пор, пока рекурсия не станет слишком важной и Джексон вас не остановит.

Аннотируя parent или children с помощью @JsonIgnore, вы просите Джексона пропустить одну из двух сериализаций, и это нарушает рекурсию. Например, на parent это даст:

1) Джексон пытается сериализовать поля Trash-1, которые нас интересуют поля children (Trash-2 и Trash-3). Так начинается сериализация children.
2) Trash-2 сериализуется (сериализация поля parent не требуется).
2) Trash-3 сериализуется (сериализация поля parent не требуется).

И это сделано.

Спасибо! Теперь, зная это, могу ли я получить как родительские, так и дочерние данные для корзины с помощью запроса на получение, используя эту модель? «Корзина» по сути такая же, как твит, поэтому я хочу иметь возможность показывать ответы на корзину и разговор, ведущий к этой корзине.

Eric Tercasio 19.07.2019 17:33

Конечно. У вас есть несколько способов: повторно связать их на стороне клиента, которая использует JSON. Для каждого родителя вы знаете своих детей. Таким образом, повторяя дочерние элементы родителя, вы можете установить их родителя. Другой подход заключается в использовании пользовательского сериализатора JSON, который сериализует поле parent как целое число или строку, представляющую его идентификатор (@JsonSerialize в поле).

davidxxx 19.07.2019 18:30

Это известная проблема. Он называется: «Задача бесконечной рекурсии Джексона». Использование @JsonIgnore — одно из возможных решений проблемы бесконечной рекурсии. Люди оставили действительно хороший ответ на этот пост о том, почему @JsonIgnore решает бесконечный цикл; однако, если вам нужна дополнительная информация или другие решения этой проблемы, посетите этот сайт.

Другие вопросы по теме