Jackson JSON 및 Hibernate JPA 문제로 무한 재귀
양방향 연관이있는 JPA 객체를 JSON으로 변환하려고 할 때 계속
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError)
모든 I의 FOUND는 이 스레드 기본적으로 양방향 연결을 방지하기 위해 추천으로 결론. 이 봄 버그에 대한 해결 방법에 대한 아이디어가 있습니까?
------ 편집 2010-07-24 16:26:22 -------
코드 조각 :
사업 목표 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;
}
}
교육생 DAO의 JPA 구현 :
@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>
@JsonIgnore
사이클을 중단하는 데 사용할 수 있습니다 .
JsonIgnoreProperties [2017 업데이트] :
이제 JsonIgnoreProperties 를 사용 하여 특성 직렬화 를 억제하거나 (직렬화 중) JSON 특성 읽기 처리 (직렬화 해제 중)를 무시할 수 있습니다. 원하는 것이 아닌 경우 아래를 계속 읽으십시오.
(이 점을 지적한 As Zammel AlaaEddine에게 감사합니다).
JsonManagedReference 및 JsonBackReference
잭슨 1.6 이후 당신은 직렬화하는 동안 게터 / 세터를 무시하지 않고 무한 재귀 문제를 해결하기 위해 두 개의 주석을 사용할 수 있습니다 @JsonManagedReference
및 @JsonBackReference
.
설명
Jackson이 제대로 작동하려면 스택 오버플로 오류를 일으키는 인피 트 루프를 피하기 위해 관계의 양면 중 하나를 직렬화하면 안됩니다.
따라서 Jackson은 참조의 앞 부분 ( Set<BodyStat> bodyStats
Trainee 클래스)을 가져 와서 json과 같은 저장 형식으로 변환합니다. 이것이 소위 마샬링 프로세스입니다. 그런 다음 Jackson은 참조의 뒷면 부분 (예 : Trainee trainee
BodyStat 클래스)을 찾아 직렬화하지 않고 그대로 둡니다. 관계의이 부분은 순방향 참조 의 역 직렬화 ( 비 정렬 화 ) 중에 재구성됩니다 .
다음과 같이 코드를 변경할 수 있습니다 (쓸모없는 부분은 건너 뜁니다).
사업 목표 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;
이제 모두 제대로 작동합니다.
더 많은 정보를 원한다면 내 블로그 인 Keenformatics 에 Json and Jackson Stackoverflow 문제에 관한 기사를 썼습니다 .
편집하다:
확인할 수있는 또 다른 유용한 주석은 @JsonIdentityInfo입니다 . Jackson을 사용하여 객체를 직렬화 할 때마다 ID (또는 선택한 다른 속성)가 추가되어 매번 다시 완전히 "스캔"되지 않습니다. 이것은 더 많은 상호 관련 된 객체 (예 : 주문-> 주문 라인-> 사용자-> 주문 이상) 사이에 체인 루프가있는 경우 유용 할 수 있습니다.
이 경우 객체 속성을 두 번 이상 읽어야하므로 (예 : 동일한 판매자를 공유하는 더 많은 제품이있는 제품 목록에서)이 주석을 사용하면 그렇게 할 수 없으므로주의해야합니다. Json 응답을 확인하고 코드에서 무슨 일이 일어나고 있는지 확인하려면 항상 Firebug 로그를 살펴 보는 것이 좋습니다.
출처 :
- Keenformatics - JSON 무한 재귀 유래 해결하는 방법 (내 블로그)
- 잭슨 참조
- 개인적인 경험
새로운 주석 @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
또한 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 {
작동해야합니다.
또한 Jackson 1.6은 양방향 참조 처리 를 지원합니다 ... 원하는 것 같습니다 ( 이 블로그 항목 에도 기능이 언급되어 있습니다)
그리고 2011 년 7 월 현재, " jackson-module-hibernate "도 있는데,이 특성은 반드시 주석이 필요한 특정 오브젝트는 아니지만 Hibernate 오브젝트 처리의 일부 측면에서 도움이 될 수 있습니다.
이제 Jackson은 필드를 무시하지 않고 사이클을 피할 수 있습니다.
Jackson-쌍방향 관계를 가진 엔티티의 직렬화 (주기를 피함)
이것은 나를 위해 완벽하게 작동했습니다. 부모 클래스에 대한 참조를 언급하는 자식 클래스에 주석 @JsonIgnore를 추가하십시오.
@ManyToOne
@JoinColumn(name = "ID", nullable = false, updatable = false)
@JsonIgnore
private Member member;
직렬화 할 때 Hibernate 지연 초기화 문제를 처리하도록 특별히 설계된 Jackson 모듈 (Jackson 2 용)이 있습니다.
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>
Jackson의 ObjectMapper를 초기화 할 때 모듈을 등록하십시오.
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate4Module());
현재 문서가 좋지 않습니다. 사용 가능한 옵션에 대해서는 Hibernate4Module 코드 를 참조하십시오 .
나를 위해 작동 벌금 잭슨과 함께 작업 할 때 JSON 무한 재귀 문제 해결
이것이 oneToMany 및 ManyToOne 매핑에서 수행 한 작업입니다.
@ManyToOne
@JoinColumn(name="Key")
@JsonBackReference
private LgcyIsp Key;
@OneToMany(mappedBy="LgcyIsp ")
@JsonManagedReference
private List<Safety> safety;
나에게 가장 좋은 해결책은 @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;
}
}
serializer가 View를 사용하도록 Test를 변경하십시오.
@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);
}
}
이제이 코드를 실행하면 주석이 달린 속성을 직렬화하려고한다고 명시 적으로 말했기 때문에 무한 재귀 문제가 해결되었습니다 @JsonView(Filter.CompanyData.class)
.
에서 회사의 역 참조에 도달하면 Employee
주석이 없는지 확인하고 직렬화를 무시합니다. REST API를 통해 전송할 데이터를 선택할 수있는 강력하고 유연한 솔루션도 있습니다.
Spring을 사용하면 원하는 @JsonView
필터로 REST Controllers 메소드에 주석을 달 수 있으며 직렬화는 반환 객체에 투명하게 적용됩니다.
확인해야 할 경우에 사용되는 가져 오기는 다음과 같습니다.
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;
어디에서나 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);
@JsonIgnoreProperties 가 답입니다.
이런 식으로 사용하십시오 ::
@OneToMany(mappedBy = "course",fetch=FetchType.EAGER)
@JsonIgnoreProperties("course")
private Set<Student> students;
@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;
따라서 연수생 에게 위의 부분을 언급 / 생략해야합니다.
DTO 패턴을 사용하여 주석 최대 절전 모드없이 클래스 TraineeDTO를 만들 수 있으며 Jackson Jackson을 사용하여 Trainee를 TraineeDTO로 변환하고 오류 메시지 disapeare를 빙고 할 수 있습니다 :)
나는 또한 같은 문제를 만났다. 내가 사용 @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 {
...
@ManyToOne 엔티티에는 @JsonBackReference를, 엔티티 클래스를 포함하는 @onetomany에는 @JsonManagedReference를 사용해야합니다.
@OneToMany(
mappedBy = "queue_group",fetch = FetchType.LAZY,
cascade = CascadeType.ALL
)
@JsonManagedReference
private Set<Queue> queues;
@ManyToOne(cascade=CascadeType.ALL)
@JoinColumn(name = "qid")
// @JsonIgnore
@JsonBackReference
private Queue_group queue_group;
이 문제가 있었지만 엔티티에 주석을 사용하고 싶지 않았으므로 클래스의 생성자를 작성하여 해결했습니다.이 생성자는이 엔티티를 참조하는 엔티티에 대한 참조를 가져서는 안됩니다. 이 시나리오를 가정 해 봅시다.
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 버전 에서 허용하지 않기 때문에 필요합니다. 뷰에 두 엔티티를 표시하려면 다음을 수행하십시오.
public A getAById(int id); //THE A id
public List<B> getBsByAId(int idA); //the A id.
이 솔루션의 다른 문제는 속성을 추가하거나 제거하면 생성자와 모든 쿼리를 업데이트해야한다는 것입니다.
Spring Data Rest를 사용하는 경우 주기적 참조와 관련된 모든 엔티티에 대해 저장소를 작성하여 문제를 해결할 수 있습니다.
속성을 무시할 수 없으면 필드의 가시성을 수정하십시오. 우리의 경우에는 오래된 코드가 여전히 관계가있는 엔티티를 제출하므로 내 경우에는 이것이 수정되었습니다.
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Trainee trainee;
'Programming' 카테고리의 다른 글
삽입 모드로 붙여 넣기 (0) | 2020.02.28 |
---|---|
Firebase에서 백그라운드에서 앱을 사용할 때 알림을 처리하는 방법 (0) | 2020.02.27 |
Java에서 재귀 적으로 디렉토리 삭제 (0) | 2020.02.27 |
PowerShell에서 스크립트 종료 (0) | 2020.02.27 |
트위터 부트 스트랩 3 : 미디어 쿼리를 사용하는 방법? (0) | 2020.02.27 |