当尝试将具有双向关联的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>

当前回答

如果不能忽略该属性,请尝试修改字段的可见性。在我们的情况下,我们有旧的代码仍然提交实体与关系,所以在我的情况下,这是修复:

    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private Trainee trainee;

其他回答

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

如果你使用@JsonManagedReference, @JsonBackReference或@JsonIgnore注释,它会忽略一些字段,并使用Jackson JSON解决无限递归。

但是如果你使用@JsonIdentityInfo也避免无限递归,你可以得到所有的字段值,所以我建议你使用@JsonIdentityInfo注释。

@JsonIdentityInfo(generator= ObjectIdGenerators.UUIDGenerator.class, property="@id")

请参阅本文https://www.toptal.com/javascript/bidirectional-relationship-in-json以更好地理解@JsonIdentityInfo注释。

作为一个使用Spring Data和Lombok的人,这是我自己解决问题的方法。

@Entity
@Data
public class Foo extends BaseEntity {

    @OneToMany(fetch = FetchType.EAGER)
    @JoinColumn(name = "foo_id")
    @JsonIgnoreProperties("parent_foo")
    @EqualsAndHashCode.Exclude
    private Set<Bar> linkedBars;
}

@Entity
@Data
public class Bar extends BaseEntity {

    @Column(name = "foo_id")
    private Long parentFooId;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "foo_id", insertable = false, updatable = false)
    @JsonIgnoreProperties({"linkedBars"})
    private Foo parentFoo;
}

JsonIgnoreProperties注释停止了无限递归,正如上面讨论的许多答案一样。

@EqualsAndHashCode。Exclude可以防止由hashCode和equals引起的StackOverflowError被递归调用。

使用Set over List解决了添加多个集合字段时发生的MultipleBagFetchException。您还可以使用@Fetch(value = FetchMode.SUBSELECT)来避免笛卡尔积,但我个人没有尝试过,因为我的用例不需要它。

Bar中parentFooId的显式定义是允许将Foo实体映射到Bar。

在做了更多的分析后,我也有同样的问题,我知道,我们也可以通过在OneToMany注释中保持@JsonBackReference来获得映射实体

@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)
@JsonBackReference
private Set<BodyStat> bodyStats;

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

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