Я попытался создать отношения родитель-потомок одного и того же типа сущности. Проблема, с которой я столкнулся, заключалась в том, что при попытке выполнить запрос 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




У вас был бесконечный цикл, потому что каждый экземпляр вашего класса ссылается как на своего родителя, так и на своих дочерних элементов.
Таким образом, чтобы сериализовать дочерний элемент, вы должны сериализовать его родителя, а чтобы сериализовать родителя, вы должны сериализовать его дочерние элементы. Включая ребенка, с которым вы начали.
Добавленный вами тег @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 не требуется).
И это сделано.
Конечно. У вас есть несколько способов: повторно связать их на стороне клиента, которая использует JSON. Для каждого родителя вы знаете своих детей. Таким образом, повторяя дочерние элементы родителя, вы можете установить их родителя. Другой подход заключается в использовании пользовательского сериализатора JSON, который сериализует поле parent как целое число или строку, представляющую его идентификатор (@JsonSerialize в поле).
Это известная проблема. Он называется: «Задача бесконечной рекурсии Джексона». Использование @JsonIgnore — одно из возможных решений проблемы бесконечной рекурсии. Люди оставили действительно хороший ответ на этот пост о том, почему @JsonIgnore решает бесконечный цикл; однако, если вам нужна дополнительная информация или другие решения этой проблемы, посетите этот сайт.
Спасибо! Теперь, зная это, могу ли я получить как родительские, так и дочерние данные для корзины с помощью запроса на получение, используя эту модель? «Корзина» по сути такая же, как твит, поэтому я хочу иметь возможность показывать ответы на корзину и разговор, ведущий к этой корзине.