Spring Boot бесконечная рекурсия [дубликат]

Статические методы не связаны с экземпляром, поэтому они не могут получить доступ к каким-либо нестатическим полям в классе.

Вы использовали бы статический метод, если метод не использует никаких полей (или только статических полей) класса.

Если используются нестатические поля класса, вы должны использовать нестатический метод.

288
задан Filip Spiridonov 13 March 2013 в 09:46
поделиться

18 ответов

Вы можете использовать @JsonIgnore , чтобы разорвать цикл.

210
ответ дан anothershrubery 20 August 2018 в 13:17
поделиться
  • 1
    @Ben: На самом деле я не знаю. Возможно, его поддержка не была включена: wiki.fasterxml.com/JacksonJAXBAnnotations – axtavt 1 October 2010 в 16:51
  • 2
    Так как у Jackson 1.6 есть лучшее решение, вы можете использовать две новые аннотации для решения проблемы бесконечной рекурсии без игнорирования геттеров / сеттеров во время сериализации. Подробнее см. Мой ответ ниже. – Kurt Bourbaki 5 February 2014 в 14:00
  • 3
    @axtavt Спасибо за прекрасный ответ. Кстати, я пришел с другим решением: вы можете просто избежать создания getter для этого значения, поэтому Spring не сможет получить к нему доступ при создании JSON (но я не думаю, что он подходит для каждого случая, поэтому ваш ответ лучше) – Semyon Danilov 28 February 2014 в 16:58
  • 4
    Все вышеперечисленные решения, похоже, нуждаются в изменении объектов домена путем добавления аннотаций. Если я сериализую сторонние классы, у меня нет возможности их модифицировать. Как я могу избежать этой проблемы? – Jianwu Chen 26 March 2015 в 10:41
  • 5
    Отличный пример, почему ссылки только ответы ужасны. – ChiefTwoPencils 2 July 2016 в 17:37

Я также встретил ту же проблему. Я использовал тип генератора @JsonIdentityInfo ObjectIdGenerators.PropertyGenerator.class.

Это мое решение:

@Entity
@Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Trainee extends BusinessObject {
...
1
ответ дан Arif Acar 20 August 2018 в 13:17
поделиться

Теперь Джексон поддерживает избегание циклов без игнорирования полей:

Джексон - сериализация объектов с двунаправленными отношениями (избегая циклов)

11
ответ дан Community 20 August 2018 в 13:17
поделиться

Новая аннотация @JsonIgnoreProperties разрешает многие проблемы с другими параметрами.

@Entity

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

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

Проверьте здесь. Он работает так же, как в документации: http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html

68
ответ дан CorayThan 20 August 2018 в 13:17
поделиться

вы можете использовать шаблон DTO для создания класса Trainee DTO без каких-либо аннотаций hibernate, и вы можете использовать jackson mapper для преобразования Trainee в TraineeDTO и bingo сообщение об ошибке disapeare:)

1
ответ дан Dahar Youssef 20 August 2018 в 13:17
поделиться

Для меня лучшим решением является использование @JsonView и создание определенных фильтров для каждого сценария. Вы также можете использовать @JsonManagedReference и @JsonBackReference, однако это жестко запрограммированное решение только в одной ситуации, где владелец всегда ссылается на сторону владельца и никогда не обращается. Если у вас есть другой сценарий сериализации, где вам нужно повторно аннотировать атрибут по-другому, вы не сможете.

Проблема

Давайте используем два класса: Company и Employee, где у вас есть циклическая зависимость между ними:

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

И тестовый класс, который пытается сериализоваться с помощью 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);
    }
}

Если вы запустите этот код вы получите:

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

Решение Использование `@ JsonView`

@JsonView позволяет вам использовать фильтры и выбирать, какие поля должны быть включены при сериализации объекты. Фильтр - это просто ссылка на класс, используемая в качестве идентификатора. Итак, давайте сначала создадим фильтры:

public class Filter {

    public static interface EmployeeData {};

    public static interface CompanyData extends EmployeeData {};

} 

Помните, что фильтры являются фиктивными классами, просто используемыми для указания полей с аннотацией @JsonView, поэтому вы можете создавать столько, сколько хотите и нуждаетесь. Давайте посмотрим на это в действии, но сначала нам нужно аннотировать наш класс Company:

