양방향 연관이있는 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은 필드를 무시하지 않고 사이클을 피할 수 있도록 지원합니다.
답변
이것은 나를 위해 완벽하게 작동했습니다. 부모 클래스에 대한 참조를 언급하는 자식 클래스에 주석 @JsonIgnore를 추가하십시오.
@ManyToOne
@JoinColumn(name = "ID", nullable = false, updatable = false)
@JsonIgnore
private Member member;