Статические методы не связаны с экземпляром, поэтому они не могут получить доступ к каким-либо нестатическим полям в классе.
Вы использовали бы статический метод, если метод не использует никаких полей (или только статических полей) класса.
Если используются нестатические поля класса, вы должны использовать нестатический метод.
Вы можете использовать @JsonIgnore
, чтобы разорвать цикл.
Я также встретил ту же проблему. Я использовал тип генератора @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 {
...
Теперь Джексон поддерживает избегание циклов без игнорирования полей:
Джексон - сериализация объектов с двунаправленными отношениями (избегая циклов)
Новая аннотация @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
вы можете использовать шаблон DTO для создания класса Trainee DTO без каких-либо аннотаций hibernate, и вы можете использовать jackson mapper для преобразования Trainee в TraineeDTO и bingo сообщение об ошибке disapeare:)
Для меня лучшим решением является использование @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
позволяет вам использовать фильтры и выбирать, какие поля должны быть включены при сериализации объекты. Фильтр - это просто ссылка на класс, используемая в качестве идентификатора. Итак, давайте сначала создадим фильтры:
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;
В моем случае достаточно было изменить отношение от:
@OneToMany(mappedBy = "county")
private List<Town> towns;
к:
@OneToMany
private List<Town> towns;
другое соотношение оставалось таким, каким оно было:
@ManyToOne
@JoinColumn(name = "county_id")
private County county;
Теперь вы можете использовать JsonIgnoreProperties для подавить сериализацию свойств (во время сериализации) или игнорировать обработку свойств JSON, прочитанных (во время десериализации) . Если это не то, что вы ищете, пожалуйста, продолжайте читать ниже.
(Спасибо As Zammel AlaaEddine за указание этого).
Начиная с 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 и посмотреть, что происходит в вашем коде.
Источники:
@JsonIgnore
на обратную ссылку.
– Utku Özdemir
28 December 2013 в 04:46
@JsonIgnore
.
– Piotr Nowicki
7 April 2015 в 06:57
Это отлично сработало для меня. Добавьте аннотацию @JsonIgnore в дочерний класс, где упоминается ссылка на родительский класс.
@ManyToOne
@JoinColumn(name = "ID", nullable = false, updatable = false)
@JsonIgnore
private Member member;
@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 {
, и оно должно работать.
@JsonIdentityInfo
в своем ответе выше.
– Kurt Bourbaki
31 December 2013 в 17:27
@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.
Другая проблема с этим решением состоит в том, что если вы добавляете или удаляете свойство, вы должны обновить свой конструктор и все ваши запросы.
Если вы используете Spring Data Rest, проблема может быть решена путем создания репозиториев для каждого объекта, участвующего в циклических ссылках.
Убедитесь, что вы используете 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);
Прекрасно работает для меня Устранить проблему Json Infinite Recursion при работе с Jackson
Это то, что я сделал в oneToMany и ManyToOne Mapping
@ManyToOne
@JoinColumn(name="Key")
@JsonBackReference
private LgcyIsp Key;
@OneToMany(mappedBy="LgcyIsp ")
@JsonManagedReference
private List<Safety> safety;
Вы можете использовать @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;
Поэтому вам нужно прокомментировать / пропустить указанную выше часть у Стажера
Теперь есть модуль 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 для доступных опций.
Кроме того, у Jackson 1.6 есть поддержка обработки двунаправленных ссылок ... которые кажутся вам, что вы ищете ( эта запись в блоге также упоминает эту функцию)
И по состоянию на июль 2011 года существует также « jackson-module-hibernate », который может помочь в некоторых аспектах работы с объектами Hibernate, хотя не обязательно это конкретное (что делает требуют аннотации).
@JsonIgnoreProperties - это ответ.
Используйте что-то вроде этого ::
@OneToMany(mappedBy = "course",fetch=FetchType.EAGER)
@JsonIgnoreProperties("course")
private Set<Student> students;