public class Company {

    @JsonView(Filter.CompanyData.class)
    private Employee employee;

    public Company(Employee employee) {
        this.employee = employee;
    }

    public Employee getEmployee() {
        return employee;
    }
}

и изменить тест, чтобы сериализатор использовал View:

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

Теперь, если вы запустите этот код, проблема Infinite Recursion решена, потому что вы явно сказали, что хотите просто сериализовать атрибуты, которые были аннотированы с помощью @JsonView(Filter.CompanyData.class).

Когда он достигает обратной ссылки для компании в Employee она проверяет, что она не аннотируется и игнорирует сериализацию. У вас также есть мощное и гибкое решение для выбора данных, которые вы хотите отправить через API REST.

С Spring вы можете аннотировать методы REST Controllers с помощью желаемого фильтра @JsonView, и сериализация применяется прозрачно к возвращаемому объекту.

Вот импорт, использованный в случае, если вам нужно проверить:

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;
4
ответ дан fabioresner 20 August 2018 в 13:17
поделиться
  • 1
    Это хорошая статья, объясняющая множество альтернативных решений для рекурсии: baeldung.com/… – Hugo Baés 20 July 2017 в 14:59

В моем случае достаточно было изменить отношение от:

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

к:

@OneToMany
private List<Town> towns;

другое соотношение оставалось таким, каким оно было:

@ManyToOne
@JoinColumn(name = "county_id")
private County county;
4
ответ дан Klapsa2503 20 August 2018 в 13:17
поделиться
  • 1
    Я думаю, что лучше использовать решение Курта. Поскольку решение JoinColumn может завершаться в неработающих данных мертвых тел. – flosk8 6 September 2014 в 19:40
  • 2
    На самом деле это единственное, что помогло мне. Никаких других решений сверху не работало. Я все еще не уверен, почему ... – Deniss M. 25 March 2018 в 11:41

JsonIgnoreProperties [Обновление 2017]:

Теперь вы можете использовать JsonIgnoreProperties для подавить сериализацию свойств (во время сериализации) или игнорировать обработку свойств JSON, прочитанных (во время десериализации) . Если это не то, что вы ищете, пожалуйста, продолжайте читать ниже.

(Спасибо As Zammel AlaaEddine за указание этого).


JsonManagedReference и JsonBackReference

Начиная с Jackson 1.6 вы можете использовать две аннотации для решения проблемы бесконечной рекурсии без игнорирования геттеров / сеттеров во время сериализации: @JsonManagedReference и @JsonBackReference .

Объяснение

Для того, чтобы Джексон работал хорошо, одну из двух сторон отношения не следует сериализовать, чтобы избежать цикла infite, который вызывает ошибку stackoverflow.

Итак, Джексон берет переднюю часть ссылки (ваш Set<BodyStat> bodyStats в классе Trainee) и преобразует ее в json-подобный формат хранения; это так называемый процесс сортировки. Затем Джексон ищет заднюю часть ссылки (т. Е. Trainee trainee в классе BodyStat) и оставляет ее как есть, а не сериализует ее. Эта часть отношения будет реконструирована во время десериализации (unmarshalling) прямой ссылки.

Вы можете изменить свой код следующим образом (я пропускаю бесполезные части):

Бизнес-объект 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<BodyStat> bodyStats;

Бизнес-объект 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;

Теперь все должно работать должным образом.

Если вам нужна дополнительная информация, я написал статья о проблемах Json и Jackson Stackoverflow в Keenformatics , в моем блоге.

EDIT:

Еще одна полезная аннотация, которую вы могли проверить, - @JsonIdentityInfo : используя его, каждый раз, когда Джексон сериализует ваш объект, он добавляет к нему ID (или другой атрибут вашего выбора), чтобы он не полностью «сканировал» его снова каждый раз. Это может быть полезно, если у вас есть цепной цикл между более взаимосвязанными объектами (например: Order -> OrderLine -> User -> Order and over again).

