当尝试将具有双向关联的 JPA 对象转换为 JSON 时,我不断收到
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError)
我找到的只是 this thread其结论基本上是建议避免双向关联。有谁知道这个 spring bug 的解决方法吗?
------编辑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;
}
}
JPA-实习生 DAO 的实现:
@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();
}
}
持久性.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
)并保持原样,而不对其进行序列化。这部分关系将在前向引用的反序列化(解码)过程中重新构建。
您可以像这样更改代码(我跳过无用的部分):
业务对象 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 响应并查看代码中发生了什么。
来源:
关于java - Jackson JSON 和 Hibernate JPA 问题的无限递归,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61598744/