java - jackson JSON 和 Hibernate JPA 问题的无限递归

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

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

我只找到了this thread基本上以建议避免双向关联结束。有没有人知道这个 Spring 错误的解决方法?

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

Controller :

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>

最佳答案

JsonIgnoreProperties [2017 更新]:

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

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


JsonManagedReference 和 JsonBackReference

从 Jackson 1.6 开始,您可以使用两个注解来解决无限递归问题,而无需在序列化过程中忽略 getter/setter: @JsonManagedReference@JsonBackReference .

说明

为了让 Jackson 正常工作,关系的两侧之一不应被序列化,以避免导致您的 stackoverflow 错误的无限循环。

因此,Jackson 将引用的前半部分(您在 Trainee 类中的 Set<BodyStat> bodyStats)转换为类似 json 的存储格式;这就是所谓的编码过程。然后,Jackson 查找引用的后面部分(即 BodyStat 类中的 Trainee trainee)并保持原样,而不是对其进行序列化。这部分关系将在前向引用的反序列化(unmarshalling)期间重新构建。

您可以像这样更改您的代码(我跳过了无用的部分):

业务对象 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;

现在一切都应该正常工作了。

如果您想了解更多信息,我写了一篇关于 Json and Jackson Stackoverflow issues on Keenformatics 的文章,我的博客。

编辑:

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

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

来源:

  • Keenformatics - How To Solve JSON infinite recursion Stackoverflow (我的博客)
  • Jackson References
  • 个人经历

关于java - jackson JSON 和 Hibernate JPA 问题的无限递归,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3325387/

相关文章:

ruby - 在 Ruby 中解析 JSON 字符串

javascript - JSON:为什么正斜杠转义?

ios - 在 iOS 中从 NSDictionary 生成 JSON 字符串

json - 如何将 JSON 对象转换为 TypeScript 类?

python - 使用 Python 将 JSON 字符串转换为 dict

javascript - Chrome 发送请求错误 : TypeError: Converting

php - 在 PHP 中检查字符串是否为 JSON 的最快方法?

java - 我应该将 jackson 的 ObjectMapper 声明为静态字段吗?

json - 在 JSON 中表示 null

java - jackson VS。 Gson