В этом случае вы должны будьте осторожны, так как вам может потребоваться прочитать атрибуты вашего объекта более одного раза (например, в списке продуктов с большим количеством продуктов, которые используют один и тот же продавец), и эта аннотация предотвращает это. Я предлагаю всегда взглянуть на журналы firebug, чтобы проверить ответ Json и посмотреть, что происходит в вашем коде.

Источники:

457
ответ дан Kurt Bourbaki 20 August 2018 в 13:17
поделиться
  • 1
    Спасибо за четкий ответ. Это более удобное решение, чем установка @JsonIgnore на обратную ссылку. – Utku Özdemir 28 December 2013 в 04:46
  • 2
    Это, безусловно, правильный способ сделать это. Если вы делаете это так на стороне сервера, потому что вы используете Джексона там, не имеет значения, какой json mapper вы используете на стороне клиента, и вам не нужно устанавливать родительское руководство для родительских ссылок. Это просто работает. Благодарю Курта – flosk8 6 September 2014 в 19:38
  • 3
    Хорошее, подробное объяснение и, безусловно, лучший и более описательный подход, чем @JsonIgnore. – Piotr Nowicki 7 April 2015 в 06:57
  • 4
    Благодаря! @JsonIdentityInfo работал для циклических ссылок, которые включали несколько объектов во многих перекрывающихся циклах. – n00b 21 May 2015 в 06:45
  • 5
    Использовал '@JsonManagedReference' и '@JsonBackReference', две аннотации, которые спасли меня .. Большое вам спасибо. – erluxman 19 June 2017 в 11:07

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

@ManyToOne
@JoinColumn(name = "ID", nullable = false, updatable = false)
@JsonIgnore
private Member member;
7
ответ дан Manjunath BR 20 August 2018 в 13:17
поделиться
  • 1
    Можете ли вы рассказать о том, как это решает проблему? – Kmeixner 9 June 2015 в 21:35
  • 2
    Я думаю, что @JsonIgnore игнорирует этот атрибут от того, чтобы быть восстановленным на стороне клиента. Что делать, если мне нужен этот атрибут с его дочерним элементом (если он имеет дочерний элемент)? – Khasan 24-7 7 August 2015 в 12:52

Кроме того, используя Jackson 2.0+, вы можете использовать @JsonIdentityInfo. Это работало намного лучше для моих спящих классов, чем @JsonBackReference и @JsonManagedReference, что имело проблемы для меня и не решило проблему. Просто добавьте что-то вроде:

