При попытке преобразовать объект JPA, который имеет двунаправленную ассоциацию в JSON, я продолжаю получать
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError)
Все, что я нашел, это эта тема, которая в основном завершается рекомендацией избегать двунаправленных ассоциаций. У кого-нибудь есть идея для обхода этой весенней ошибки?
------ РЕДАКТИРОВАТЬ 2010-07-24 16:26:22 -------
CodeSnippets:
Бизнес-объект 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 ...
Бизнес-объект 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;
контроллер:
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;
}
}
JPA-реализация стажера 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
вTrainee.bodyStats
.@JsonIgnoreProperties
это самое чистое решение. Проверьте ответ Zammel AlaaEddine для более подробной информации.Ответы:
Вы можете использовать,
@JsonIgnore
чтобы разорвать цикл.источник
@JsonIgnore
вы введете значение «внешний ключ», то при обновлении сущности вы получите нулевое значение ...JsonIgnoreProperties [обновление 2017 года]:
Теперь вы можете использовать JsonIgnoreProperties для подавления сериализации свойств (во время сериализации) или игнорировать обработку прочитанных свойств JSON (во время десериализации) . Если это не то, что вы ищете, пожалуйста, продолжайте читать ниже.
(Спасибо As Zammel AlaaEddine за указание на это).
JsonManagedReference и JsonBackReference
Начиная с Jackson 1.6 вы можете использовать две аннотации для решения проблемы бесконечной рекурсии, не игнорируя геттеры / сеттеры во время сериализации:
@JsonManagedReference
и@JsonBackReference
.объяснение
Чтобы Джексон работал хорошо, не следует сериализовать одну из двух сторон отношения, чтобы избежать бесконечного цикла, который вызывает ошибку стека переполнения.
Итак, Джексон берет переднюю часть ссылки (вашу
Set<BodyStat> bodyStats
в классе Trainee) и преобразует ее в json-подобный формат хранения; это так называемый процесс сортировки . Затем Джексон ищет заднюю часть ссылки (т.Trainee trainee
Е. В классе BodyStat) и оставляет ее как есть, не сериализовав ее. Эта часть отношений будет перестроена во время десериализации ( демаршаллинга ) прямой ссылки.Вы можете изменить свой код следующим образом (я пропускаю ненужные части):
Бизнес-объект 1:
Бизнес-объект 2:
Теперь все должно работать правильно.
Если вы хотите больше информации, я написал статью о проблемах Keenformatics в Json и Jackson Stackoverflow в своем блоге.
РЕДАКТИРОВАТЬ:
Другой полезной аннотацией, которую вы можете проверить, является @JsonIdentityInfo : используя ее, каждый раз, когда Джексон сериализует ваш объект, он добавляет к нему идентификатор (или другой выбранный вами атрибут), чтобы он не полностью «сканировал» его снова каждый раз. Это может быть полезно, если у вас есть цепочка между более взаимосвязанными объектами (например: Order -> OrderLine -> User -> Order и снова).
В этом случае вы должны быть осторожны, так как вам может потребоваться прочитать атрибуты вашего объекта более одного раза (например, в списке продуктов с большим количеством продуктов, имеющих одного и того же продавца), и эта аннотация не позволяет вам сделать это. Я предлагаю всегда просматривать журналы Firebug, чтобы проверить ответ Json и посмотреть, что происходит в вашем коде.
Источники:
источник
@JsonIgnore
обратная ссылка.@JsonIgnore
.Новая аннотация @JsonIgnoreProperties решает многие проблемы с другими опциями.
Проверьте это здесь. Это работает так же, как в документации:
http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html
источник
Также с помощью Jackson 2.0+ вы можете использовать
@JsonIdentityInfo
. Это работало намного лучше для моих классов гибернации, чем@JsonBackReference
и@JsonManagedReference
, которые имели проблемы для меня и не решили проблему. Просто добавьте что-то вроде:и это должно работать.
источник
@JsonIdentityInfo
в моем ответе выше.@JsonIdentityInfo
аннотацию к своим сущностям, но она не решает проблему рекурсии. Только@JsonBackReference
и@JsonManagedReference
решает, но они удаляют сопоставленные свойства из JSON.Кроме того, Jackson 1.6 имеет поддержку для обработки двунаправленных ссылок ... что похоже на то, что вы ищете ( эта запись в блоге также упоминает эту функцию)
По состоянию на июль 2011 года существует также « jackson-module-hibernate », который может помочь в некоторых аспектах работы с объектами Hibernate, хотя не обязательно именно этот (который требует аннотаций).
источник
Теперь Джексон поддерживает обход циклов без игнорирования полей:
Джексон - сериализация сущностей с двунаправленными отношениями (избегая циклов)
источник
Это прекрасно сработало для меня. Добавьте аннотацию @JsonIgnore в дочерний класс, где вы упоминаете ссылку на родительский класс.
источник
@JsonIgnore
игнорирует этот атрибут от получения на стороне клиента. Что если мне нужен этот атрибут с его дочерним элементом (если у него есть дочерний элемент)?Теперь есть модуль Jackson (для Jackson 2), специально разработанный для решения проблем отложенной инициализации Hibernate при сериализации.
https://github.com/FasterXML/jackson-datatype-hibernate
Просто добавьте зависимость (обратите внимание, что существуют разные зависимости для Hibernate 3 и Hibernate 4):
и затем зарегистрируйте модуль при инициализации ObjectMapper Джексона:
Документация в настоящее время не очень хорошая. См. Код Hibernate4Module для доступных опций.
источник
У меня хорошо получается Решить проблему бесконечной рекурсии Json при работе с Джексоном
Это то, что я сделал в OneToMany и ManyToOne Mapping.
источник
@JsonManagedReference
,@JsonBackReference
не дает вам данные , связанные с@OneToMany
и@ManyToOne
сценарии, а также при использовании@JsonIgnoreProperties
же пропустить соответствующие данные сущности. Как это решить?Для меня лучшее решение - использовать
@JsonView
и создавать специальные фильтры для каждого сценария. Вы также можете использовать@JsonManagedReference
и@JsonBackReference
, тем не менее, это жестко запрограммированное решение только для одной ситуации, когда владелец всегда ссылается на сторону владельца, а не наоборот. Если у вас есть другой сценарий сериализации, где вам нужно по-другому аннотировать атрибут, вы не сможете.проблема
Давайте использовать два класса,
Company
иEmployee
где у вас есть циклическая зависимость между ними:И тестовый класс, который пытается сериализовать с помощью
ObjectMapper
( Spring Boot ):Если вы запустите этот код, вы получите:
Решение с использованием `@ JsonView`
@JsonView
позволяет использовать фильтры и выбирать, какие поля следует включать при сериализации объектов. Фильтр - это просто ссылка на класс, используемая в качестве идентификатора. Итак, давайте сначала создадим фильтры:Помните, что фильтры - это фиктивные классы, которые используются только для указания полей с
@JsonView
аннотацией, поэтому вы можете создавать столько, сколько захотите и нуждаетесь. Давайте посмотрим на это в действии, но сначала нам нужно аннотировать нашCompany
класс:и измените Test, чтобы сериализатор использовал View:
Теперь, если вы запустите этот код, проблема бесконечной рекурсии будет решена, потому что вы прямо сказали, что хотите просто сериализовать атрибуты, которые были помечены
@JsonView(Filter.CompanyData.class)
.Когда он достигает обратной ссылки для компании в
Employee
, он проверяет, что это не аннотировано, и игнорирует сериализацию. У вас также есть мощное и гибкое решение для выбора данных, которые вы хотите отправить через API REST.С помощью Spring вы можете аннотировать ваши методы REST Controllers требуемым
@JsonView
фильтром, и сериализация прозрачно применяется к возвращаемому объекту.Вот импорт, используемый в случае, если вам нужно проверить:
источник
@JsonIgnoreProperties является ответом.
Используйте что-то вроде этого:
источник
@JsonManagedReference
,@JsonBackReference
не дает вам данные , связанные с@OneToMany
и@ManyToOne
сценарии, а также при использовании@JsonIgnoreProperties
же пропустить соответствующие данные сущности. Как это решить?В моем случае было достаточно изменить отношение с:
чтобы:
другое отношение осталось прежним:
источник
Убедитесь, что вы используете com.fasterxml.jackson везде. Я потратил много времени, чтобы выяснить это.
Тогда используйте
@JsonManagedReference
и@JsonBackReference
.Наконец, вы можете сериализовать вашу модель в JSON:
источник
Вы можете использовать @JsonIgnore , но при этом будут игнорироваться данные json, к которым можно получить доступ из-за отношения внешнего ключа. Поэтому, если вы запрашиваете данные внешнего ключа (большую часть времени мы требуем), то @JsonIgnore вам не поможет. В такой ситуации следуйте приведенному ниже решению.
вы получаете бесконечную рекурсию, из-за BodyStat класса снова со ссылкой на Trainee объект
BodyStat
стажер
Поэтому вы должны оставить комментарий / опустить вышеупомянутую часть в Trainee
источник
Я также встретил ту же проблему. Я использовал
@JsonIdentityInfo
«sObjectIdGenerators.PropertyGenerator.class
тип генератора.Это мое решение:
источник
Вы должны использовать @JsonBackReference с сущностью @ManyToOne и @JsonManagedReference с @onetomany, содержащим классы сущностей.
источник
Вы можете использовать DTO шаблон создания класса TraineeDTO без какой-либо аннотации hiberbnate, и вы можете использовать Джексон маппер для преобразования Trainee в TraineeDTO и удаления сообщения об ошибке :)
источник
Если вы не можете игнорировать свойство, попробуйте изменить видимость поля. В нашем случае у нас был старый код, по-прежнему отправляющий сущности со связями, поэтому в моем случае это было исправлением:
источник
У меня была эта проблема, но я не хотел использовать аннотации в своих сущностях, поэтому я решил, создав конструктор для своего класса, этот конструктор не должен иметь ссылку на сущности, которые ссылаются на эту сущность. Давайте скажем этот сценарий.
Если вы попытаетесь отправить в представление класс
B
илиA
вместе с@ResponseBody
ним может вызвать бесконечный цикл. Вы можете написать конструктор в своем классе и создать запрос сentityManager
таким же образом.Это класс с конструктором.
Тем не менее, есть некоторые ограничения в этом решении, как вы можете видеть, в конструкторе я не делал ссылки на List bs, это потому, что Hibernate не разрешает это, по крайней мере, в версии 3.6.10.Final , поэтому, когда мне нужно чтобы показать обе сущности в представлении, я делаю следующее.
Другая проблема, связанная с этим решением, заключается в том, что если вы добавляете или удаляете свойство, вы должны обновить конструктор и все ваши запросы.
источник
Если вы используете Spring Data Rest, проблему можно решить, создав репозитории для каждой сущности, вовлеченной в циклические ссылки.
источник
Я опоздал, и это уже такая длинная тема. Но я потратил пару часов, пытаясь понять это, и хотел бы привести мой случай в качестве другого примера.
Я попробовал оба решения: JsonIgnore, JsonIgnoreProperties и BackReference, но, как ни странно, казалось, что они не были подобраны.
Я использовал Lombok и подумал, что, возможно, он мешает, поскольку он создает конструкторы и переопределяет toString (видел toString в стеке stackoverflowerror).
Наконец, это была не ошибка Ломбока - я использовал автоматическую генерацию сущностей JPA NetBeans из таблиц базы данных, не задумываясь об этом - ну, и одна из аннотаций, которые были добавлены к сгенерированным классам, была @XmlRootElement. Как только я удалил его, все начало работать. Ну что ж.
источник
Дело в том, чтобы разместить @JsonIgnore в том, в метод сеттера следующим образом. в моем случае.
Township.java
Village.java
источник