無限遞歸與Jackson JSON和Hibernate JPA問題

當嘗試將具備雙向關聯的JPA對象轉換爲JSON時,我不斷 html

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

我所發現的只是該線程 ,基本上以建議避免雙向關聯爲結尾。 有誰知道這個春季錯誤的解決方法? java

------編輯2010-07-24 16:26:22 ------- git

代碼段: github

業務對象1: web

@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: spring

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;

控制器: json

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

JPA實施學員DAO: app

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

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

#1樓

如今,傑克遜支持在不忽略字段的狀況下避免循環: ui

傑克遜-具備雙向關係的實體的序列化(避免循環)


#2樓

JsonIgnoreProperties [2017更新]:

如今,您可使用JsonIgnoreProperties 抑制屬性的序列化(在序列化期間),或者忽略對JSON屬性讀取的處理(在反序列化期間) 。 若是這不是您想要的,請繼續閱讀如下內容。

(感謝As Zammel AlaaEddine指出了這一點)。


JsonManagedReference和JsonBackReference

從Jackson 1.6開始,您可使用兩個批註來解決無限遞歸問題,而沒必要在序列化過程當中忽略getter / setter: @JsonManagedReference@JsonBackReference

說明

爲了使Jackson正常工做,不該將關係的兩個方面之一進行序列化,以免引發您stackoverflow錯誤的infite循環。

所以,Jackson接受了引用的前一部分(Trainee類中的Set<BodyStat> bodyStats ),並將其轉換爲相似json的存儲格式; 這就是所謂的編組過程。 而後,Jackson尋找參考的後半部分(即BodyStat類中的Trainee trainee ),並保持原樣,而不對其進行序列化。 關係的這一部分將在前向引用的反序列化( 反編組 )期間從新構建。

您能夠這樣更改代碼(我跳過了無用的部分):

業務對象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和Jackson Stackoverflow問題的文章。

編輯:

您能夠檢查的另外一個有用的註釋是@JsonIdentityInfo :使用它,每次Jackson序列化您的對象時,它都會向其中添加一個ID(或您選擇的另外一個屬性),這樣就不會每次都徹底「掃描」它。 當您在更多相互關聯的對象之間造成鏈循環時(例如:Order-> OrderLine-> User-> Order and over over),這頗有用。

在這種狀況下,您必需要當心,由於您可能須要屢次讀取對象的屬性(例如,在一個產品列表中有多個共享同一賣方的產品),而且此註釋阻止您這樣作。 我建議始終查看Firebug日誌,以檢查Json響應,並查看代碼中發生了什麼。

資料來源:


#3樓

另外,使用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 {

它應該工做。


#4樓

就我而言,將關係從如下位置更改就足夠了:

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

至:

@OneToMany
private List<Town> towns;

另外一個關係保持不變:

@ManyToOne
@JoinColumn(name = "county_id")
private County county;

#5樓

如今有一個專爲Jackson 2設計的Jackson模塊,用於處理序列化時的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>

而後在初始化Jackson的ObjectMapper時註冊該模塊:

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

文檔目前不是很好。 請參閱Hibernate4Module代碼以獲取可用選項。

相關文章
相關標籤/搜索