@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 {

, и оно должно работать.

41
ответ дан Marcus 20 August 2018 в 13:17
поделиться
  • 1
    Можете ли вы объяснить «Это работало намного лучше»? Есть ли проблема с управляемой ссылкой? – Utku Özdemir 28 December 2013 в 04:48
  • 2
    @ UtkuÖzdemir Я добавил подробности о @JsonIdentityInfo в своем ответе выше. – Kurt Bourbaki 31 December 2013 в 17:27
  • 3
    это лучшее решение, которое мы нашли до сих пор, потому что, когда мы использовали & quot; @ JsonManagedReference & Quot; метод get успешно возвращает значения без какой-либо ошибки stackoverflow. Но, когда мы пытались сохранить данные с помощью сообщения, она вернула ошибку 415 (неподдерживаемая ошибка носителя) – cuser 16 January 2014 в 20:17
  • 4
    Я добавил аннотацию @JsonIdentityInfo к моим объектам, но не решает проблему рекурсии. Решимы только @JsonBackReference и @JsonManagedReference, но они удаляют отображаемые свойства из JSON. – Oleg Abrazhaev 15 July 2016 в 11:15

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

public class A{
   private int id;
   private String code;
   private String name;
   private List<B> bs;
}

public class B{
   private int id;
   private String code;
   private String name;
   private A a;
}

Если вы попытаетесь отправить в представление класс B или A с @ResponseBody, это может вызвать бесконечный цикл. Вы можете написать конструктор в своем классе и создать запрос с вашим entityManager следующим образом.

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

Это класс с конструктором.

public class A{
   private int id;
   private String code;
   private String name;
   private List<B> bs;

   public A(){
   }

   public A(int id, String code, String name){
      this.id = id;
      this.code = code;
      this.name = name;
   }

}

Однако, есть некоторые сужения об этом решении, как вы можете видеть, в конструкторе я не ссылался на List bs, потому что Hibernate не разрешает его, по крайней мере, в версии 3.6.10.Final, поэтому, когда мне нужно показать оба объекта в представлении я делаю следующее.

public A getAById(int id); //THE A id

public List<B> getBsByAId(int idA); //the A id.

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

0
ответ дан Mogsdad 20 August 2018 в 13:17
поделиться

Если вы используете Spring Data Rest, проблема может быть решена путем создания репозиториев для каждого объекта, участвующего в циклических ссылках.

0
ответ дан Morik 20 August 2018 в 13:17
поделиться

Убедитесь, что вы используете com.fasterxml.jackson всюду. Я потратил много времени, чтобы узнать это.

<properties>
  <fasterxml.jackson.version>2.9.2</fasterxml.jackson.version>
</properties>

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>${fasterxml.jackson.version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>${fasterxml.jackson.version}</version>
</dependency>

Затем используйте @JsonManagedReference и @JsonBackReference.

Наконец, вы можете сериализовать свою модель на JSON:

import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(model);
2
ответ дан Mr. Polywhirl 20 August 2018 в 13:17
поделиться

Прекрасно работает для меня Устранить проблему Json Infinite Recursion при работе с Jackson

Это то, что я сделал в oneToMany и ManyToOne Mapping

@ManyToOne
@JoinColumn(name="Key")
@JsonBackReference
private LgcyIsp Key;


@OneToMany(mappedBy="LgcyIsp ")
@JsonManagedReference
private List<Safety> safety;
0
ответ дан Prabu M 20 August 2018 в 13:17
поделиться
  • 1
    Я использовал отображение hibernate в приложении весенней загрузки – Prabu M 28 May 2018 в 05:18

Вы можете использовать @JsonIgnore, но это будет игнорировать json-данные, к которым можно получить доступ из-за отношения внешнего ключа. Поэтому, если вы запрашиваете данные внешнего ключа (большую часть времени мы требуем), то @JsonIgnore вам не поможет. В этой ситуации следуйте приведенному ниже решению.

вы получаете бесконечную рекурсию из-за того, что класс BodyStat снова ссылается на объект Trainee

BodyStat

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

Ученик

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

Поэтому вам нужно прокомментировать / пропустить указанную выше часть у Стажера

1
ответ дан RAJ KASHWAN 20 August 2018 в 13:17
поделиться

Теперь есть модуль Jackson (для Jackson 2), специально предназначенный для обработки проблем с инициализацией Hibernate при инициализации.

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

Просто добавьте зависимость (обратите внимание, что существуют разные зависимости для Hibernate 3 и Hibernate 4):

<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-hibernate4</artifactId>
  <version>2.4.0</version>
</dependency>

, а затем регистрируйте модуль при инициализации ObjectMapper от Jackson:

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

В настоящее время документация невелика. См. Код Hibernate4Module для доступных опций.

6
ответ дан Shane 20 August 2018 в 13:17
поделиться
  • 1
    Это решает совершенно другую проблему. – Giulio Piancastelli 8 May 2015 в 11:04
  • 2
    Тогда проблема возникает, потому что это выглядит интересно. У меня такая же проблема, как и у OP, и все трюки, в том числе и выше, не сработали. – user1567291 24 March 2016 в 19:52

Кроме того, у Jackson 1.6 есть поддержка обработки двунаправленных ссылок ... которые кажутся вам, что вы ищете ( эта запись в блоге также упоминает эту функцию)

И по состоянию на июль 2011 года существует также « jackson-module-hibernate », который может помочь в некоторых аспектах работы с объектами Hibernate, хотя не обязательно это конкретное (что делает требуют аннотации).

19
ответ дан StaxMan 20 August 2018 в 13:17
поделиться

@JsonIgnoreProperties - это ответ.

Используйте что-то вроде этого ::

@OneToMany(mappedBy = "course",fetch=FetchType.EAGER)
@JsonIgnoreProperties("course")
private Set<Student> students;
0
ответ дан SumanP 20 August 2018 в 13:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: