当尝试将具有双向关联的JPA对象转换为JSON时,我不断得到

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

我所找到的是这个帖子,基本上是建议避免双向关联。有人有解决这个春季bug的方法吗?

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

当前回答

在使用Spring Data Rest的情况下,可以通过为循环引用中涉及的每个实体创建存储库来解决这个问题。

其他回答

JsonIgnoreProperties[2017年更新]:

您现在可以使用JsonIgnoreProperties来抑制属性的序列化(在序列化期间),或者忽略JSON属性读取的处理(在反序列化期间)。如果这不是你想要的,请继续阅读下面的内容。

(感谢As Zammel AlaaEddine指出这一点)。


JsonManagedReference和JsonBackReference

从Jackson 1.6开始,您可以使用@JsonManagedReference和@JsonBackReference这两个注释来解决无限递归问题,而不必在序列化期间忽略getter /setter。

解释

为了使Jackson能够正常工作,关系的两个方面之一不应该被序列化,以避免导致stackoverflow错误的无限循环。

因此,Jackson取引用的前部分(您的Set<BodyStat> bodyStats in练习生类),并将其转换为类似json的存储格式;这就是所谓的编组过程。然后,Jackson查找引用的后面部分(即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和Jackson Stackoverflow问题的文章。

编辑:

你可以检查的另一个有用的注释是@JsonIdentityInfo:使用它,每次Jackson序列化你的对象时,它都会添加一个ID(或你选择的其他属性),这样它就不会每次都完全“扫描”它。当您在更多相关的对象之间有一个链循环时(例如:Order -> OrderLine -> User -> Order and over again),这可能是有用的。

在这种情况下,您必须小心,因为您可能需要多次读取对象的属性(例如,在具有多个共享同一卖家的产品列表中),而该注释阻止了您这样做。我建议经常查看firebug日志,检查Json响应,看看代码中发生了什么。

来源:

Keenformatics如何解决JSON无限递归Stackoverflow(我的博客) 杰克逊的引用 个人经验

Jackson 1.6也支持处理双向引用…看起来就像 你在寻找什么(这篇博客也提到了这个功能)

截至2011年7月,还有“jackson-module-hibernate”,它可能在处理Hibernate对象的某些方面有所帮助,尽管不一定是这个(它确实需要注释)。

出于某种原因,在我的情况下,它不能与赛特一起工作。我必须将其更改为List并使用@JsonIgnore和@ToString。排除让它工作。

用List替换Set:

//before
@OneToMany(mappedBy="client")
private Set<address> addressess;

//after
@OneToMany(mappedBy="client")
private List<address> addressess;

并添加@JsonIgnore和@ToString。排除注释:

@ManyToOne
@JoinColumn(name="client_id", nullable = false)
@JsonIgnore
@ToString.Exclude
private Client client;

您可以使用@JsonIgnore来打破循环(引用)。

您需要导入org.codehaus.jackson. annotation. jsonignore(遗留版本)或com.fasterxml.jackson.annotation.JsonIgnore(当前版本)。

你可以使用@JsonIgnore,但这将忽略可以访问的json数据,因为外键关系。因此,如果您需要外键数据(大多数时候我们需要),那么@JsonIgnore将无法帮助您。在这种情况下,请遵循以下解决方案。

你将得到无限递归,因为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;

因此,您必须在练习生中注释/省略上述部分