[java] 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> bodyStatsTrainee 클래스)을 가져 와서 json과 같은 저장 형식으로 변환합니다. 이것이 소위 마샬링 프로세스입니다. 그런 다음 Jackson은 참조의 뒷면 부분 (예 : Trainee traineeBodyStat 클래스)을 찾아 직렬화하지 않고 그대로 둡니다. 관계의이 부분은 순방향 참조 의 역 직렬화 ( 비 정렬 화 ) 중에 재구성됩니다 .

다음과 같이 코드를 변경할 수 있습니다 (쓸모없는 부분은 건너 뜁니다).

사업 목표 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 로그를 살펴 보는 것이 좋습니다.

출처 :


답변

새로운 주석 @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;